Skip to content

[LWDM] feat(coin-solana): [LIVE-27766] alpaca api validateIntent#15586

Merged
cted-ledger merged 1 commit intodevelopfrom
feat/LIVE-27766-solana-alpaca-validateIntent
Mar 30, 2026
Merged

[LWDM] feat(coin-solana): [LIVE-27766] alpaca api validateIntent#15586
cted-ledger merged 1 commit intodevelopfrom
feat/LIVE-27766-solana-alpaca-validateIntent

Conversation

@cted-ledger
Copy link
Copy Markdown
Contributor

✅ Checklist

  • npx changeset was attached.
  • Covered by automatic tests.
  • Impact of the changes:
    • No user-facing changes

📝 Description

Implements the validateIntent Alpaca API method for Solana

❓ Context

  • JIRA or GitHub link: LIVE-LIVE-27766

🧐 Checklist for the PR Reviewers

  • The code aligns with the requirements described in the linked JIRA or GitHub issue.
  • The PR description clearly documents the changes made and explains any technical trade-offs or design decisions.
  • There are no undocumented trade-offs, technical debt, or maintainability issues.
  • The PR has been tested thoroughly, and any potential edge cases have been considered and handled.
  • Any new dependencies have been justified and documented.
  • Performance considerations have been taken into account. (changes have been profiled or benchmarked if necessary)

@cted-ledger cted-ledger requested a review from a team as a code owner March 19, 2026 17:29
Copilot AI review requested due to automatic review settings March 19, 2026 17:29
@live-github-bot live-github-bot Bot added coin-modules-api Has changes in the coin-framework/coin-modules APIs coin-modules labels Mar 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 19, 2026

⚠️ E2E tests are required

Changes detected require e2e testing before merge (even before asking for any review).

🖥️ Desktop

-> Run Desktop E2E

  • Select "Run workflow"
  • Branch: feat/LIVE-27766-solana-alpaca-validateIntent
  • Device: nanoSP or stax

📱 Mobile

-> Run Mobile E2E

  • Select "Run workflow"
  • Branch: feat/LIVE-27766-solana-alpaca-validateIntent
  • Device: nanoX

Affected coins modules: solana

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements the validateIntent Alpaca API method for Solana along with supporting utility functions for address validation, token conversion, sequence retrieval, and intent type mapping. The implementation provides comprehensive validation logic for both regular and staking transactions on Solana, including proper handling of native SOL and SPL token transfers.

Changes:

  • Implements validateIntent with comprehensive validation logic for Solana transaction intents
  • Adds supporting utility functions: validateAddress, getNextSequence, getTokenFromAsset, getAssetFromToken, and computeIntentType
  • Integrates new functions into the Alpaca and Bridge API factories
  • Includes extensive unit, integration, and mocked tests for all new functionality

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
libs/coin-modules/coin-solana/src/logic/validateIntent.ts Main validation logic for transaction intents, handling both regular and staking operations with error/warning detection
libs/coin-modules/coin-solana/src/logic/validateAddress.ts Simple wrapper for Base58 address validation
libs/coin-modules/coin-solana/src/logic/getTokenFromAsset.ts Token/asset conversion utilities for linking Ledger TokenCurrency with Alpaca AssetInfo
libs/coin-modules/coin-solana/src/logic/getNextSequence.ts Retrieves current Solana slot for transaction sequencing
libs/coin-modules/coin-solana/src/logic/computeIntentType.ts Maps transaction modes to granular intent types for staking operations
libs/coin-modules/coin-solana/src/api/index.ts API factory refactored to separate AlpacaApi and BridgeApi, combining both in createApi
libs/coin-modules/coin-solana/src/api/index.unit.test.ts Comprehensive test coverage for API factory with mocked logic functions
libs/coin-modules/coin-solana/src/logic/tests/ Multiple test files with unit, integration, and mocked test coverage
.changeset/odd-mugs-complain.md Changeset documenting the minor version bump

Comment thread libs/coin-modules/coin-solana/src/logic/validateIntent.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 19, 2026

Web Tools Build Status

Build Status Deployment
Web Tools Build ✅ Deployed https://web-tools-8t39ffoxp-ledger-hq-prd.vercel.app
Native Storybook Build ⏭️ Skipped
React Storybook Build ⏭️ Skipped

@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from 1b5b66b to 233baa9 Compare March 19, 2026 22:51
Copilot AI review requested due to automatic review settings March 20, 2026 08:33
@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from 233baa9 to afdddfa Compare March 20, 2026 08:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements the validateIntent Alpaca API method for Solana, along with supporting utilities for address validation, sequence numbering, token-asset conversion, and intent type computation. These additions extend the Solana coin module's API compliance with the Alpaca framework.

