Skip to content

feat: add swap command#57

Merged
blakecduncan merged 4 commits intoreleasefrom
blake/swap
Apr 10, 2026
Merged

feat: add swap command#57
blakecduncan merged 4 commits intoreleasefrom
blake/swap

Conversation

@blakecduncan
Copy link
Copy Markdown

@blakecduncan blakecduncan commented Apr 10, 2026

Summary

  • add alchemy swap quote and alchemy swap execute for same-chain token swaps
  • wire the new swap command into the CLI and add focused test coverage
  • clarify swap output so omitted slippage shows the API default and quoted receive amounts are labeled as minimum receive values

Test plan

  • pnpm test tests/commands/swap.test.ts
  • pnpm lint

Add swap quote and execute commands so the CLI can request quotes, handle permit flows, and submit same-chain token swaps with test coverage.

Made-with: Cursor
Avoid sending the default slippage value to wallet quote requests so swap quotes and executions use the API default unless the user explicitly overrides it.

Made-with: Cursor
Avoid reporting a slippage value the CLI did not send and label swap quote output as a minimum receive amount so the output matches the underlying quote semantics.

Made-with: Cursor
@blakecduncan blakecduncan requested a review from a team as a code owner April 10, 2026 00:27
@blakecduncan blakecduncan changed the title feat: add contract, swap, and Solana wallet flows feat: add swap command Apr 10, 2026
.description("Get a swap quote without executing")
.requiredOption("--from <address>", "Token address to swap from (use 0xEeee...EEeE for the native token)")
.requiredOption("--to <address>", "Token address to swap to")
.requiredOption("--amount <number>", "Amount to swap (human-readable)")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: what do we mean by human-readable here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is ambiguous - I updated it.

was basically saying to pass the value in decimal form (human readable) and not in base units like wei

.command("quote")
.description("Get a swap quote without executing")
.requiredOption("--from <address>", "Token address to swap from (use 0xEeee...EEeE for the native token)")
.requiredOption("--to <address>", "Token address to swap to")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I know it's in the description, but just to add additional guardrail for agents - can we do --to/from <token_address>

const type = quote.type ?? "unknown";

if (quote.quote?.minimumToAmount !== undefined) {
return { type, minimumOutput: BigInt(quote.quote.minimumToAmount) };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quote is typed any here, which bypasses the RequestQuoteV0Result type that's already imported. If the SDK response shape changes this would silently hide bugs. Consider using the actual SDK type:

function extractQuoteData(quote: RequestQuoteV0Result): { type: string; minimumOutput?: bigint }

...(paymaster ? { capabilities: { paymaster } } : {}),
};

return request as Parameters<ReturnType<ReturnType<typeof buildWalletClient>["client"]["extend"]>["requestQuoteV0"]>[0];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This as Parameters<...>[0] cast is brittle — if the SDK signature changes, this will silently pass the wrong shape at runtime. Consider typing the request object directly against the SDK's expected input type, or at minimum adding a comment explaining why the cast is necessary.

printKeyValueBox(pairs);
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quote is declared with let but never reassigned — can be const.

More broadly, the preparedQuote flow is a bit hard to follow with two conditional branches and a post-check for the permit type. Consider extracting the paymaster-permit handling into a small helper to reduce nesting in performSwapExecute.


// ── Quote implementation ────────────────────────────────────────────

async function performSwapQuote(program: Command, opts: SwapOpts) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the token address is invalid or the RPC call to fetch decimals fails (e.g. non-ERC20 address), the error will bubble up with a potentially cryptic message. Worth confirming that fetchTokenDecimals provides a user-friendly error in this context, or wrapping with a clearer message like "Failed to resolve token info for

".

}

function formatTokenAmount(rawAmount: bigint, decimals: number): string {
const str = rawAmount.toString().padStart(decimals + 1, "0");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatTokenAmount works correctly when decimals is 0, but the behavior is subtle (the padStart / slice logic). A brief comment or a unit test for that edge case would help future maintainers.

Type the swap quote helpers against the SDK surface so response and request shape changes fail fast, and clarify swap inputs and token metadata failures for operators.

Made-with: Cursor
@blakecduncan blakecduncan merged commit c0e5ec6 into release Apr 10, 2026
4 checks passed
@blakecduncan blakecduncan deleted the blake/swap branch April 10, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants