@solard/cli 0.2.27 → 0.2.31

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.
@@ -1,200 +1,205 @@
1
- import { PublicKey } from "@solana/web3.js";
2
- import {
3
- readMint,
4
- sol,
5
- tokenAmount,
6
- transferTokenIxs,
7
- type SenderId,
8
- type Solard,
9
- } from "@solard/core";
10
-
11
- import { resolveDestinationRef } from "../refs.ts";
12
-
13
- const CANONICAL_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
14
-
15
- export type TransferCliFlags = ReadonlyMap<string, string>;
16
-
17
- type RunTransferCommandArgs = {
18
- slrd: Solard;
19
- values: string[];
20
- flags: TransferCliFlags;
21
- };
22
-
23
- function requiredFlag(flags: TransferCliFlags, key: string): string {
24
- const value = flags.get(key);
25
- if (!value || value === "true") {
26
- throw new Error(`Missing --${key} <value>`);
27
- }
28
- return value;
29
- }
30
-
31
- function senderId(flags: TransferCliFlags): SenderId {
32
- const value = flags.get("sender") ?? "rpc";
33
- if (value !== "rpc" && value !== "helius" && value !== "jito") {
34
- throw new Error(`Invalid --sender: ${value}`);
35
- }
36
- return value;
37
- }
38
-
39
- function nonNegativeInteger(
40
- flags: TransferCliFlags,
41
- key: string,
42
- fallback: number,
43
- ): number {
44
- const raw = flags.get(key);
45
- const value = raw == null ? fallback : Number(raw);
46
- if (!Number.isInteger(value) || value < 0) {
47
- throw new Error(`--${key} must be a non-negative integer`);
48
- }
49
- return value;
50
- }
51
-
52
- function positiveInteger(
53
- flags: TransferCliFlags,
54
- key: string,
55
- fallback: number,
56
- ): number {
57
- const value = nonNegativeInteger(flags, key, fallback);
58
- if (value <= 0) throw new Error(`--${key} must be a positive integer`);
59
- return value;
60
- }
61
-
62
- export async function runTransferCommand({
63
- slrd,
64
- values,
65
- flags,
66
- }: RunTransferCommandArgs): Promise<Record<string, unknown>> {
67
- const recipientInput = values[0] === "sol" ? values[1] : values[0];
68
- if (!recipientInput) {
69
- throw new Error(
70
- "Usage: slrd transfer <contact|wallet|address> --wallet <wallet> (--sol <amount> | --token <USDC|mint> --amount <ui>) [--sender rpc|helius|jito] [--simulate-only]",
71
- );
72
- }
73
-
74
- const recipient = resolveDestinationRef(slrd, recipientInput);
75
- const wallet = requiredFlag(flags, "wallet");
76
- const via = senderId(flags);
77
- const tokenRef = flags.get("token")?.trim();
78
- const solValue = flags.get("sol")?.trim();
79
-
80
- if (Boolean(tokenRef) === Boolean(solValue)) {
81
- throw new Error(
82
- "Transfer requires exactly one of --sol <amount> or --token <USDC|mint> --amount <ui>",
83
- );
84
- }
85
-
86
- const cuLimit = positiveInteger(
87
- flags,
88
- "cu-limit",
89
- tokenRef ? 30_000 : 10_000,
90
- );
91
- const priorityMicroLamports = nonNegativeInteger(
92
- flags,
93
- "priority-micro-lamports",
94
- 0,
95
- );
96
-
97
- if (tokenRef) {
98
- const amountUi = requiredFlag(flags, "amount");
99
- const normalizedToken = tokenRef.replace(/^\$/, "").trim();
100
- const isUsdc = normalizedToken.toUpperCase() === "USDC";
101
- const mint = new PublicKey(isUsdc ? CANONICAL_USDC_MINT : normalizedToken);
102
- const mintState = await readMint(slrd.connection(), mint, slrd.cache);
103
- const amountRaw = tokenAmount(
104
- amountUi,
105
- mint,
106
- mintState.decimals,
107
- mintState.tokenProgram,
108
- ).raw;
109
- if (amountRaw <= 0n) {
110
- throw new Error("Token transfer amount must be greater than zero");
111
- }
112
-
113
- const owner = slrd.signer(wallet).publicKey;
114
- const destinationOwner = new PublicKey(recipient.address);
115
- const transfer = transferTokenIxs({
116
- payer: owner,
117
- owner,
118
- recipient: destinationOwner,
119
- mint,
120
- amountRaw,
121
- decimals: mintState.decimals,
122
- tokenProgram: mintState.tokenProgram,
123
- });
124
-
125
- const composer = slrd
126
- .tx(wallet)
127
- .addMany(transfer.instructions, {
128
- kind: "transfer-token",
129
- mint,
130
- recipient: destinationOwner,
131
- meta: { raw: amountRaw.toString() },
132
- })
133
- .track({
134
- address: transfer.destination,
135
- kind: "token",
136
- mint,
137
- })
138
- .priorityFee({ cuLimit, microLamports: priorityMicroLamports });
139
-
140
- if (flags.has("simulate-only")) {
141
- const result = await slrd.simulatePlan(await composer.build());
142
- return {
143
- mode: "simulation",
144
- wallet,
145
- recipient,
146
- token: isUsdc ? "USDC" : mint.toBase58(),
147
- mint: mint.toBase58(),
148
- amount: amountUi,
149
- decimals: mintState.decimals,
150
- amountRaw,
151
- result,
152
- };
153
- }
154
-
155
- const receipt = await composer.send({
156
- via,
157
- kind: "transfer-token",
158
- skipSimulation: flags.has("skip-simulation"),
159
- skipPreflight:
160
- flags.has("skip-preflight") || flags.has("skip-simulation"),
161
- });
162
-
163
- return {
164
- ...receipt,
165
- wallet,
166
- recipient,
167
- token: isUsdc ? "USDC" : mint.toBase58(),
168
- mint: mint.toBase58(),
169
- amount: amountUi,
170
- decimals: mintState.decimals,
171
- amountRaw,
172
- };
173
- }
174
-
175
- const amount = solValue!;
176
- const composer = slrd
177
- .tx(wallet)
178
- .transferSol(recipient.address, sol(amount))
179
- .priorityFee({ cuLimit, microLamports: priorityMicroLamports });
180
-
181
- if (flags.has("simulate-only")) {
182
- const result = await slrd.simulatePlan(await composer.build());
183
- return {
184
- mode: "simulation",
185
- wallet,
186
- recipient,
187
- sol: amount,
188
- result,
189
- };
190
- }
191
-
192
- const receipt = await composer.send({
193
- via,
194
- kind: "transfer-sol",
195
- skipSimulation: flags.has("skip-simulation"),
196
- skipPreflight: flags.has("skip-preflight") || flags.has("skip-simulation"),
197
- });
198
-
199
- return { ...receipt, recipient };
200
- }
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ readMint,
4
+ sol,
5
+ tokenAmount,
6
+ transferTokenIxs,
7
+ estimatePlanFee,
8
+ tradeResult,
9
+ type SenderId,
10
+ type Solard,
11
+ } from "@solard/core";
12
+ import { tradeFeeOptions } from "./trade-fees.ts";
13
+
14
+ import { resolveDestinationRef } from "../refs.ts";
15
+
16
+ const CANONICAL_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
17
+
18
+ export type TransferCliFlags = ReadonlyMap<string, string>;
19
+
20
+ type RunTransferCommandArgs = {
21
+ slrd: Solard;
22
+ values: string[];
23
+ flags: TransferCliFlags;
24
+ };
25
+
26
+ function requiredFlag(flags: TransferCliFlags, key: string): string {
27
+ const value = flags.get(key);
28
+ if (!value || value === "true") {
29
+ throw new Error(`Missing --${key} <value>`);
30
+ }
31
+ return value;
32
+ }
33
+
34
+ function senderId(flags: TransferCliFlags): SenderId {
35
+ const value = flags.get("sender") ?? "rpc";
36
+ if (value !== "rpc" && value !== "helius" && value !== "jito") {
37
+ throw new Error(`Invalid --sender: ${value}`);
38
+ }
39
+ return value;
40
+ }
41
+
42
+ function nonNegativeInteger(
43
+ flags: TransferCliFlags,
44
+ key: string,
45
+ fallback: number,
46
+ ): number {
47
+ const raw = flags.get(key);
48
+ const value = raw == null ? fallback : Number(raw);
49
+ if (!Number.isInteger(value) || value < 0) {
50
+ throw new Error(`--${key} must be a non-negative integer`);
51
+ }
52
+ return value;
53
+ }
54
+
55
+ function positiveInteger(
56
+ flags: TransferCliFlags,
57
+ key: string,
58
+ fallback: number,
59
+ ): number {
60
+ const value = nonNegativeInteger(flags, key, fallback);
61
+ if (value <= 0) throw new Error(`--${key} must be a positive integer`);
62
+ return value;
63
+ }
64
+
65
+ export async function runTransferCommand({
66
+ slrd,
67
+ values,
68
+ flags,
69
+ }: RunTransferCommandArgs): Promise<Record<string, unknown>> {
70
+ const recipientInput = values[0] === "sol" ? values[1] : values[0];
71
+ if (!recipientInput) {
72
+ throw new Error(
73
+ "Usage: slrd transfer <contact|wallet|address> --wallet <wallet> (--sol <amount> | --token <USDC|mint> --amount <ui>) [--sender rpc|helius|jito] [--simulate-only]",
74
+ );
75
+ }
76
+
77
+ const recipient = resolveDestinationRef(slrd, recipientInput);
78
+ const wallet = requiredFlag(flags, "wallet");
79
+ const via = senderId(flags);
80
+ const tokenRef = flags.get("token")?.trim();
81
+ const solValue = flags.get("sol")?.trim();
82
+
83
+ if (Boolean(tokenRef) === Boolean(solValue)) {
84
+ throw new Error(
85
+ "Transfer requires exactly one of --sol <amount> or --token <USDC|mint> --amount <ui>",
86
+ );
87
+ }
88
+
89
+ const cuLimit = positiveInteger(
90
+ flags,
91
+ "cu-limit",
92
+ tokenRef ? 30_000 : 10_000,
93
+ );
94
+ const feeOptions = tradeFeeOptions(flags);
95
+ const priorityMicroLamports = feeOptions.priorityFee.microLamports;
96
+ const executionOptions = {
97
+ ...feeOptions,
98
+ priorityFee: { cuLimit, microLamports: priorityMicroLamports },
99
+ skipSimulation: flags.has("skip-simulation"),
100
+ skipPreflight: flags.has("skip-preflight") || flags.has("skip-simulation"),
101
+ };
102
+
103
+ if (tokenRef) {
104
+ const amountUi = requiredFlag(flags, "amount");
105
+ const normalizedToken = tokenRef.replace(/^\$/, "").trim();
106
+ const isUsdc = normalizedToken.toUpperCase() === "USDC";
107
+ const mint = new PublicKey(isUsdc ? CANONICAL_USDC_MINT : normalizedToken);
108
+ const mintState = await readMint(slrd.connection(), mint, slrd.cache);
109
+ const amountRaw = tokenAmount(
110
+ amountUi,
111
+ mint,
112
+ mintState.decimals,
113
+ mintState.tokenProgram,
114
+ ).raw;
115
+ if (amountRaw <= 0n) {
116
+ throw new Error("Token transfer amount must be greater than zero");
117
+ }
118
+
119
+ const owner = slrd.signer(wallet).publicKey;
120
+ const destinationOwner = new PublicKey(recipient.address);
121
+ const transfer = transferTokenIxs({
122
+ payer: owner,
123
+ owner,
124
+ recipient: destinationOwner,
125
+ mint,
126
+ amountRaw,
127
+ decimals: mintState.decimals,
128
+ tokenProgram: mintState.tokenProgram,
129
+ });
130
+
131
+ const composer = slrd
132
+ .tx(wallet)
133
+ .addMany(transfer.instructions, {
134
+ kind: "transfer-token",
135
+ mint,
136
+ recipient: destinationOwner,
137
+ meta: { raw: amountRaw.toString() },
138
+ })
139
+ .track({
140
+ address: transfer.destination,
141
+ kind: "token",
142
+ mint,
143
+ })
144
+ .priorityFee({ cuLimit, microLamports: priorityMicroLamports });
145
+
146
+ if (flags.has("simulate-only")) {
147
+ const prepared = await slrd.prepareTradePlan(wallet, await composer.build(), executionOptions);
148
+ const result = await slrd.simulatePlan(prepared.plan);
149
+ return {
150
+ mode: "simulation",
151
+ wallet,
152
+ recipient,
153
+ token: isUsdc ? "USDC" : mint.toBase58(),
154
+ mint: mint.toBase58(),
155
+ amount: amountUi,
156
+ decimals: mintState.decimals,
157
+ amountRaw,
158
+ result,
159
+ feeEstimate: await estimatePlanFee(slrd.connection(), prepared.plan),
160
+ priorityMicroLamports: prepared.priorityMicroLamports,
161
+ cuLimit,
162
+ };
163
+ }
164
+
165
+ const execution = await slrd.executeTradePlan(wallet, () => composer.build(), via, "transfer-token", executionOptions);
166
+ const receipt = tradeResult(execution.receipt, execution.attempts, execution.submission.executionId);
167
+
168
+ return {
169
+ ...receipt,
170
+ wallet,
171
+ recipient,
172
+ token: isUsdc ? "USDC" : mint.toBase58(),
173
+ mint: mint.toBase58(),
174
+ amount: amountUi,
175
+ decimals: mintState.decimals,
176
+ amountRaw,
177
+ };
178
+ }
179
+
180
+ const amount = solValue!;
181
+ const composer = slrd
182
+ .tx(wallet)
183
+ .transferSol(recipient.address, sol(amount))
184
+ .priorityFee({ cuLimit, microLamports: priorityMicroLamports });
185
+
186
+ if (flags.has("simulate-only")) {
187
+ const prepared = await slrd.prepareTradePlan(wallet, await composer.build(), executionOptions);
188
+ const result = await slrd.simulatePlan(prepared.plan);
189
+ return {
190
+ mode: "simulation",
191
+ wallet,
192
+ recipient,
193
+ sol: amount,
194
+ result,
195
+ feeEstimate: await estimatePlanFee(slrd.connection(), prepared.plan),
196
+ priorityMicroLamports: prepared.priorityMicroLamports,
197
+ cuLimit,
198
+ };
199
+ }
200
+
201
+ const execution = await slrd.executeTradePlan(wallet, () => composer.build(), via, "transfer-sol", executionOptions);
202
+ const receipt = tradeResult(execution.receipt, execution.attempts, execution.submission.executionId);
203
+
204
+ return { ...receipt, recipient, wallet, sol: amount };
205
+ }