@provablehq/shield-swap-cli 0.7.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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/balances-6SU4DCKM.js +66 -0
- package/dist/balances-6SU4DCKM.js.map +1 -0
- package/dist/chunk-2OT6LZPW.js +178 -0
- package/dist/chunk-2OT6LZPW.js.map +1 -0
- package/dist/chunk-IBVZHLUT.js +152 -0
- package/dist/chunk-IBVZHLUT.js.map +1 -0
- package/dist/chunk-IHYFMX5A.js +54 -0
- package/dist/chunk-IHYFMX5A.js.map +1 -0
- package/dist/collect-G3GNFL57.js +240 -0
- package/dist/collect-G3GNFL57.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/liquidity-RB5MKGPA.js +352 -0
- package/dist/liquidity-RB5MKGPA.js.map +1 -0
- package/dist/liquidity-e2e-WCTSSZYS.js +309 -0
- package/dist/liquidity-e2e-WCTSSZYS.js.map +1 -0
- package/dist/mint-6OKWODQG.js +253 -0
- package/dist/mint-6OKWODQG.js.map +1 -0
- package/dist/pools-NWQNFJ7W.js +184 -0
- package/dist/pools-NWQNFJ7W.js.map +1 -0
- package/dist/positions-MILT3BRU.js +134 -0
- package/dist/positions-MILT3BRU.js.map +1 -0
- package/dist/session.d.ts +161 -0
- package/dist/session.js +31 -0
- package/dist/session.js.map +1 -0
- package/dist/setup-CZI3SHUT.js +229 -0
- package/dist/setup-CZI3SHUT.js.map +1 -0
- package/dist/swap-W72XGG7Y.js +143 -0
- package/dist/swap-W72XGG7Y.js.map +1 -0
- package/dist/swap-concurrent-IHGWJMST.js +169 -0
- package/dist/swap-concurrent-IHGWJMST.js.map +1 -0
- package/dist/swap-history-FBXGMVRJ.js +371 -0
- package/dist/swap-history-FBXGMVRJ.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirmed,
|
|
3
|
+
done,
|
|
4
|
+
fail,
|
|
5
|
+
flags,
|
|
6
|
+
output,
|
|
7
|
+
run,
|
|
8
|
+
step,
|
|
9
|
+
warn
|
|
10
|
+
} from "./chunk-IBVZHLUT.js";
|
|
11
|
+
import "./chunk-IHYFMX5A.js";
|
|
12
|
+
import {
|
|
13
|
+
formatAmount,
|
|
14
|
+
loadSession,
|
|
15
|
+
pollUntil
|
|
16
|
+
} from "./chunk-2OT6LZPW.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/liquidity-e2e.ts
|
|
19
|
+
var USAGE = `shield-swap liquidity-e2e \u2014 the whole position lifecycle in one run
|
|
20
|
+
|
|
21
|
+
--pair <symbol:symbol> pool to use, e.g. USDCx:ETH
|
|
22
|
+
--pool <poolKey> exact pool
|
|
23
|
+
--percent <n> share of the private balance of each side to
|
|
24
|
+
commit, default 0.1
|
|
25
|
+
--range-pct <n> range half-width around the price, default 5
|
|
26
|
+
--network <testnet|mainnet> default testnet
|
|
27
|
+
--execute actually submit the five transactions
|
|
28
|
+
--json machine-readable output
|
|
29
|
+
|
|
30
|
+
With neither --pair nor --pool, the deepest pool this account is funded on both
|
|
31
|
+
sides of is chosen. A completed run leaves nothing behind: the position it opens
|
|
32
|
+
is the position it burns.`;
|
|
33
|
+
async function main(argv) {
|
|
34
|
+
const args = flags(
|
|
35
|
+
{
|
|
36
|
+
pair: { type: "string" },
|
|
37
|
+
pool: { type: "string" },
|
|
38
|
+
percent: { type: "string" },
|
|
39
|
+
"range-pct": { type: "string" }
|
|
40
|
+
},
|
|
41
|
+
USAGE,
|
|
42
|
+
argv
|
|
43
|
+
);
|
|
44
|
+
const percent = args.percent ? Number(args.percent) : 0.1;
|
|
45
|
+
if (!(percent > 0) || percent > 100) {
|
|
46
|
+
fail(`--percent must be greater than 0 and at most 100, got ${args.percent}`);
|
|
47
|
+
}
|
|
48
|
+
const rangePercent = args["range-pct"] ? Number(args["range-pct"]) : 5;
|
|
49
|
+
const share = (total, pct) => total * BigInt(Math.round(pct * 100)) / 10000n;
|
|
50
|
+
await run(async () => {
|
|
51
|
+
const { client, account, network } = await loadSession({ network: args.network });
|
|
52
|
+
done(`session on ${network} for ${account.address}`);
|
|
53
|
+
const tokens = await client.listTokens();
|
|
54
|
+
const infoOf = (id) => tokens.find((token) => token.id === id);
|
|
55
|
+
const balances = await client.getBalances();
|
|
56
|
+
const held = (id) => balances[id]?.private ?? 0n;
|
|
57
|
+
let poolKey = args.pool;
|
|
58
|
+
if (!poolKey) {
|
|
59
|
+
step("reading the pool index");
|
|
60
|
+
const listed = (await client.api.getPools({ limit: 100 })).data;
|
|
61
|
+
let candidates = listed;
|
|
62
|
+
if (args.pair) {
|
|
63
|
+
const [left, right] = args.pair.split(":");
|
|
64
|
+
if (!left || !right) throw new Error(`"${args.pair}" is not symbol:symbol, e.g. USDCx:ETH`);
|
|
65
|
+
const [a, b] = await Promise.all([client.tokenData(left), client.tokenData(right)]);
|
|
66
|
+
candidates = listed.filter(
|
|
67
|
+
(pool2) => pool2.token0 === a.id && pool2.token1 === b.id || pool2.token0 === b.id && pool2.token1 === a.id
|
|
68
|
+
);
|
|
69
|
+
if (!candidates.length) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`no pool pairs ${a.symbol} with ${b.symbol} on ${network}. Run \`shield-swap pools\` to see what exists.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const funded = candidates.filter((pool2) => held(pool2.token0) > 0n && held(pool2.token1) > 0n);
|
|
76
|
+
if (!funded.length) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"this account is not funded on both sides of any listed pool. `shield-swap balances` shows what it holds; `shield-swap setup` can draw testnet funds."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
step(`ranking ${funded.length} pool(s) this account can mint into`);
|
|
82
|
+
const withDepth = await Promise.all(
|
|
83
|
+
funded.map(async (pool2) => ({ pool: pool2, liquidity: (await client.getSlot({ poolKey: pool2.key }))?.liquidity ?? 0n }))
|
|
84
|
+
);
|
|
85
|
+
withDepth.sort((x, y) => y.liquidity > x.liquidity ? 1 : y.liquidity < x.liquidity ? -1 : 0);
|
|
86
|
+
poolKey = withDepth[0].pool.key;
|
|
87
|
+
}
|
|
88
|
+
const pool = await client.getPool({ poolKey });
|
|
89
|
+
if (!pool) throw new Error(`no pool ${poolKey} on ${network} \u2014 check the key with \`shield-swap pools\`.`);
|
|
90
|
+
const token0 = infoOf(pool.token0);
|
|
91
|
+
const token1 = infoOf(pool.token1);
|
|
92
|
+
if (!token0 || !token1) throw new Error(`the registry does not describe both tokens of pool ${poolKey}.`);
|
|
93
|
+
const pair = `${token0.symbol}/${token1.symbol}`;
|
|
94
|
+
const controls = await client.getTradeControls({ poolKey });
|
|
95
|
+
if (!controls.tradeable) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`pool ${poolKey} is gated on chain right now (global pause ${controls.globalPaused}, pool enabled ${controls.poolEnabled}, pair paused ${controls.pairPaused}) \u2014 every step of this run would revert.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const budget0 = share(held(pool.token0), percent);
|
|
101
|
+
const budget1 = share(held(pool.token1), percent);
|
|
102
|
+
step(`pricing ${percent}% of each side over a \xB1${rangePercent}% range`);
|
|
103
|
+
const preview = await client.previewMint({
|
|
104
|
+
poolKey,
|
|
105
|
+
amount0Desired: budget0,
|
|
106
|
+
amount1Desired: budget1,
|
|
107
|
+
rangePercent
|
|
108
|
+
});
|
|
109
|
+
if (preview.liquidity === 0n) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${percent}% of this account's ${pair} balances backs no liquidity over ticks ${preview.tickLower}\u2026${preview.tickUpper}. Raise --percent, or narrow --range-pct.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (!preview.inRange) {
|
|
115
|
+
warn(
|
|
116
|
+
`the pool trades at tick ${preview.tickCurrent}, outside ${preview.tickLower}\u2026${preview.tickUpper}: this run will still complete, but the position earns no fees while it sits out of range`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const planLines = [
|
|
120
|
+
["pool", `${pair} fee ${preview.fee} spacing ${preview.tickSpacing}`],
|
|
121
|
+
["range", `ticks ${preview.tickLower}\u2026${preview.tickUpper} (${preview.inRange ? "in range" : "OUT OF RANGE"})`],
|
|
122
|
+
[
|
|
123
|
+
"1 mint",
|
|
124
|
+
`${formatAmount(preview.amount0, token0.decimals, token0.symbol)} + ${formatAmount(preview.amount1, token1.decimals, token1.symbol)}`
|
|
125
|
+
],
|
|
126
|
+
["2 increase", "the same amounts again"],
|
|
127
|
+
["3 decrease", "all of the liquidity, booking it as owed"],
|
|
128
|
+
["4 collect", "everything owed, paid to the withdrawal address"],
|
|
129
|
+
["5 burn", "the emptied position"],
|
|
130
|
+
["owner", `${account.address} (also the withdrawal address)`]
|
|
131
|
+
];
|
|
132
|
+
if (!confirmed({ execute: args.execute, network, plan: planLines })) {
|
|
133
|
+
output({ network, submitted: false, poolKey, preview }, () => {
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const imports = await client.resolveDexImports({
|
|
138
|
+
tokenPrograms: [token0.ammTokenProgram, token1.ammTokenProgram].filter((program) => !!program)
|
|
139
|
+
});
|
|
140
|
+
const progress = { positionTokenId: "", liquidity: 0n, owed: false, burned: false };
|
|
141
|
+
const recovery = () => {
|
|
142
|
+
if (!progress.positionTokenId) return "Nothing was opened, so nothing is left to recover.";
|
|
143
|
+
const id = progress.positionTokenId;
|
|
144
|
+
if (progress.burned) return "The position was already burned; nothing is left to recover.";
|
|
145
|
+
if (progress.liquidity > 0n) {
|
|
146
|
+
return `Position ${id} is open with ${progress.liquidity} liquidity. Withdraw it with \`shield-swap liquidity --position ${id} --decrease --percent 100\`, then \`shield-swap collect --position ${id} --close\`.`;
|
|
147
|
+
}
|
|
148
|
+
if (progress.owed) {
|
|
149
|
+
return `Position ${id} is drained and owed its deposit back \u2014 \`shield-swap collect --position ${id} --close\`.`;
|
|
150
|
+
}
|
|
151
|
+
return `Position ${id} is empty and can be closed with \`shield-swap collect --position ${id} --close\`.`;
|
|
152
|
+
};
|
|
153
|
+
const waitForPosition = async (predicate, what) => {
|
|
154
|
+
for (let attempt = 0; attempt < 15; attempt++) {
|
|
155
|
+
const position = await client.getPosition({ positionTokenId: progress.positionTokenId });
|
|
156
|
+
if (predicate(position)) return position;
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`the position did not ${what} within 30s of the transaction landing. ${recovery()}`);
|
|
160
|
+
};
|
|
161
|
+
const waitForFreshRecord = async (staleTag) => {
|
|
162
|
+
let tag;
|
|
163
|
+
let lastError;
|
|
164
|
+
const indexed = await pollUntil(
|
|
165
|
+
async () => {
|
|
166
|
+
try {
|
|
167
|
+
const current = await client.getOwnedPosition({ positionTokenId: progress.positionTokenId });
|
|
168
|
+
if (current && current.record.tag !== staleTag) tag = current.record.tag;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
lastError = error;
|
|
171
|
+
}
|
|
172
|
+
return tag !== void 0;
|
|
173
|
+
},
|
|
174
|
+
30,
|
|
175
|
+
2e3
|
|
176
|
+
);
|
|
177
|
+
if (!indexed || tag === void 0) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`the record scanner served no position record newer than the one the last write spent (60s), so the next transaction would be built on a spent record and dropped. ${recovery()}`,
|
|
180
|
+
lastError instanceof Error ? { cause: lastError } : void 0
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return tag;
|
|
184
|
+
};
|
|
185
|
+
const transactions = [];
|
|
186
|
+
step("1/5 minting the position \u2014 proving takes a minute or two");
|
|
187
|
+
const minted = await client.mint({
|
|
188
|
+
poolKey,
|
|
189
|
+
tickLower: preview.tickLower,
|
|
190
|
+
tickUpper: preview.tickUpper,
|
|
191
|
+
amount0Desired: preview.amount0,
|
|
192
|
+
amount1Desired: preview.amount1,
|
|
193
|
+
recipient: account.address,
|
|
194
|
+
withdrawal: account.address,
|
|
195
|
+
imports
|
|
196
|
+
});
|
|
197
|
+
transactions.push({ step: "mint", transactionId: minted.transactionId });
|
|
198
|
+
if (!minted.positionTokenId) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`the mint landed (tx ${minted.transactionId}) but returned no position id, so the rest of this run cannot address it. Find it with \`shield-swap positions\` and continue with \`shield-swap liquidity\`.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
progress.positionTokenId = minted.positionTokenId;
|
|
204
|
+
done(`minted ${minted.positionTokenId} (tx ${minted.transactionId})`);
|
|
205
|
+
const opened = await waitForPosition((position) => position !== null, "appear in the positions mapping");
|
|
206
|
+
progress.liquidity = opened.liquidity;
|
|
207
|
+
done(`chain carries ${progress.liquidity} liquidity (predicted ${preview.liquidity})`);
|
|
208
|
+
let recordTag = await waitForFreshRecord();
|
|
209
|
+
const after = await client.getBalances({ tokens: [token0.id, token1.id] });
|
|
210
|
+
const addition = await client.previewMint({
|
|
211
|
+
poolKey,
|
|
212
|
+
amount0Desired: share(after[token0.id]?.private ?? 0n, percent),
|
|
213
|
+
amount1Desired: share(after[token1.id]?.private ?? 0n, percent),
|
|
214
|
+
tickLower: preview.tickLower,
|
|
215
|
+
tickUpper: preview.tickUpper
|
|
216
|
+
});
|
|
217
|
+
if (addition.liquidity === 0n) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`there is not enough left to add a second time \u2014 ${percent}% of the remaining balance backs no liquidity over this range. ${recovery()}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
step(
|
|
223
|
+
`2/5 adding ${formatAmount(addition.amount0, token0.decimals, token0.symbol)} + ${formatAmount(addition.amount1, token1.decimals, token1.symbol)}`
|
|
224
|
+
);
|
|
225
|
+
const increased = await client.increaseLiquidity({
|
|
226
|
+
positionTokenId: progress.positionTokenId,
|
|
227
|
+
poolKey,
|
|
228
|
+
amount0Desired: addition.amount0,
|
|
229
|
+
amount1Desired: addition.amount1,
|
|
230
|
+
imports
|
|
231
|
+
});
|
|
232
|
+
transactions.push({ step: "increase", transactionId: increased.transactionId });
|
|
233
|
+
done(`increase landed (tx ${increased.transactionId})`);
|
|
234
|
+
const grown = await waitForPosition(
|
|
235
|
+
(position) => (position?.liquidity ?? 0n) > progress.liquidity,
|
|
236
|
+
"show the added liquidity"
|
|
237
|
+
);
|
|
238
|
+
progress.liquidity = grown.liquidity;
|
|
239
|
+
done(`liquidity now ${progress.liquidity}`);
|
|
240
|
+
recordTag = await waitForFreshRecord(recordTag);
|
|
241
|
+
step(`3/5 withdrawing all ${progress.liquidity} liquidity`);
|
|
242
|
+
const decreased = await client.decreaseLiquidity({
|
|
243
|
+
positionTokenId: progress.positionTokenId,
|
|
244
|
+
poolKey,
|
|
245
|
+
liquidityToRemove: progress.liquidity
|
|
246
|
+
});
|
|
247
|
+
transactions.push({ step: "decrease", transactionId: decreased.transactionId });
|
|
248
|
+
done(`decrease landed (tx ${decreased.transactionId})`);
|
|
249
|
+
const drained = await waitForPosition((position) => position?.liquidity === 0n, "drop to zero liquidity");
|
|
250
|
+
progress.liquidity = 0n;
|
|
251
|
+
progress.owed = drained.tokens_owed0 > 0n || drained.tokens_owed1 > 0n;
|
|
252
|
+
done(
|
|
253
|
+
`owed back ${formatAmount(drained.tokens_owed0, token0.decimals, token0.symbol)} + ${formatAmount(drained.tokens_owed1, token1.decimals, token1.symbol)} \u2014 a withdrawal books, it does not pay`
|
|
254
|
+
);
|
|
255
|
+
recordTag = await waitForFreshRecord(recordTag);
|
|
256
|
+
step("4/5 collecting what the position is owed");
|
|
257
|
+
const collected = await client.collect({
|
|
258
|
+
positionTokenId: progress.positionTokenId,
|
|
259
|
+
poolKey,
|
|
260
|
+
amount0Requested: drained.tokens_owed0,
|
|
261
|
+
amount1Requested: drained.tokens_owed1,
|
|
262
|
+
imports
|
|
263
|
+
});
|
|
264
|
+
transactions.push({ step: "collect", transactionId: collected.transactionId });
|
|
265
|
+
done(`collect landed (tx ${collected.transactionId}) \u2014 paid to ${account.address}`);
|
|
266
|
+
await waitForPosition(
|
|
267
|
+
(position) => position?.tokens_owed0 === 0n && position?.tokens_owed1 === 0n,
|
|
268
|
+
"clear its owed balances"
|
|
269
|
+
);
|
|
270
|
+
progress.owed = false;
|
|
271
|
+
recordTag = await waitForFreshRecord(recordTag);
|
|
272
|
+
step("5/5 burning the emptied position");
|
|
273
|
+
const burned = await client.burn({ positionTokenId: progress.positionTokenId, poolKey });
|
|
274
|
+
transactions.push({ step: "burn", transactionId: burned.transactionId });
|
|
275
|
+
done(`burn landed (tx ${burned.transactionId})`);
|
|
276
|
+
await waitForPosition((position) => position === null, "disappear from the positions mapping");
|
|
277
|
+
progress.burned = true;
|
|
278
|
+
output(
|
|
279
|
+
{
|
|
280
|
+
network,
|
|
281
|
+
submitted: true,
|
|
282
|
+
poolKey,
|
|
283
|
+
pair,
|
|
284
|
+
positionTokenId: minted.positionTokenId,
|
|
285
|
+
tickLower: preview.tickLower,
|
|
286
|
+
tickUpper: preview.tickUpper,
|
|
287
|
+
deposited0: preview.amount0 + addition.amount0,
|
|
288
|
+
deposited1: preview.amount1 + addition.amount1,
|
|
289
|
+
recovered0: drained.tokens_owed0,
|
|
290
|
+
recovered1: drained.tokens_owed1,
|
|
291
|
+
transactions
|
|
292
|
+
},
|
|
293
|
+
(data) => {
|
|
294
|
+
console.log(`
|
|
295
|
+
Round trip complete on ${data.pair} over ticks ${data.tickLower}\u2026${data.tickUpper}.`);
|
|
296
|
+
console.log(
|
|
297
|
+
`Deposited ${formatAmount(data.deposited0, token0.decimals, token0.symbol)} + ${formatAmount(data.deposited1, token1.decimals, token1.symbol)} across two transactions, recovered ${formatAmount(data.recovered0, token0.decimals, token0.symbol)} + ${formatAmount(data.recovered1, token1.decimals, token1.symbol)}.`
|
|
298
|
+
);
|
|
299
|
+
console.log("The difference is what the range gave up to the price it deposited at, plus fees earned.");
|
|
300
|
+
for (const entry of data.transactions) console.log(` ${entry.step.padEnd(9)} ${entry.transactionId}`);
|
|
301
|
+
console.log("\nThe position is burned; nothing is left open. `shield-swap balances` shows the account.");
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
main
|
|
308
|
+
};
|
|
309
|
+
//# sourceMappingURL=liquidity-e2e-WCTSSZYS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/liquidity-e2e.ts"],"sourcesContent":["/**\n * Liquidity round trip — mint, increase, decrease, collect, burn, in one run.\n *\n * The whole life of a position, end to end, as a trader would walk it. Useful as\n * a smoke test of a funded account against a live deployment, and as the shape to\n * copy when wiring the same sequence into an application.\n *\n * Five transactions, each depending on the one before, with two kinds of waiting\n * between them:\n *\n * The positions mapping lags its own writes. A read taken straight after a\n * confirmed transaction can still show the previous state, so each step polls\n * for what it just changed before the next one builds on it.\n *\n * The record scanner lags further. Every write spends the position record and\n * issues a new one, and a write built on the spent record carries a serial\n * number the chain has already consumed — the node drops it at verification, so\n * it never reaches a block and the only symptom is a confirmation wait against\n * a transaction nothing has heard of. Checking that a record exists is not\n * enough, since the spent one satisfies that too; the record's tag has to\n * change.\n *\n * A step that fails stops the run, because everything after it builds on what it\n * would have produced. The position is then left wherever the failure found it,\n * and the message says what it holds and which script recovers it.\n *\n * SPENDS REAL FUNDS with --execute. Without it, prints the plan and stops.\n *\n * Usage:\n * shield-swap liquidity-e2e # plan against the best-funded pool\n * shield-swap liquidity-e2e --execute\n * shield-swap liquidity-e2e --pair USDCx:ETH --percent 0.5 --execute\n * shield-swap liquidity-e2e --pool <poolKey> --range-pct 10 --execute\n */\nimport { loadSession, formatAmount, pollUntil } from '../session.js'\nimport { flags, step, done, warn, output, confirmed, run, fail } from '../shared.js'\n\nconst USAGE = `shield-swap liquidity-e2e — the whole position lifecycle in one run\n\n --pair <symbol:symbol> pool to use, e.g. USDCx:ETH\n --pool <poolKey> exact pool\n --percent <n> share of the private balance of each side to\n commit, default 0.1\n --range-pct <n> range half-width around the price, default 5\n --network <testnet|mainnet> default testnet\n --execute actually submit the five transactions\n --json machine-readable output\n\nWith neither --pair nor --pool, the deepest pool this account is funded on both\nsides of is chosen. A completed run leaves nothing behind: the position it opens\nis the position it burns.`\n\n/**\n * Runs the `liquidity-e2e` subcommand.\n *\n * @param argv Arguments after the subcommand name, as the dispatcher supplies them.\n */\nexport async function main(argv: string[]): Promise<void> {\n const args = flags(\n {\n pair: { type: 'string' },\n pool: { type: 'string' },\n percent: { type: 'string' },\n 'range-pct': { type: 'string' },\n },\n USAGE,\n argv,\n )\n\n const percent = args.percent ? Number(args.percent) : 0.1\n if (!(percent > 0) || percent > 100) {\n fail(`--percent must be greater than 0 and at most 100, got ${args.percent as string}`)\n }\n const rangePercent = args['range-pct'] ? Number(args['range-pct']) : 5\n\n /** Basis points of a whole, so `--percent 0.1` survives the conversion to bigint. */\n const share = (total: bigint, pct: number) => (total * BigInt(Math.round(pct * 100))) / 10_000n\n\n await run(async () => {\n const { client, account, network } = await loadSession({ network: args.network as string | undefined })\n done(`session on ${network} for ${account.address}`)\n\n const tokens = await client.listTokens()\n const infoOf = (id: string) => tokens.find((token) => token.id === id)\n const balances = await client.getBalances()\n const held = (id: string) => balances[id]?.private ?? 0n\n\n // ---- choose the pool -----------------------------------------------------\n let poolKey = args.pool as string | undefined\n if (!poolKey) {\n step('reading the pool index')\n const listed = (await client.api.getPools({ limit: 100 })).data as Array<{\n key: string\n token0: string\n token1: string\n }>\n let candidates = listed\n if (args.pair) {\n const [left, right] = (args.pair as string).split(':')\n if (!left || !right) throw new Error(`\"${args.pair as string}\" is not symbol:symbol, e.g. USDCx:ETH`)\n const [a, b] = await Promise.all([client.tokenData(left), client.tokenData(right)])\n candidates = listed.filter(\n (pool) =>\n (pool.token0 === a.id && pool.token1 === b.id) || (pool.token0 === b.id && pool.token1 === a.id),\n )\n if (!candidates.length) {\n throw new Error(\n `no pool pairs ${a.symbol} with ${b.symbol} on ${network}. Run \\`shield-swap pools\\` to see what exists.`,\n )\n }\n }\n // A mint needs both sides, so a pool funded on only one is unusable however\n // deep it is. Among the usable ones, the deepest: a thin pool moves price\n // sharply against the deposit.\n const funded = candidates.filter((pool) => held(pool.token0) > 0n && held(pool.token1) > 0n)\n if (!funded.length) {\n throw new Error(\n 'this account is not funded on both sides of any listed pool. `shield-swap balances` shows what it ' +\n 'holds; `shield-swap setup` can draw testnet funds.',\n )\n }\n step(`ranking ${funded.length} pool(s) this account can mint into`)\n const withDepth = await Promise.all(\n funded.map(async (pool) => ({ pool, liquidity: (await client.getSlot({ poolKey: pool.key }))?.liquidity ?? 0n })),\n )\n withDepth.sort((x, y) => (y.liquidity > x.liquidity ? 1 : y.liquidity < x.liquidity ? -1 : 0))\n poolKey = withDepth[0]!.pool.key\n }\n\n const pool = await client.getPool({ poolKey })\n if (!pool) throw new Error(`no pool ${poolKey} on ${network} — check the key with \\`shield-swap pools\\`.`)\n const token0 = infoOf(pool.token0)\n const token1 = infoOf(pool.token1)\n if (!token0 || !token1) throw new Error(`the registry does not describe both tokens of pool ${poolKey}.`)\n const pair = `${token0.symbol}/${token1.symbol}`\n\n // A gated pool reverts every one of the five transactions while still charging\n // for them, so the gates are read before anything is planned.\n const controls = await client.getTradeControls({ poolKey })\n if (!controls.tradeable) {\n throw new Error(\n `pool ${poolKey} is gated on chain right now (global pause ${controls.globalPaused}, pool enabled ` +\n `${controls.poolEnabled}, pair paused ${controls.pairPaused}) — every step of this run would revert.`,\n )\n }\n\n // ---- price the deposit ---------------------------------------------------\n const budget0 = share(held(pool.token0), percent)\n const budget1 = share(held(pool.token1), percent)\n step(`pricing ${percent}% of each side over a ±${rangePercent}% range`)\n const preview = await client.previewMint({\n poolKey,\n amount0Desired: budget0,\n amount1Desired: budget1,\n rangePercent,\n })\n if (preview.liquidity === 0n) {\n throw new Error(\n `${percent}% of this account's ${pair} balances backs no liquidity over ticks ` +\n `${preview.tickLower}…${preview.tickUpper}. Raise --percent, or narrow --range-pct.`,\n )\n }\n if (!preview.inRange) {\n warn(\n `the pool trades at tick ${preview.tickCurrent}, outside ${preview.tickLower}…${preview.tickUpper}: ` +\n 'this run will still complete, but the position earns no fees while it sits out of range',\n )\n }\n\n const planLines: Array<readonly [string, string]> = [\n ['pool', `${pair} fee ${preview.fee} spacing ${preview.tickSpacing}`],\n ['range', `ticks ${preview.tickLower}…${preview.tickUpper} (${preview.inRange ? 'in range' : 'OUT OF RANGE'})`],\n [\n '1 mint',\n `${formatAmount(preview.amount0, token0.decimals, token0.symbol)} + ` +\n `${formatAmount(preview.amount1, token1.decimals, token1.symbol)}`,\n ],\n ['2 increase', 'the same amounts again'],\n ['3 decrease', 'all of the liquidity, booking it as owed'],\n ['4 collect', 'everything owed, paid to the withdrawal address'],\n ['5 burn', 'the emptied position'],\n ['owner', `${account.address} (also the withdrawal address)`],\n ]\n if (!confirmed({ execute: args.execute as boolean | undefined, network, plan: planLines })) {\n output({ network, submitted: false, poolKey, preview }, () => {})\n return\n }\n\n // Both token programs' sources: the prover cannot discover the dynamically\n // dispatched IARC20 callees on its own. Resolved once for the whole run.\n const imports = await client.resolveDexImports({\n tokenPrograms: [token0.ammTokenProgram, token1.ammTokenProgram].filter((program): program is string => !!program),\n })\n\n /** How far the run got, so a failure can say what is left behind. */\n const progress = { positionTokenId: '', liquidity: 0n, owed: false, burned: false }\n\n /** The positions mapping entry, as every wait below reports it. */\n type PositionEntry = NonNullable<Awaited<ReturnType<typeof client.getPosition>>>\n\n /**\n * Reports what a failure left on chain and how to get it back.\n *\n * Nothing is unrecoverable at any point in this sequence — the position holds\n * the deposit until something withdraws it — so the message names the script\n * that finishes the job rather than treating the funds as lost.\n */\n const recovery = (): string => {\n if (!progress.positionTokenId) return 'Nothing was opened, so nothing is left to recover.'\n const id = progress.positionTokenId\n if (progress.burned) return 'The position was already burned; nothing is left to recover.'\n if (progress.liquidity > 0n) {\n return (\n `Position ${id} is open with ${progress.liquidity} liquidity. Withdraw it with ` +\n `\\`shield-swap liquidity --position ${id} --decrease --percent 100\\`, then ` +\n `\\`shield-swap collect --position ${id} --close\\`.`\n )\n }\n if (progress.owed) {\n return `Position ${id} is drained and owed its deposit back — \\`shield-swap collect --position ${id} --close\\`.`\n }\n return `Position ${id} is empty and can be closed with \\`shield-swap collect --position ${id} --close\\`.`\n }\n\n /**\n * Polls the positions mapping until it shows what the last write changed.\n *\n * @param predicate What the entry must show, taking `null` for \"no entry\".\n * @param what Completes \"the position did not … within 30s\".\n * @throws When the read never caught up, since the next step would build on\n * state that has not materialized.\n */\n const waitForPosition = async (\n predicate: (position: PositionEntry | null) => boolean,\n what: string,\n ): Promise<PositionEntry | null> => {\n // Written as a loop rather than through `pollUntil` because the entry itself\n // is the result, and the next step is built from it.\n for (let attempt = 0; attempt < 15; attempt++) {\n const position = await client.getPosition({ positionTokenId: progress.positionTokenId })\n if (predicate(position)) return position\n await new Promise((resolve) => setTimeout(resolve, 2_000))\n }\n throw new Error(`the position did not ${what} within 30s of the transaction landing. ${recovery()}`)\n }\n\n /**\n * Waits until the scanner serves a position record other than `staleTag`.\n *\n * @param staleTag The tag of the record the last write spent.\n * @returns The new record's tag, to pass here after the next write.\n * @throws When no newer record appears, since the next write would be built on\n * the spent one and silently dropped by the node.\n */\n const waitForFreshRecord = async (staleTag?: string): Promise<string> => {\n let tag: string | undefined\n let lastError: unknown\n const indexed = await pollUntil(\n async () => {\n try {\n const current = await client.getOwnedPosition({ positionTokenId: progress.positionTokenId })\n if (current && current.record.tag !== staleTag) tag = current.record.tag\n } catch (error) {\n // The hosted scanner answers with intermittent 401s. A failed poll is\n // retried inside the window, and only the last failure is reported.\n lastError = error\n }\n return tag !== undefined\n },\n 30,\n 2_000,\n )\n if (!indexed || tag === undefined) {\n throw new Error(\n `the record scanner served no position record newer than the one the last write spent (60s), so ` +\n `the next transaction would be built on a spent record and dropped. ${recovery()}`,\n lastError instanceof Error ? { cause: lastError } : undefined,\n )\n }\n return tag\n }\n\n const transactions: Array<{ step: string; transactionId: string }> = []\n\n // ---- 1. mint -------------------------------------------------------------\n // Tick insert hints are deliberately not passed. `mint` derives both, and for\n // the upper bound it applies a correction a caller cannot: finalize inserts\n // tick_lower before validating the upper hint, so when no initialized tick sits\n // between the bounds the upper predecessor is the just-inserted lower tick\n // rather than the one visible on chain.\n step('1/5 minting the position — proving takes a minute or two')\n const minted = await client.mint({\n poolKey,\n tickLower: preview.tickLower,\n tickUpper: preview.tickUpper,\n amount0Desired: preview.amount0,\n amount1Desired: preview.amount1,\n recipient: account.address,\n withdrawal: account.address,\n imports,\n })\n transactions.push({ step: 'mint', transactionId: minted.transactionId })\n if (!minted.positionTokenId) {\n throw new Error(\n `the mint landed (tx ${minted.transactionId}) but returned no position id, so the rest of this run ` +\n 'cannot address it. Find it with `shield-swap positions` and continue with `shield-swap liquidity`.',\n )\n }\n progress.positionTokenId = minted.positionTokenId\n done(`minted ${minted.positionTokenId} (tx ${minted.transactionId})`)\n\n const opened = await waitForPosition((position) => position !== null, 'appear in the positions mapping')\n progress.liquidity = opened!.liquidity\n done(`chain carries ${progress.liquidity} liquidity (predicted ${preview.liquidity})`)\n let recordTag = await waitForFreshRecord()\n\n // ---- 2. increase ---------------------------------------------------------\n // Re-priced rather than reusing the mint's amounts: the pool has traded since,\n // and the balances have the mint's deposit taken out of them.\n const after = await client.getBalances({ tokens: [token0.id, token1.id] })\n const addition = await client.previewMint({\n poolKey,\n amount0Desired: share(after[token0.id]?.private ?? 0n, percent),\n amount1Desired: share(after[token1.id]?.private ?? 0n, percent),\n tickLower: preview.tickLower,\n tickUpper: preview.tickUpper,\n })\n if (addition.liquidity === 0n) {\n throw new Error(\n `there is not enough left to add a second time — ${percent}% of the remaining balance backs no ` +\n `liquidity over this range. ${recovery()}`,\n )\n }\n step(\n `2/5 adding ${formatAmount(addition.amount0, token0.decimals, token0.symbol)} + ` +\n `${formatAmount(addition.amount1, token1.decimals, token1.symbol)}`,\n )\n const increased = await client.increaseLiquidity({\n positionTokenId: progress.positionTokenId,\n poolKey,\n amount0Desired: addition.amount0,\n amount1Desired: addition.amount1,\n imports,\n })\n transactions.push({ step: 'increase', transactionId: increased.transactionId })\n done(`increase landed (tx ${increased.transactionId})`)\n\n const grown = await waitForPosition(\n (position) => (position?.liquidity ?? 0n) > progress.liquidity,\n 'show the added liquidity',\n )\n progress.liquidity = grown!.liquidity\n done(`liquidity now ${progress.liquidity}`)\n recordTag = await waitForFreshRecord(recordTag)\n\n // ---- 3. decrease ---------------------------------------------------------\n step(`3/5 withdrawing all ${progress.liquidity} liquidity`)\n const decreased = await client.decreaseLiquidity({\n positionTokenId: progress.positionTokenId,\n poolKey,\n liquidityToRemove: progress.liquidity,\n })\n transactions.push({ step: 'decrease', transactionId: decreased.transactionId })\n done(`decrease landed (tx ${decreased.transactionId})`)\n\n const drained = await waitForPosition((position) => position?.liquidity === 0n, 'drop to zero liquidity')\n progress.liquidity = 0n\n progress.owed = drained!.tokens_owed0 > 0n || drained!.tokens_owed1 > 0n\n done(\n `owed back ${formatAmount(drained!.tokens_owed0, token0.decimals, token0.symbol)} + ` +\n `${formatAmount(drained!.tokens_owed1, token1.decimals, token1.symbol)} — a withdrawal books, it does not pay`,\n )\n recordTag = await waitForFreshRecord(recordTag)\n\n // ---- 4. collect ----------------------------------------------------------\n // Requested from what the decrease actually booked. With the liquidity at zero\n // there is nothing accruing on top, so the booked figure is the whole of it.\n step('4/5 collecting what the position is owed')\n const collected = await client.collect({\n positionTokenId: progress.positionTokenId,\n poolKey,\n amount0Requested: drained!.tokens_owed0,\n amount1Requested: drained!.tokens_owed1,\n imports,\n })\n transactions.push({ step: 'collect', transactionId: collected.transactionId })\n done(`collect landed (tx ${collected.transactionId}) — paid to ${account.address}`)\n\n await waitForPosition(\n (position) => position?.tokens_owed0 === 0n && position?.tokens_owed1 === 0n,\n 'clear its owed balances',\n )\n progress.owed = false\n recordTag = await waitForFreshRecord(recordTag)\n\n // ---- 5. burn -------------------------------------------------------------\n step('5/5 burning the emptied position')\n const burned = await client.burn({ positionTokenId: progress.positionTokenId, poolKey })\n transactions.push({ step: 'burn', transactionId: burned.transactionId })\n done(`burn landed (tx ${burned.transactionId})`)\n\n // The chain is the authority on the burn: the entry is gone from `positions`.\n // The scanner's own view is deliberately not waited on — it marks records spent\n // on its own schedule and can serve a burned position for minutes.\n await waitForPosition((position) => position === null, 'disappear from the positions mapping')\n progress.burned = true\n\n output(\n {\n network,\n submitted: true,\n poolKey,\n pair,\n positionTokenId: minted.positionTokenId,\n tickLower: preview.tickLower,\n tickUpper: preview.tickUpper,\n deposited0: preview.amount0 + addition.amount0,\n deposited1: preview.amount1 + addition.amount1,\n recovered0: drained!.tokens_owed0,\n recovered1: drained!.tokens_owed1,\n transactions,\n },\n (data) => {\n console.log(`\\nRound trip complete on ${data.pair} over ticks ${data.tickLower}…${data.tickUpper}.`)\n console.log(\n `Deposited ${formatAmount(data.deposited0, token0.decimals, token0.symbol)} + ` +\n `${formatAmount(data.deposited1, token1.decimals, token1.symbol)} across two transactions, ` +\n `recovered ${formatAmount(data.recovered0, token0.decimals, token0.symbol)} + ` +\n `${formatAmount(data.recovered1, token1.decimals, token1.symbol)}.`,\n )\n console.log('The difference is what the range gave up to the price it deposited at, plus fees earned.')\n for (const entry of data.transactions) console.log(` ${entry.step.padEnd(9)} ${entry.transactionId}`)\n console.log('\\nThe position is burned; nothing is left open. `shield-swap balances` shows the account.')\n },\n )\n })\n}\n"],"mappings":"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","names":["pool"]}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirmed,
|
|
3
|
+
done,
|
|
4
|
+
fail,
|
|
5
|
+
flags,
|
|
6
|
+
output,
|
|
7
|
+
run,
|
|
8
|
+
step,
|
|
9
|
+
warn
|
|
10
|
+
} from "./chunk-IBVZHLUT.js";
|
|
11
|
+
import "./chunk-IHYFMX5A.js";
|
|
12
|
+
import {
|
|
13
|
+
formatAmount,
|
|
14
|
+
loadSession,
|
|
15
|
+
namedAmounts,
|
|
16
|
+
pollUntil
|
|
17
|
+
} from "./chunk-2OT6LZPW.js";
|
|
18
|
+
|
|
19
|
+
// src/commands/mint.ts
|
|
20
|
+
var USAGE = `shield-swap mint \u2014 open a liquidity position
|
|
21
|
+
|
|
22
|
+
--pair <symbol:symbol> pool to enter, e.g. USDCx:ETH (or --pool)
|
|
23
|
+
--pool <poolKey> exact pool, skipping pair lookup
|
|
24
|
+
--amount <symbol>:<decimal> how much of one named token, e.g. USDCx:0.5.
|
|
25
|
+
Repeatable, once per side. Prefer this \u2014 it does
|
|
26
|
+
not depend on knowing the pool's token order
|
|
27
|
+
--amount0 <decimal> token0 to commit, in human units
|
|
28
|
+
--amount1 <decimal> token1 to commit, in human units
|
|
29
|
+
--percent <n> commit n% of the private balance of both sides
|
|
30
|
+
--range-pct <n> range half-width around the price, default 5
|
|
31
|
+
--network <testnet|mainnet> default testnet
|
|
32
|
+
--execute actually submit
|
|
33
|
+
--json machine-readable output
|
|
34
|
+
|
|
35
|
+
Either --percent or at least one amount. A side left unnamed commits its whole
|
|
36
|
+
private balance as a ceiling \u2014 the plan shows what the range actually consumes,
|
|
37
|
+
which is never more than that.
|
|
38
|
+
|
|
39
|
+
--amount0/--amount1 follow the POOL's token order, which is fixed on chain and
|
|
40
|
+
need NOT match the order in --pair: naming --pair USDCx:ETH does not make USDCx
|
|
41
|
+
side 0. --amount names the token instead and cannot be transposed. The plan names
|
|
42
|
+
both symbols either way.`;
|
|
43
|
+
async function main(argv) {
|
|
44
|
+
const args = flags(
|
|
45
|
+
{
|
|
46
|
+
pair: { type: "string" },
|
|
47
|
+
pool: { type: "string" },
|
|
48
|
+
amount: { type: "string", multiple: true },
|
|
49
|
+
amount0: { type: "string" },
|
|
50
|
+
amount1: { type: "string" },
|
|
51
|
+
percent: { type: "string" },
|
|
52
|
+
"range-pct": { type: "string" }
|
|
53
|
+
},
|
|
54
|
+
USAGE,
|
|
55
|
+
argv
|
|
56
|
+
);
|
|
57
|
+
if (!args.pair && !args.pool) fail(`--pair or --pool is required.
|
|
58
|
+
|
|
59
|
+
${USAGE}`);
|
|
60
|
+
const bySymbol = args.amount ?? [];
|
|
61
|
+
const anyAmount = bySymbol.length > 0 || !!args.amount0 || !!args.amount1;
|
|
62
|
+
if (!args.percent && !anyAmount) {
|
|
63
|
+
fail(`--percent, --amount, --amount0, or --amount1 is required.
|
|
64
|
+
|
|
65
|
+
${USAGE}`);
|
|
66
|
+
}
|
|
67
|
+
if (args.percent && anyAmount) {
|
|
68
|
+
fail(`--percent and the amount flags are alternatives, not both.
|
|
69
|
+
|
|
70
|
+
${USAGE}`);
|
|
71
|
+
}
|
|
72
|
+
const percent = args.percent ? Number(args.percent) : void 0;
|
|
73
|
+
if (percent !== void 0 && (!(percent > 0) || percent > 100)) {
|
|
74
|
+
fail(`--percent must be greater than 0 and at most 100, got ${args.percent}`);
|
|
75
|
+
}
|
|
76
|
+
const share = (total, pct) => total * BigInt(Math.round(pct * 100)) / 10000n;
|
|
77
|
+
await run(async () => {
|
|
78
|
+
const { client, account, network } = await loadSession({ network: args.network });
|
|
79
|
+
done(`session on ${network}`);
|
|
80
|
+
let poolKey = args.pool;
|
|
81
|
+
if (!poolKey) {
|
|
82
|
+
const [left, right] = args.pair.split(":");
|
|
83
|
+
if (!left || !right) throw new Error(`"${args.pair}" is not symbol:symbol, e.g. USDCx:ETH`);
|
|
84
|
+
const [a, b] = await Promise.all([client.tokenData(left), client.tokenData(right)]);
|
|
85
|
+
step(`looking for a ${a.symbol}/${b.symbol} pool`);
|
|
86
|
+
const listed = (await client.api.getPools({ limit: 100 })).data;
|
|
87
|
+
const matches = listed.filter(
|
|
88
|
+
(pool2) => pool2.token0 === a.id && pool2.token1 === b.id || pool2.token0 === b.id && pool2.token1 === a.id
|
|
89
|
+
);
|
|
90
|
+
if (!matches.length) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`no pool pairs ${a.symbol} with ${b.symbol} on ${network}. Run \`shield-swap pools\` to see what exists.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const withDepth = await Promise.all(
|
|
96
|
+
matches.map(async (pool2) => ({ pool: pool2, liquidity: (await client.getSlot({ poolKey: pool2.key }))?.liquidity ?? 0n }))
|
|
97
|
+
);
|
|
98
|
+
withDepth.sort((x, y) => y.liquidity > x.liquidity ? 1 : y.liquidity < x.liquidity ? -1 : 0);
|
|
99
|
+
poolKey = withDepth[0].pool.key;
|
|
100
|
+
if (matches.length > 1) done(`${matches.length} fee tiers pair them \u2014 taking the deepest`);
|
|
101
|
+
}
|
|
102
|
+
const pool = await client.getPool({ poolKey });
|
|
103
|
+
if (!pool) throw new Error(`no pool ${poolKey} on ${network} \u2014 check the key with \`shield-swap pools\`.`);
|
|
104
|
+
const controls = await client.getTradeControls({ poolKey });
|
|
105
|
+
if (!controls.tradeable) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`pool ${poolKey} is gated on chain right now (global pause ${controls.globalPaused}, pool enabled ${controls.poolEnabled}, pair paused ${controls.pairPaused}) \u2014 a mint would revert.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const tokens = await client.listTokens();
|
|
111
|
+
const infoOf = (id) => tokens.find((token) => token.id === id);
|
|
112
|
+
const token0 = infoOf(pool.token0);
|
|
113
|
+
const token1 = infoOf(pool.token1);
|
|
114
|
+
if (!token0 || !token1) throw new Error(`the registry does not describe both tokens of pool ${poolKey}.`);
|
|
115
|
+
step("reading private balances for both sides");
|
|
116
|
+
const balances = await client.getBalances({ tokens: [token0.id, token1.id] });
|
|
117
|
+
const held0 = balances[token0.id]?.private ?? 0n;
|
|
118
|
+
const held1 = balances[token1.id]?.private ?? 0n;
|
|
119
|
+
if (held0 === 0n || held1 === 0n) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`an in-range position needs both sides, and this account holds ${formatAmount(held0, token0.decimals, token0.symbol)} and ${formatAmount(held1, token1.decimals, token1.symbol)} privately. Fund the empty side first.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const named = namedAmounts({
|
|
125
|
+
entries: bySymbol,
|
|
126
|
+
indexed: [args.amount0, args.amount1],
|
|
127
|
+
tokens: [token0, token1]
|
|
128
|
+
});
|
|
129
|
+
const budget0 = percent ? share(held0, percent) : named.amount0 ?? held0;
|
|
130
|
+
const budget1 = percent ? share(held1, percent) : named.amount1 ?? held1;
|
|
131
|
+
if (budget0 > held0 || budget1 > held1) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`asked to commit ${formatAmount(budget0, token0.decimals, token0.symbol)} / ${formatAmount(budget1, token1.decimals, token1.symbol)} but the account holds ${formatAmount(held0, token0.decimals, token0.symbol)} / ${formatAmount(held1, token1.decimals, token1.symbol)} privately.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const rangePercent = args["range-pct"] ? Number(args["range-pct"]) : 5;
|
|
137
|
+
step(`pricing a \xB1${rangePercent}% range against the pool's live price`);
|
|
138
|
+
const preview = await client.previewMint({
|
|
139
|
+
poolKey,
|
|
140
|
+
amount0Desired: budget0,
|
|
141
|
+
amount1Desired: budget1,
|
|
142
|
+
rangePercent
|
|
143
|
+
});
|
|
144
|
+
if (preview.liquidity === 0n) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`that budget backs no liquidity over ticks ${preview.tickLower}\u2026${preview.tickUpper} \u2014 commit more, or narrow the range with --range-pct. A mint would cost a fee and open nothing.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (!preview.inRange) {
|
|
150
|
+
warn(
|
|
151
|
+
`the pool trades at tick ${preview.tickCurrent}, outside ${preview.tickLower}\u2026${preview.tickUpper}: this position earns nothing until the price moves into its range, and is funded from one side only`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (preview.feeTierSpacing !== null && preview.feeTierSpacing !== preview.tickSpacing) {
|
|
155
|
+
warn(
|
|
156
|
+
`the pool's tick spacing (${preview.tickSpacing}) differs from what fee tier ${preview.fee} binds (${preview.feeTierSpacing}) \u2014 the bounds follow the pool, which is what the contract aligns to`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const perSide = [
|
|
160
|
+
{ info: token0, needed: preview.amount0, held: held0 },
|
|
161
|
+
{ info: token1, needed: preview.amount1, held: held1 }
|
|
162
|
+
];
|
|
163
|
+
for (const side of perSide) {
|
|
164
|
+
if (side.needed > side.held) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`the range needs ${formatAmount(side.needed, side.info.decimals, side.info.symbol)} but only ${formatAmount(side.held, side.info.decimals, side.info.symbol)} is held privately.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const planLines = [
|
|
171
|
+
["pool", `${token0.symbol}/${token1.symbol} fee ${preview.fee} spacing ${preview.tickSpacing}`],
|
|
172
|
+
[
|
|
173
|
+
"range",
|
|
174
|
+
`ticks ${preview.tickLower}\u2026${preview.tickUpper} (\xB1${rangePercent}%, price at tick ${preview.tickCurrent})`
|
|
175
|
+
],
|
|
176
|
+
["status", preview.inRange ? "in range \u2014 earns fees immediately" : "OUT OF RANGE \u2014 earns nothing yet"],
|
|
177
|
+
["deposit", formatAmount(preview.amount0, token0.decimals, token0.symbol)],
|
|
178
|
+
// Empty label: the second side of the same deposit, not a separate step.
|
|
179
|
+
["", formatAmount(preview.amount1, token1.decimals, token1.symbol)],
|
|
180
|
+
[
|
|
181
|
+
"unused",
|
|
182
|
+
`${formatAmount(budget0 - preview.amount0, token0.decimals, token0.symbol)} / ${formatAmount(budget1 - preview.amount1, token1.decimals, token1.symbol)} of the budget stays in the account`
|
|
183
|
+
],
|
|
184
|
+
["owner", `${account.address} (also the withdrawal address collect pays)`]
|
|
185
|
+
];
|
|
186
|
+
if (!confirmed({ execute: args.execute, network, plan: planLines })) {
|
|
187
|
+
output({ network, submitted: false, poolKey, preview }, () => {
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const imports = await client.resolveDexImports({
|
|
192
|
+
tokenPrograms: [token0.ammTokenProgram, token1.ammTokenProgram].filter((program) => !!program)
|
|
193
|
+
});
|
|
194
|
+
step("proving and submitting the mint \u2014 this takes a minute or two");
|
|
195
|
+
const minted = await client.mint({
|
|
196
|
+
poolKey,
|
|
197
|
+
tickLower: preview.tickLower,
|
|
198
|
+
tickUpper: preview.tickUpper,
|
|
199
|
+
amount0Desired: preview.amount0,
|
|
200
|
+
amount1Desired: preview.amount1,
|
|
201
|
+
recipient: account.address,
|
|
202
|
+
withdrawal: account.address,
|
|
203
|
+
imports
|
|
204
|
+
});
|
|
205
|
+
done(`mint landed: tx ${minted.transactionId}`);
|
|
206
|
+
if (!minted.positionTokenId) {
|
|
207
|
+
warn("the position id was not returned \u2014 find it with `shield-swap positions`");
|
|
208
|
+
output({ network, submitted: true, poolKey, transactionId: minted.transactionId, preview }, () => {
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
step(`position ${minted.positionTokenId} \u2014 waiting for the positions mapping to catch up`);
|
|
213
|
+
let onchain;
|
|
214
|
+
const appeared = await pollUntil(
|
|
215
|
+
async () => {
|
|
216
|
+
const position = await client.getPosition({ positionTokenId: minted.positionTokenId });
|
|
217
|
+
if (position) onchain = position;
|
|
218
|
+
return position !== null;
|
|
219
|
+
},
|
|
220
|
+
20,
|
|
221
|
+
3e3
|
|
222
|
+
);
|
|
223
|
+
if (appeared) done(`chain carries the position with liquidity ${onchain.liquidity}`);
|
|
224
|
+
else warn("the position has not appeared in the positions mapping yet \u2014 check `shield-swap positions` shortly");
|
|
225
|
+
output(
|
|
226
|
+
{
|
|
227
|
+
network,
|
|
228
|
+
submitted: true,
|
|
229
|
+
poolKey,
|
|
230
|
+
positionTokenId: minted.positionTokenId,
|
|
231
|
+
transactionId: minted.transactionId,
|
|
232
|
+
tickLower: preview.tickLower,
|
|
233
|
+
tickUpper: preview.tickUpper,
|
|
234
|
+
deposited0: preview.amount0,
|
|
235
|
+
deposited1: preview.amount1,
|
|
236
|
+
predictedLiquidity: preview.liquidity,
|
|
237
|
+
liquidity: onchain?.liquidity ?? null
|
|
238
|
+
},
|
|
239
|
+
(data) => {
|
|
240
|
+
console.log(`
|
|
241
|
+
Position ${data.positionTokenId} open on ${token0.symbol}/${token1.symbol}.`);
|
|
242
|
+
console.log(
|
|
243
|
+
`Deposited ${formatAmount(data.deposited0, token0.decimals, token0.symbol)} and ${formatAmount(data.deposited1, token1.decimals, token1.symbol)} over ticks ${data.tickLower}\u2026${data.tickUpper}.`
|
|
244
|
+
);
|
|
245
|
+
console.log("Track it with `shield-swap positions`; collect earnings with `shield-swap collect`.");
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
export {
|
|
251
|
+
main
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=mint-6OKWODQG.js.map
|