mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-05 12:28:23 +00:00
fix: force fallback loop to use selected mint by excluding failed mints
The SDK treats mintUrl as a preference, not a requirement — its own balance-based candidate could win and make every outer retry hit the same failed mint. Thread excludeMints through TopUpOptions and, for each fallback candidate, exclude every other known candidate so the SDK is forced to use the loop's chosen mint.
This commit is contained in:
@@ -13,6 +13,7 @@ type TopUpOptions = {
|
||||
baseUrl: string;
|
||||
amount: number;
|
||||
token?: string;
|
||||
excludeMints?: string[];
|
||||
};
|
||||
|
||||
type TopUpResult = {
|
||||
@@ -298,11 +299,12 @@ export function installCreateProviderTokenFallback(
|
||||
const originalCreateProviderToken = balanceManager.createProviderToken.bind(balanceManager);
|
||||
|
||||
balanceManager.createProviderToken = async (options: TopUpOptions): Promise<TopUpResult> => {
|
||||
const candidates = await getTopUpMintCandidates(
|
||||
const inheritedExclusions = new Set(options.excludeMints ?? []);
|
||||
const candidates = (await getTopUpMintCandidates(
|
||||
options.mintUrl,
|
||||
walletClient,
|
||||
walletAdapter,
|
||||
);
|
||||
)).filter((mintUrl) => !inheritedExclusions.has(mintUrl));
|
||||
|
||||
let lastResult: TopUpResult | undefined;
|
||||
for (const [index, mintUrl] of candidates.entries()) {
|
||||
@@ -312,7 +314,19 @@ export function installCreateProviderTokenFallback(
|
||||
);
|
||||
}
|
||||
|
||||
const result = await originalCreateProviderToken({ ...options, mintUrl });
|
||||
// The SDK treats mintUrl as a preference, not a requirement: its own
|
||||
// balance-based candidate can still win and make every outer retry hit
|
||||
// the same failed mint. Excluding every other known candidate makes this
|
||||
// attempt use the mint selected by this fallback loop.
|
||||
const excludeMints = uniqueMintUrls([
|
||||
...inheritedExclusions,
|
||||
...candidates.filter((candidate) => candidate !== mintUrl),
|
||||
]);
|
||||
const result = await originalCreateProviderToken({
|
||||
...options,
|
||||
mintUrl,
|
||||
excludeMints,
|
||||
});
|
||||
if (result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -269,6 +269,59 @@ describe("SDK top-up mint fallback", () => {
|
||||
expect(attempts).toEqual(["https://mint-a.example", "https://mint-b.example"]);
|
||||
});
|
||||
|
||||
test("forces the requested fallback mint by excluding previously failed mints", async () => {
|
||||
const attempts: Array<{ preferred: string; selected: string; excluded: string[] }> = [];
|
||||
const availableMints = ["https://mint-a.example", "https://mint-b.example"];
|
||||
const balanceManager = {
|
||||
topUp: async (_options: { mintUrl: string }) => ({ success: false, message: "not patched" }),
|
||||
// Reproduce the SDK's selection order: its automatic balance choice wins
|
||||
// over preferredMintUrl unless the earlier mint is explicitly excluded.
|
||||
createProviderToken: async (options: { mintUrl: string; excludeMints?: string[] }) => {
|
||||
const excluded = options.excludeMints ?? [];
|
||||
const selected = availableMints.find((mint) => !excluded.includes(mint))!;
|
||||
attempts.push({ preferred: options.mintUrl, selected, excluded });
|
||||
if (selected === "https://mint-a.example") {
|
||||
return { success: false, error: "Send failed: Not enough proofs to send" };
|
||||
}
|
||||
return { success: true, token: "cashu-token", selectedMintUrl: selected, amountSpent: 21 };
|
||||
},
|
||||
};
|
||||
const client = { getBalanceManager: () => balanceManager };
|
||||
const walletAdapter = {
|
||||
getBalances: async () => ({
|
||||
"https://mint-a.example": 2_916,
|
||||
"https://mint-b.example": 30_000,
|
||||
}),
|
||||
};
|
||||
|
||||
installMintFallbackTopUp(
|
||||
client,
|
||||
createCocodClient(availableMints),
|
||||
walletAdapter,
|
||||
{ log: () => undefined, warn: () => undefined, error: () => undefined },
|
||||
);
|
||||
|
||||
const result = await balanceManager.createProviderToken({
|
||||
mintUrl: "https://mint-a.example",
|
||||
baseUrl: "https://provider.example",
|
||||
amount: 389,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(attempts).toEqual([
|
||||
{
|
||||
preferred: "https://mint-a.example",
|
||||
selected: "https://mint-a.example",
|
||||
excluded: ["https://mint-b.example"],
|
||||
},
|
||||
{
|
||||
preferred: "https://mint-b.example",
|
||||
selected: "https://mint-b.example",
|
||||
excluded: ["https://mint-a.example"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("createProviderToken falls back to routstr-core invoice + NWC payment", async () => {
|
||||
const createAttempts: string[] = [];
|
||||
const balanceManager = {
|
||||
|
||||
Reference in New Issue
Block a user