@intent-engine/solver 0.1.35 → 0.1.37

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.
@@ -3,7 +3,8 @@ import { type Config, type Intent } from "@intent-engine/core";
3
3
  import { type Backoff } from "./backoff.js";
4
4
  import { type Tip } from "./build.js";
5
5
  import type { Pricer } from "./market.js";
6
- import { type Strategy } from "./plan.js";
6
+ import { type Plan, type Strategy } from "./plan.js";
7
+ import { type Hold } from "./holds.js";
7
8
  export { BACKOFF_MAX_INTERVALS, type Backoff, backingOff, backoff } from "./backoff.js";
8
9
  export { createdRent, DEFAULT_MAX_RENT, DEFAULT_MAX_SOL_COST, LAMPORTS_PER_SIGNATURE, MICRO_LAMPORTS_PER_LAMPORT, mintExtensions, rentExempt, type SolCost, signaturesOf, solCost, TOKEN_2022_ACCOUNT_SPACE, TOKEN_2022_PROGRAM, TOKEN_ACCOUNT_SPACE, tokenAccountSpace, tooCostly, tooMuchRent, } from "./costs.js";
9
10
  export type Chain = {
@@ -42,22 +43,30 @@ export type SolverOptions = {
42
43
  maxSolCost?: bigint;
43
44
  maxRent?: bigint;
44
45
  backoff?: Backoff;
46
+ concurrency?: number;
47
+ planned?(intent: Address): void;
48
+ held?(account: Address): bigint;
45
49
  unitPrice(instructions: readonly Instruction[]): Promise<bigint>;
46
50
  simulate(wire: Base64EncodedWireTransaction, watch: readonly Address[]): Promise<Simulated>;
47
51
  send(wire: Base64EncodedWireTransaction): Promise<Sent>;
52
+ sendBundle?: (wires: readonly Base64EncodedWireTransaction[]) => Promise<string>;
53
+ bundleSize?: number;
48
54
  };
49
55
  export type Report = {
50
56
  filled: {
51
57
  intent: Address;
52
58
  profit: bigint;
59
+ cost: bigint;
53
60
  signature: string;
54
61
  rent?: bigint;
55
62
  }[];
56
63
  sent: {
57
64
  intent: Address;
58
65
  profit: bigint;
66
+ cost: bigint;
59
67
  signature: string;
60
68
  rent?: bigint;
69
+ holds?: Hold;
61
70
  }[];
62
71
  skipped: {
63
72
  intent: Address;
@@ -73,22 +82,10 @@ export declare const SIMULATION_REFUSED = "the fill failed in simulation, so it
73
82
  export declare const BALANCES_REFUSED = "the fill would leave the solver with less of a token than planned, so it was not sent";
74
83
  export declare const ROUTE_ACCOUNT_BUDGETS: readonly [40, 30, 20];
75
84
  export declare const tooLarge: (bytes: number | null, accounts: number) => string;
85
+ export declare const DEFAULT_CONCURRENCY = 4;
86
+ export declare const MAX_CONCURRENCY = 32;
87
+ export declare const badConcurrency: (value: number) => string;
88
+ export declare const ALREADY_FILLING = "the order already has a fill in this tick";
89
+ export declare const INVENTORY_SPENT = "the solver no longer holds enough of the output token for this fill";
90
+ export declare function firstToSend(a: Plan, b: Plan): number;
76
91
  export declare function tick(o: SolverOptions): Promise<Report>;
77
- export type Flight = {
78
- until: bigint;
79
- signature: string;
80
- };
81
- export declare const FAILED_ON_CHAIN = "the fill was sent but failed on chain";
82
- export declare function landings(flights: Map<Address, Flight>, now: bigint, listed: readonly {
83
- address: Address;
84
- intent: Intent;
85
- }[], failedOnChain: (signature: string) => Promise<boolean>, held: Backoff): Promise<{
86
- intent: Address;
87
- error: string;
88
- }[]>;
89
- export declare const CONFIG_TTL_MS = 30000;
90
- export declare const BLOCKHASH_TTL_MS = 20000;
91
- export type Remembering = Chain & {
92
- forget(): void;
93
- };
94
- export declare function remembering(chain: Chain, clock?: () => number): Remembering;
@@ -1,21 +1,218 @@
1
1
  import {} from "@solana/kit";
2
- import { findAssociatedTokenAddress, IntentStatus } from "@intent-engine/core";
2
+ import { findAssociatedTokenAddress } from "@intent-engine/core";
3
3
  import { backingOff } from "./backoff.js";
4
4
  import { createdRent, DEFAULT_MAX_RENT, DEFAULT_MAX_SOL_COST, signaturesOf, solCost, tooCostly, tooMuchRent } from "./costs.js";
5
5
  import { accountsOf, bytesFit, compile, fillInstructions, headroom, locksFit, lookupTablesOf, MAX_COMPUTE_UNITS, MAX_TX_ACCOUNTS, PACKET_DATA_SIZE, payoutAccounts, wireSize, } from "./build.js";
6
+ import { bundleSizeFits, badBundleSize, sendBundles, withoutTip } from "./bundle.js";
7
+ import { MAX_BUNDLE } from "./jito.js";
6
8
  import { RateLimited } from "./jupiter.js";
7
9
  import { decide, standing } from "./plan.js";
10
+ import { netShort, solValueFits } from "./net.js";
11
+ import { freshly, fromCache } from "./quote-cache.js";
12
+ import { spend, stockLeft } from "./inventory.js";
13
+ import { unheld } from "./holds.js";
8
14
  export { BACKOFF_MAX_INTERVALS, backingOff, backoff } from "./backoff.js";
9
15
  export { createdRent, DEFAULT_MAX_RENT, DEFAULT_MAX_SOL_COST, LAMPORTS_PER_SIGNATURE, MICRO_LAMPORTS_PER_LAMPORT, mintExtensions, rentExempt, signaturesOf, solCost, TOKEN_2022_ACCOUNT_SPACE, TOKEN_2022_PROGRAM, TOKEN_ACCOUNT_SPACE, tokenAccountSpace, tooCostly, tooMuchRent, } from "./costs.js";
10
16
  export const SIMULATION_REFUSED = "the fill failed in simulation, so it was not sent";
11
17
  export const BALANCES_REFUSED = "the fill would leave the solver with less of a token than planned, so it was not sent";
12
18
  export const ROUTE_ACCOUNT_BUDGETS = [40, 30, 20];
13
19
  export const tooLarge = (bytes, accounts) => `even the smallest route makes the fill ${bytes === null ? "lock" : `${bytes} bytes with`} ${accounts} accounts; a transaction holds ${PACKET_DATA_SIZE} bytes and ${MAX_TX_ACCOUNTS} accounts`;
20
+ export const DEFAULT_CONCURRENCY = 4;
21
+ export const MAX_CONCURRENCY = 32;
22
+ export const badConcurrency = (value) => `concurrency must be an integer from 1 to ${MAX_CONCURRENCY}, not ${value}`;
23
+ export const ALREADY_FILLING = "the order already has a fill in this tick";
24
+ export const INVENTORY_SPENT = "the solver no longer holds enough of the output token for this fill";
25
+ export function firstToSend(a, b) {
26
+ if (a.profit !== b.profit) {
27
+ return a.profit > b.profit ? -1 : 1;
28
+ }
29
+ if (a.intent.expiresAt !== b.intent.expiresAt) {
30
+ return a.intent.expiresAt < b.intent.expiresAt ? -1 : 1;
31
+ }
32
+ return a.address < b.address ? -1 : a.address > b.address ? 1 : 0;
33
+ }
34
+ const skipped = (intent, reason, rent) => ({
35
+ kind: "skipped",
36
+ entry: { intent, reason, ...(rent === undefined ? {} : { rent }) },
37
+ });
38
+ const failed = (intent, error) => ({ kind: "failed", entry: { intent, error } });
39
+ const messageOf = (error) => (error instanceof Error ? error.message : String(error));
40
+ async function planOrder(o, config, now, address, intent) {
41
+ const ready = standing(intent, config, now, o.strategy);
42
+ if (typeof ready === "string") {
43
+ return skipped(address, ready);
44
+ }
45
+ const waiting = o.backoff?.waiting(address, intent) ?? null;
46
+ if (waiting !== null) {
47
+ return skipped(address, backingOff(waiting.failures, waiting.seconds));
48
+ }
49
+ const [inputTokenProgram, outputTokenProgram] = await Promise.all([
50
+ o.chain.tokenProgram(intent.inputMint),
51
+ o.chain.tokenProgram(intent.outputMint),
52
+ ]);
53
+ const payable = async (draft) => {
54
+ const payouts = await payoutAccounts(draft, config.feeRecipient, inputTokenProgram);
55
+ const found = await Promise.all(payouts.map((payout) => o.chain.exists(payout.address)));
56
+ const missing = payouts.filter((_, i) => !found[i]).map((payout) => payout.name);
57
+ return missing.length === 0 ? null : `no ${missing.join(" or ")} account to pay into`;
58
+ };
59
+ const solverInput = await findAssociatedTokenAddress({
60
+ owner: o.solver.address,
61
+ mint: intent.inputMint,
62
+ tokenProgram: inputTokenProgram,
63
+ });
64
+ const solverOutput = await findAssociatedTokenAddress({
65
+ owner: o.solver.address,
66
+ mint: intent.outputMint,
67
+ tokenProgram: outputTokenProgram,
68
+ });
69
+ const [inputExists, outputExists, latest] = await Promise.all([
70
+ o.chain.exists(solverInput),
71
+ o.chain.exists(solverOutput),
72
+ o.chain.blockhash(),
73
+ ]);
74
+ const draft = {
75
+ solver: o.solver,
76
+ feeRecipient: config.feeRecipient,
77
+ inputTokenProgram,
78
+ outputTokenProgram,
79
+ computeUnits: MAX_COMPUTE_UNITS,
80
+ unitPrice: 0n,
81
+ createSolverInput: !inputExists,
82
+ createSolverOutput: !outputExists,
83
+ tip: o.tip,
84
+ };
85
+ o.planned?.(address);
86
+ let size = { bytes: null, accounts: 0 };
87
+ for (const routeAccounts of ROUTE_ACCOUNT_BUDGETS) {
88
+ const budget = { ...o.strategy, routeAccounts };
89
+ const first = await decide(address, intent, config, now, o.pricer, o.solver.address, budget, payable);
90
+ const decision = first.kind === "fill" && fromCache(first.plan.route)
91
+ ? await decide(address, intent, config, now, freshly(o.pricer), o.solver.address, budget, payable)
92
+ : first;
93
+ if (decision.kind === "skip") {
94
+ return skipped(address, decision.reason);
95
+ }
96
+ const plan = decision.plan;
97
+ const context = { ...draft, unitPrice: await o.unitPrice(await fillInstructions(plan, draft)) };
98
+ const instructions = await fillInstructions(plan, context);
99
+ const accounts = accountsOf(instructions, o.solver.address);
100
+ if (!locksFit(accounts)) {
101
+ size = { bytes: null, accounts };
102
+ continue;
103
+ }
104
+ const tables = await o.chain.lookupTables(lookupTablesOf(plan, o.lookupTables));
105
+ const probe = await compile(instructions, o.solver, latest, tables);
106
+ const bytes = wireSize(probe);
107
+ size = { bytes, accounts };
108
+ if (bytesFit(bytes)) {
109
+ return { kind: "ready", ready: { plan, context, tables, probe, latest, solverInput, solverOutput } };
110
+ }
111
+ }
112
+ return skipped(address, tooLarge(size.bytes, size.accounts));
113
+ }
114
+ async function untipped(o, ready) {
115
+ if (ready.context.tip === null) {
116
+ return { kind: "ready", ready };
117
+ }
118
+ const bare = await withoutTip(ready.plan, ready.context, o.solver, ready.latest, ready.tables, o.unitPrice);
119
+ return "probe" in bare ? { kind: "ready", ready: { ...ready, ...bare } } : skipped(ready.plan.address, tooLarge(bare.bytes, bare.accounts));
120
+ }
121
+ async function checkOrder(o, caps, mintData, ready, stock) {
122
+ const { plan, context, tables, probe, latest, solverInput, solverOutput } = ready;
123
+ const address = plan.address;
124
+ const before = await Promise.all([o.chain.tokenAmount(solverInput), o.chain.tokenAmount(solverOutput)]);
125
+ const free = unheld(before[1] ?? 0n, o.held?.(solverOutput) ?? 0n);
126
+ const left = stockLeft(stock, solverOutput, free);
127
+ if (plan.route.inventory === true && left < plan.required) {
128
+ return skipped(address, `${INVENTORY_SPENT}: ${left} left, ${plan.required} needed`);
129
+ }
130
+ const simulated = await o.simulate(probe, [solverInput, solverOutput]);
131
+ if (simulated.err !== null) {
132
+ return failed(address, `${SIMULATION_REFUSED}: ${simulated.err}`);
133
+ }
134
+ const [inputAfter, outputAfter] = simulated.balances;
135
+ const least = plan.route.inventory === true
136
+ ? [(before[0] ?? 0n) + plan.fees.net, (before[1] ?? 0n) - plan.required]
137
+ : [before[0] ?? 0n, (before[1] ?? 0n) + plan.profit];
138
+ if (inputAfter === null || inputAfter === undefined || outputAfter === null || outputAfter === undefined || inputAfter < (least[0] ?? 0n) || outputAfter < (least[1] ?? 0n)) {
139
+ return failed(address, `${BALANCES_REFUSED}: input ${before[0]} to ${inputAfter}, output ${before[1]} to ${outputAfter}, expected at least ${least[0]} and ${least[1]}`);
140
+ }
141
+ const final = simulated.unitsConsumed === null ? context : { ...context, computeUnits: headroom(simulated.unitsConsumed) };
142
+ const instructions = await fillInstructions(plan, final);
143
+ const wire = simulated.unitsConsumed === null ? probe : await compile(instructions, o.solver, latest, tables);
144
+ const cost = solCost({
145
+ signatures: signaturesOf(wire),
146
+ unitPrice: final.unitPrice,
147
+ computeUnits: final.computeUnits,
148
+ tip: final.tip?.lamports ?? 0n,
149
+ });
150
+ const rent = await createdRent(instructions, (account) => o.chain.exists(account), mintData);
151
+ const kept = rent > 0n ? rent : undefined;
152
+ if (cost > caps.cost) {
153
+ return skipped(address, tooCostly(cost, caps.cost), kept);
154
+ }
155
+ if (rent > caps.rent) {
156
+ return skipped(address, tooMuchRent(rent, caps.rent), kept);
157
+ }
158
+ const short = netShort(plan, o.strategy.minProfit.get(plan.intent.outputMint) ?? 0n, cost, o.strategy.solValue);
159
+ if (short !== null) {
160
+ return skipped(address, short, kept);
161
+ }
162
+ if (wire !== probe) {
163
+ const checked = await o.simulate(wire, [solverInput, solverOutput]);
164
+ if (checked.err !== null) {
165
+ return failed(address, `${SIMULATION_REFUSED}: ${checked.err}`);
166
+ }
167
+ }
168
+ return {
169
+ kind: "checked",
170
+ wire,
171
+ entry: { intent: address, profit: plan.profit, cost, ...(kept === undefined ? {} : { rent: kept }) },
172
+ ...(plan.route.inventory === true ? { holds: { account: solverOutput, amount: plan.required } } : {}),
173
+ spend: () => {
174
+ if (plan.route.inventory === true) {
175
+ spend(stock, solverOutput, free, plan.required);
176
+ }
177
+ },
178
+ };
179
+ }
180
+ async function fillOrder(o, caps, mintData, ready, stock) {
181
+ const checked = await checkOrder(o, caps, mintData, ready, stock);
182
+ if (checked.kind !== "checked") {
183
+ return checked;
184
+ }
185
+ const sent = await o.send(checked.wire);
186
+ checked.spend();
187
+ const holds = !sent.confirmed && checked.holds !== undefined ? { holds: checked.holds } : {};
188
+ return { kind: sent.confirmed ? "filled" : "sent", entry: { ...checked.entry, signature: sent.signature, ...holds } };
189
+ }
190
+ async function queueOrder(o, caps, mintData, ready, stock) {
191
+ const bare = await untipped(o, ready);
192
+ if (bare.kind !== "ready") {
193
+ return bare;
194
+ }
195
+ const checked = await checkOrder(o, caps, mintData, bare.ready, stock);
196
+ if (checked.kind === "checked") {
197
+ checked.spend();
198
+ }
199
+ return checked;
200
+ }
14
201
  export async function tick(o) {
15
- const report = { filled: [], sent: [], skipped: [], failed: [] };
202
+ const concurrency = o.concurrency ?? DEFAULT_CONCURRENCY;
203
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > MAX_CONCURRENCY) {
204
+ throw new RangeError(badConcurrency(concurrency));
205
+ }
206
+ const bundleSize = o.bundleSize ?? MAX_BUNDLE;
207
+ if (!bundleSizeFits(bundleSize)) {
208
+ throw new RangeError(badBundleSize(bundleSize));
209
+ }
210
+ const invalid = solValueFits(o.strategy.solValue);
211
+ if (invalid !== null) {
212
+ throw new RangeError(invalid);
213
+ }
16
214
  const [config, now, intents] = await Promise.all([o.chain.config(), o.chain.now(), o.chain.intents()]);
17
- const cap = o.maxSolCost ?? DEFAULT_MAX_SOL_COST;
18
- const rentCap = o.maxRent ?? DEFAULT_MAX_RENT;
215
+ const caps = { cost: o.maxSolCost ?? DEFAULT_MAX_SOL_COST, rent: o.maxRent ?? DEFAULT_MAX_RENT };
19
216
  const mints = new Map();
20
217
  const mintData = (mint) => {
21
218
  const known = mints.get(mint);
@@ -26,141 +223,124 @@ export async function tick(o) {
26
223
  mints.set(mint, loaded);
27
224
  return loaded;
28
225
  };
226
+ const outcomes = intents.map(() => undefined);
227
+ const planned = [];
29
228
  let limited = null;
30
- for (const { address, intent } of intents) {
31
- if (limited !== null) {
32
- report.skipped.push({ intent: address, reason: limited });
33
- continue;
229
+ let next = 0;
230
+ const planner = async () => {
231
+ for (let index = next; limited === null && index < intents.length; index = next) {
232
+ next = index + 1;
233
+ const entry = intents[index];
234
+ if (entry === undefined) {
235
+ continue;
236
+ }
237
+ const { address, intent } = entry;
238
+ try {
239
+ const result = await planOrder(o, config, now, address, intent);
240
+ if (result.kind === "ready") {
241
+ planned.push({ index, ready: result.ready });
242
+ }
243
+ else {
244
+ outcomes[index] = result;
245
+ }
246
+ }
247
+ catch (error) {
248
+ if (error instanceof RateLimited) {
249
+ limited ??= error.message;
250
+ outcomes[index] = skipped(address, error.message);
251
+ }
252
+ else {
253
+ outcomes[index] = failed(address, messageOf(error));
254
+ }
255
+ }
256
+ }
257
+ };
258
+ await Promise.all(Array.from({ length: Math.min(concurrency, intents.length) }, planner));
259
+ if (limited !== null) {
260
+ for (let index = next; index < intents.length; index += 1) {
261
+ const address = intents[index]?.address;
262
+ if (address !== undefined) {
263
+ outcomes[index] = skipped(address, limited);
264
+ }
34
265
  }
35
- const ready = standing(intent, config, now, o.strategy);
36
- if (typeof ready === "string") {
37
- report.skipped.push({ intent: address, reason: ready });
266
+ }
267
+ planned.sort((a, b) => firstToSend(a.ready.plan, b.ready.plan) || a.index - b.index);
268
+ const filling = new Set();
269
+ const stock = new Map();
270
+ const queue = [];
271
+ let halted = null;
272
+ for (const { index, ready } of planned) {
273
+ const address = ready.plan.address;
274
+ if (halted !== null) {
275
+ outcomes[index] = skipped(address, halted);
38
276
  continue;
39
277
  }
40
- const waiting = o.backoff?.waiting(address, intent) ?? null;
41
- if (waiting !== null) {
42
- report.skipped.push({ intent: address, reason: backingOff(waiting.failures, waiting.seconds) });
278
+ if (filling.has(address)) {
279
+ outcomes[index] = skipped(address, ALREADY_FILLING);
43
280
  continue;
44
281
  }
282
+ filling.add(address);
45
283
  try {
46
- const [inputTokenProgram, outputTokenProgram] = await Promise.all([
47
- o.chain.tokenProgram(intent.inputMint),
48
- o.chain.tokenProgram(intent.outputMint),
49
- ]);
50
- const payable = async (draft) => {
51
- const payouts = await payoutAccounts(draft, config.feeRecipient, inputTokenProgram);
52
- const found = await Promise.all(payouts.map((payout) => o.chain.exists(payout.address)));
53
- const missing = payouts.filter((_, i) => !found[i]).map((payout) => payout.name);
54
- return missing.length === 0 ? null : `no ${missing.join(" or ")} account to pay into`;
55
- };
56
- const solverInput = await findAssociatedTokenAddress({
57
- owner: o.solver.address,
58
- mint: intent.inputMint,
59
- tokenProgram: inputTokenProgram,
60
- });
61
- const solverOutput = await findAssociatedTokenAddress({
62
- owner: o.solver.address,
63
- mint: intent.outputMint,
64
- tokenProgram: outputTokenProgram,
65
- });
66
- const [inputExists, outputExists, latest] = await Promise.all([
67
- o.chain.exists(solverInput),
68
- o.chain.exists(solverOutput),
69
- o.chain.blockhash(),
70
- ]);
71
- const draft = {
72
- solver: o.solver,
73
- feeRecipient: config.feeRecipient,
74
- inputTokenProgram,
75
- outputTokenProgram,
76
- computeUnits: MAX_COMPUTE_UNITS,
77
- unitPrice: 0n,
78
- createSolverInput: !inputExists,
79
- createSolverOutput: !outputExists,
80
- tip: o.tip,
81
- };
82
- let fitting = null;
83
- let skipped = null;
84
- let size = { bytes: null, accounts: 0 };
85
- for (const routeAccounts of ROUTE_ACCOUNT_BUDGETS) {
86
- const decision = await decide(address, intent, config, now, o.pricer, o.solver.address, { ...o.strategy, routeAccounts }, payable);
87
- if (decision.kind === "skip") {
88
- skipped = decision.reason;
89
- break;
90
- }
91
- const plan = decision.plan;
92
- const context = { ...draft, unitPrice: await o.unitPrice(await fillInstructions(plan, draft)) };
93
- const instructions = await fillInstructions(plan, context);
94
- const accounts = accountsOf(instructions, o.solver.address);
95
- if (!locksFit(accounts)) {
96
- size = { bytes: null, accounts };
97
- continue;
284
+ if (o.sendBundle === undefined) {
285
+ outcomes[index] = await fillOrder(o, caps, mintData, ready, stock);
286
+ }
287
+ else {
288
+ const checked = await queueOrder(o, caps, mintData, ready, stock);
289
+ if (checked.kind === "checked") {
290
+ queue.push({ index, ready, wire: checked.wire, entry: checked.entry, ...(checked.holds === undefined ? {} : { holds: checked.holds }) });
98
291
  }
99
- const tables = await o.chain.lookupTables(lookupTablesOf(plan, o.lookupTables));
100
- const probe = await compile(instructions, o.solver, latest, tables);
101
- const bytes = wireSize(probe);
102
- size = { bytes, accounts };
103
- if (bytesFit(bytes)) {
104
- fitting = { plan, context, tables, probe };
105
- break;
292
+ else {
293
+ outcomes[index] = checked;
106
294
  }
107
295
  }
108
- if (skipped !== null || fitting === null) {
109
- report.skipped.push({ intent: address, reason: skipped ?? tooLarge(size.bytes, size.accounts) });
110
- continue;
111
- }
112
- const { plan, context, tables, probe } = fitting;
113
- const before = await Promise.all([o.chain.tokenAmount(solverInput), o.chain.tokenAmount(solverOutput)]);
114
- const simulated = await o.simulate(probe, [solverInput, solverOutput]);
115
- if (simulated.err !== null) {
116
- report.failed.push({ intent: address, error: `${SIMULATION_REFUSED}: ${simulated.err}` });
117
- continue;
118
- }
119
- const [inputAfter, outputAfter] = simulated.balances;
120
- const least = [before[0] ?? 0n, (before[1] ?? 0n) + plan.profit];
121
- if (inputAfter === null || inputAfter === undefined || outputAfter === null || outputAfter === undefined || inputAfter < (least[0] ?? 0n) || outputAfter < (least[1] ?? 0n)) {
122
- report.failed.push({
123
- intent: address,
124
- error: `${BALANCES_REFUSED}: input ${before[0]} to ${inputAfter}, output ${before[1]} to ${outputAfter}, expected at least ${least[0]} and ${least[1]}`,
125
- });
126
- continue;
296
+ }
297
+ catch (error) {
298
+ if (error instanceof RateLimited) {
299
+ halted = error.message;
300
+ outcomes[index] = skipped(address, halted);
127
301
  }
128
- const final = simulated.unitsConsumed === null ? context : { ...context, computeUnits: headroom(simulated.unitsConsumed) };
129
- const instructions = await fillInstructions(plan, final);
130
- const wire = simulated.unitsConsumed === null ? probe : await compile(instructions, o.solver, latest, tables);
131
- const cost = solCost({
132
- signatures: signaturesOf(wire),
133
- unitPrice: final.unitPrice,
134
- computeUnits: final.computeUnits,
135
- tip: final.tip?.lamports ?? 0n,
136
- });
137
- const rent = await createdRent(instructions, (account) => o.chain.exists(account), mintData);
138
- const kept = rent > 0n ? { rent } : {};
139
- if (cost > cap) {
140
- report.skipped.push({ intent: address, reason: tooCostly(cost, cap), ...kept });
141
- continue;
302
+ else {
303
+ outcomes[index] = failed(address, messageOf(error));
142
304
  }
143
- if (rent > rentCap) {
144
- report.skipped.push({ intent: address, reason: tooMuchRent(rent, rentCap), ...kept });
145
- continue;
305
+ }
306
+ }
307
+ if (o.sendBundle !== undefined) {
308
+ const last = async (item) => {
309
+ const { ready, wire } = item;
310
+ if (o.tip === null) {
311
+ return wire;
146
312
  }
147
- if (wire !== probe) {
148
- const checked = await o.simulate(wire, [solverInput, solverOutput]);
149
- if (checked.err !== null) {
150
- report.failed.push({ intent: address, error: `${SIMULATION_REFUSED}: ${checked.err}` });
151
- continue;
313
+ try {
314
+ const checked = await checkOrder(o, caps, mintData, ready, new Map());
315
+ if (checked.kind !== "checked") {
316
+ return checked;
152
317
  }
318
+ item.entry = checked.entry;
319
+ return checked.wire;
153
320
  }
154
- const sent = await o.send(wire);
155
- (sent.confirmed ? report.filled : report.sent).push({ intent: address, profit: plan.profit, signature: sent.signature, ...kept });
156
- }
157
- catch (error) {
158
- if (error instanceof RateLimited) {
159
- limited = error.message;
160
- report.skipped.push({ intent: address, reason: limited });
161
- continue;
321
+ catch (error) {
322
+ return error instanceof RateLimited ? skipped(ready.plan.address, error.message) : failed(ready.plan.address, messageOf(error));
162
323
  }
163
- report.failed.push({ intent: address, error: error instanceof Error ? error.message : String(error) });
324
+ };
325
+ for (const done of await sendBundles(queue, bundleSize, o.sendBundle, last)) {
326
+ outcomes[done.item.index] =
327
+ "refused" in done
328
+ ? done.refused
329
+ : "error" in done
330
+ ? failed(done.item.ready.plan.address, done.error)
331
+ : { kind: "sent", entry: { ...done.item.entry, signature: done.signature, ...(done.item.holds === undefined ? {} : { holds: done.item.holds }) } };
332
+ }
333
+ }
334
+ const report = { filled: [], sent: [], skipped: [], failed: [] };
335
+ for (const outcome of outcomes) {
336
+ if (outcome?.kind === "skipped") {
337
+ report.skipped.push(outcome.entry);
338
+ }
339
+ else if (outcome?.kind === "failed") {
340
+ report.failed.push(outcome.entry);
341
+ }
342
+ else if (outcome !== undefined) {
343
+ report[outcome.kind].push(outcome.entry);
164
344
  }
165
345
  }
166
346
  if (o.backoff !== undefined) {
@@ -177,103 +357,4 @@ export async function tick(o) {
177
357
  }
178
358
  return report;
179
359
  }
180
- export const FAILED_ON_CHAIN = "the fill was sent but failed on chain";
181
- export async function landings(flights, now, listed, failedOnChain, held) {
182
- const failed = [];
183
- for (const { address, intent } of listed) {
184
- const flight = flights.get(address);
185
- if (flight === undefined || flight.until > now) {
186
- continue;
187
- }
188
- flights.delete(address);
189
- if (intent.status !== IntentStatus.Open) {
190
- continue;
191
- }
192
- const lost = await failedOnChain(flight.signature).catch(() => false);
193
- if (lost) {
194
- held.failed(address, intent);
195
- failed.push({ intent: address, error: `${FAILED_ON_CHAIN}: ${flight.signature}` });
196
- }
197
- }
198
- return failed;
199
- }
200
- export const CONFIG_TTL_MS = 30_000;
201
- export const BLOCKHASH_TTL_MS = 20_000;
202
- function fresh(ttl, clock, load) {
203
- let held = null;
204
- return {
205
- get: () => {
206
- if (held === null || clock() - held.at > ttl) {
207
- const value = load();
208
- const entry = { at: clock(), value };
209
- held = entry;
210
- value.catch(() => {
211
- if (held === entry) {
212
- held = null;
213
- }
214
- });
215
- }
216
- return held.value;
217
- },
218
- clear: () => {
219
- held = null;
220
- },
221
- };
222
- }
223
- export function remembering(chain, clock = Date.now) {
224
- const programs = new Map();
225
- const mints = new Map();
226
- const existing = new Set();
227
- const config = fresh(CONFIG_TTL_MS, clock, () => chain.config());
228
- const blockhash = fresh(BLOCKHASH_TTL_MS, clock, () => chain.blockhash());
229
- return {
230
- ...chain,
231
- config: () => config.get(),
232
- blockhash: () => blockhash.get(),
233
- tokenProgram: (mint) => {
234
- const known = programs.get(mint);
235
- if (known !== undefined) {
236
- return known;
237
- }
238
- const loaded = chain.tokenProgram(mint);
239
- programs.set(mint, loaded);
240
- loaded.catch(() => programs.delete(mint));
241
- return loaded;
242
- },
243
- ...(chain.mintData === undefined
244
- ? {}
245
- : {
246
- mintData: (mint) => {
247
- const known = mints.get(mint);
248
- if (known !== undefined) {
249
- return known;
250
- }
251
- const loaded = chain.mintData?.(mint) ?? Promise.resolve(null);
252
- mints.set(mint, loaded);
253
- loaded.then((data) => {
254
- if (data === null) {
255
- mints.delete(mint);
256
- }
257
- }, () => mints.delete(mint));
258
- return loaded;
259
- },
260
- }),
261
- exists: async (address) => {
262
- if (existing.has(address)) {
263
- return true;
264
- }
265
- const found = await chain.exists(address);
266
- if (found) {
267
- existing.add(address);
268
- }
269
- return found;
270
- },
271
- forget: () => {
272
- programs.clear();
273
- existing.clear();
274
- config.clear();
275
- blockhash.clear();
276
- },
277
- };
278
- }
279
360
  //# sourceMappingURL=solver.js.map