Changes:

  • Implements validateIntent function with comprehensive validation logic for native transfers, token transfers, and staking operations
  • Adds validateAddress function for Base58 address validation
  • Introduces helper functions getTokenFromAsset and getAssetFromToken for bidirectional conversion between Alpaca and Ledger token representations
  • Adds getNextSequence and computeIntentType functions to support the generic-alpaca bridge
  • Refactors API factory to split AlpacaApi and BridgeApi implementations with comprehensive test coverage
  • Includes changeset documenting the new validateIntent implementation

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
libs/coin-modules/coin-solana/src/logic/validateIntent.ts Core validation logic for transaction intents with native transfer, token transfer, and staking support
libs/coin-modules/coin-solana/src/logic/validateAddress.ts Base58 address validation wrapper
libs/coin-modules/coin-solana/src/logic/getTokenFromAsset.ts Bidirectional token-asset conversion utilities
libs/coin-modules/coin-solana/src/logic/getNextSequence.ts Sequence number fetching from RPC
libs/coin-modules/coin-solana/src/logic/computeIntentType.ts Intent type mapping for generic-alpaca bridge
libs/coin-modules/coin-solana/src/api/index.ts API factory refactoring to expose AlpacaApi and BridgeApi methods
libs/coin-modules/coin-solana/src/logic/tests/ Comprehensive unit and integration tests for all new functions
.changeset/odd-mugs-complain.md Changeset documentation

Comment thread libs/coin-modules/coin-solana/src/api/index.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/logic/getNextSequence.ts Outdated
Copy link
Copy Markdown
Member

@hedi-edelbloute hedi-edelbloute left a comment

Choose a reason for hiding this comment

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

lgtm

Comment thread libs/coin-modules/coin-solana/src/logic/validateIntent.ts
Comment thread libs/coin-modules/coin-solana/src/api/index.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/logic/getTokenFromAsset.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/logic/validateAddress.ts
Comment thread libs/coin-modules/coin-solana/src/logic/validateIntent.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/logic/computeIntentType.ts Outdated
if (
isStakingTransactionIntent(transactionIntent) ||
transactionIntent.type === "stake.withdraw"
) {
Copy link
Copy Markdown
Contributor

@qperrot qperrot Mar 20, 2026

Choose a reason for hiding this comment

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

why checking transactionIntent.type === "stake.withdraw" ? tx.intentType is not "staking" when we do a withdraw ?

Copy link
Copy Markdown
Contributor Author

@cted-ledger cted-ledger Mar 27, 2026

Choose a reason for hiding this comment

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

stake.withdraw is a Solana-specific staking operation that doesn't fit the generic StakingTransactionIntent type from coin-framework, which requires mode: StakingOperation ("delegate" | "undelegate" | "redelegate") and valAddress. Since we can't extend coin-framework types here, stake.withdraw intents end up with
intentType: "transaction" by default, making isStakingTransactionIntent return false for them. The explicit || intent.type === "stake.withdraw" check is intentional to route these intents through validateStakingIntent, which correctly handles them (see the stake.withdraw case in the switch).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree it feels something if off here. We can discuss adding modes in the coin framework meeting (I am afraid it delays the migration to alpaca-coin-module even more, but that is another story).
Also what about "stake.createAccount" ? I don't see this mode in StakingOperation.

Comment thread libs/coin-modules/coin-solana/src/logic/validateIntent.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/logic/validateIntent.ts
Comment thread libs/coin-modules/coin-solana/src/logic/getNextSequence.ts Outdated
Comment thread libs/coin-modules/coin-solana/src/api/index.ts Outdated
@cted-ledger cted-ledger marked this pull request as draft March 20, 2026 09:28
@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from afdddfa to 6a175d3 Compare March 27, 2026 13:15
@live-github-bot live-github-bot Bot added the common Has changes in live-common label Mar 27, 2026
@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch 2 times, most recently from 38738a7 to 24ec6ae Compare March 27, 2026 23:42
@cted-ledger cted-ledger marked this pull request as ready for review March 27, 2026 23:42
Copilot AI review requested due to automatic review settings March 27, 2026 23:42
@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from 24ec6ae to b31b22d Compare March 27, 2026 23:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

warnings,
estimatedFees,
amount,
totalSpent: estimatedFees,
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

In validateStakingIntent, totalSpent is always set to estimatedFees. For stake.createAccount, the main account balance decreases by amount + estimatedFees (consistent with existing Live logic in src/getTransactionStatus.ts, where stake.createAccount totalSpent is amount + fee). Returning only the fees here will under-report totalSpent for the intent validation.

Consider computing totalSpent per staking intent type (e.g., stake.createAccount => amount + estimatedFees, while stake.delegate/stake.undelegate/stake.withdraw remain estimatedFees).

Suggested change
totalSpent: estimatedFees,
totalSpent:
intent.type === "stake.createAccount" ? amount + estimatedFees : estimatedFees,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed


expect(result.errors).toEqual({});
expect(result.amount).toBe(1_000_000_000n);
expect(result.totalSpent).toBe(5000n);
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The expectation result.totalSpent equals only the fee (5000n) for stake.createAccount is likely incorrect if totalSpent represents the sender’s native balance decrease. stake.createAccount moves amount out of the main account (into the stake account), so totalSpent should include amount + fees (as coin-solana’s existing getTransactionStatus does for stake.createAccount).

Suggested change
expect(result.totalSpent).toBe(5000n);
expect(result.totalSpent).toBe(1_000_000_000n + 5000n);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Copy link
Copy Markdown
Contributor

@francois-guerin-ledger francois-guerin-ledger left a comment

Choose a reason for hiding this comment

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

I am not a fan of functions modifying objects (like computeCreateAccountAmount). But the rational seems to be only mutate if the function is not exported, so fine by me.

if (
isStakingTransactionIntent(transactionIntent) ||
transactionIntent.type === "stake.withdraw"
) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree it feels something if off here. We can discuss adding modes in the coin framework meeting (I am afraid it delays the migration to alpaca-coin-module even more, but that is another story).
Also what about "stake.createAccount" ? I don't see this mode in StakingOperation.

errors: Record<string, Error>,
): bigint {
if (!intent.recipient) {
errors.recipient = new RecipientRequired("");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
errors.recipient = new RecipientRequired("");
errors.recipient = new RecipientRequired();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment on lines +3 to +5
export async function getNextSequence(api: ChainAPI, _address: string): Promise<bigint> {
const slot = await api.connection.getSlot();
return BigInt(slot);
Copy link
Copy Markdown
Contributor

@francois-guerin-ledger francois-guerin-ledger Mar 28, 2026

Choose a reason for hiding this comment

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

getSlot is the latest block height, nothing to do with the transaction sequence number for a particular address.
This one is tricky though, because Solana does not use this concept of sequence number. I see we just compute it manually here and here.
We could make a little study

  • if we don't care about differents numbers, what are the impact of returning 0 ?
  • if we care about increasing differents numbers, what are the impact of returning the timestamp ?
    I am saying this to avoid HTTP calls

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Solana doesn't have sequence numbers per address. getNextSequence was calling getSlot() only to produce a large enough number to avoid collisions with confirmed op sequences. Date.now() serves the same purpose without the RPC call.

here is the new implementation for getNextSequence:

export function getNextSequence(_address: string): bigint {
  return BigInt(Date.now());
}

Comment on lines +165 to +169
b.asset.type !== "native" &&
"assetReference" in b.asset &&
"assetReference" in (intent.asset ?? {}) &&
(b.asset as { assetReference: string }).assetReference ===
(intent.asset as { assetReference: string }).assetReference,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
b.asset.type !== "native" &&
"assetReference" in b.asset &&
"assetReference" in (intent.asset ?? {}) &&
(b.asset as { assetReference: string }).assetReference ===
(intent.asset as { assetReference: string }).assetReference,
b.asset.type !== "native" &&
"assetReference" in b.asset &&
"assetReference" in intent.asset &&
b.asset.assetReference === intent.asset.assetReference,

I see this pattern twice, you can consider having a helper.
You may be interested in the EVM equivalent that I believe quite convenient to use:

function assetsAreEqual(asset1: AssetInfo, asset2: AssetInfo): boolean {
if (asset1.type === "native" && asset2.type === "native") return true;
if ("assetReference" in asset1 && "assetReference" in asset2) {
return asset1.assetReference === asset2.assetReference;
}
return false;
}
function findBalance(asset: AssetInfo, balances: Balance[]): Balance {
return balances.find(b => assetsAreEqual(b.asset, asset)) ?? { asset, value: 0n };
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

pattern applied

};
}

export function computeIntentType(transaction: Record<string, unknown>): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe we can make this method evolve to be something like

type IntentType =
  | { intentType: 'transaction', type: string },
  | { intentType: 'staking', mode: StakingOperation, type: string }
computeIntentType(transaction: Record<string, unknown>): IntentType

Not in the scope of this PR though

@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from b31b22d to b1e60f2 Compare March 30, 2026 15:09
@cted-ledger cted-ledger force-pushed the feat/LIVE-27766-solana-alpaca-validateIntent branch from b1e60f2 to 39d4880 Compare March 30, 2026 15:10
@sonarqubecloud
Copy link
Copy Markdown

@cted-ledger cted-ledger merged commit 6b61b57 into develop Mar 30, 2026
123 of 125 checks passed
@cted-ledger cted-ledger deleted the feat/LIVE-27766-solana-alpaca-validateIntent branch March 30, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coin-modules coin-modules-api Has changes in the coin-framework/coin-modules APIs common Has changes in live-common

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants