From 05e3219b9a911b1e01dfa97d615eba143e4a3562 Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Tue, 21 Apr 2026 10:29:20 -0700 Subject: [PATCH 1/3] fix: validate solana addresses for actions --- packages/sdk/src/actions/getSwapQuote.ts | 7 ++-- packages/sdk/src/api/swap-approval.ts | 48 ++++++++++++------------ packages/sdk/src/api/validators.ts | 10 +++++ packages/sdk/src/types/index.ts | 9 +++++ 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/packages/sdk/src/actions/getSwapQuote.ts b/packages/sdk/src/actions/getSwapQuote.ts index 1701f52..bb3be6e 100644 --- a/packages/sdk/src/actions/getSwapQuote.ts +++ b/packages/sdk/src/actions/getSwapQuote.ts @@ -5,8 +5,7 @@ import { SwapApprovalApiResponse, swapApprovalResponseSchema, } from "../api/swap-approval.js"; -import { Amount, Action } from "../types/index.js"; -import { Address } from "viem"; +import { Amount, Action, AnyChainAddress } from "../types/index.js"; /** * Params for {@link getSwapQuote}. @@ -25,9 +24,9 @@ export type GetSwapQuoteParams = Omit< amount: Amount; route: { originChainId: number; - inputToken: Address; + inputToken: AnyChainAddress; destinationChainId: number; - outputToken: Address; + outputToken: AnyChainAddress; }; skipOriginTxEstimation?: boolean; slippage?: number; diff --git a/packages/sdk/src/api/swap-approval.ts b/packages/sdk/src/api/swap-approval.ts index ca6a83b..37f92ec 100644 --- a/packages/sdk/src/api/swap-approval.ts +++ b/packages/sdk/src/api/swap-approval.ts @@ -1,6 +1,6 @@ import z from "zod"; import { - ethereumAddress, + anyChainAddress, bigNumberString, positiveInteger, positiveIntString, @@ -19,21 +19,21 @@ export enum FeeDetailsType { export const baseSwapQueryParamsSchema = z.object({ amount: positiveIntString, tradeType: z.enum(["minOutput", "exactOutput", "exactInput"]).optional(), - inputToken: ethereumAddress, - outputToken: ethereumAddress, + inputToken: anyChainAddress, + outputToken: anyChainAddress, originChainId: positiveIntString, destinationChainId: positiveIntString, - depositor: ethereumAddress, - recipient: ethereumAddress.optional(), + depositor: anyChainAddress, + recipient: anyChainAddress.optional(), integratorId: z.string().optional(), - refundAddress: ethereumAddress.optional(), + refundAddress: anyChainAddress.optional(), refundOnOrigin: booleanString.optional(), slippage: positiveFloatString(0.5).optional(), // max. 50% slippage skipOriginTxEstimation: booleanString.optional(), excludeSources: stringOrStringArray.optional(), includeSources: stringOrStringArray.optional(), appFee: positiveFloatString(1).optional(), - appFeeRecipient: ethereumAddress.optional(), + appFeeRecipient: anyChainAddress.optional(), strictTradeType: booleanString.optional(), }); @@ -43,7 +43,7 @@ const feeComponentSchema = z.object({ amountUsd: z.string(), pct: z.string(), token: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -55,7 +55,7 @@ const bridgeFeeDetailComponentSchema = z.object({ pct: z.string(), token: z .object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -69,7 +69,7 @@ const gasFeeSchema = z.object({ amount: bigNumberString, amountUsd: z.string(), token: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -78,13 +78,13 @@ const gasFeeSchema = z.object({ const swapStepSchema = z.object({ tokenIn: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, }), tokenOut: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -103,14 +103,14 @@ const bridgeStepSchema = z.object({ inputAmount: bigNumberString, outputAmount: bigNumberString, tokenIn: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, name: z.string().optional(), }), tokenOut: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -120,7 +120,7 @@ const bridgeStepSchema = z.object({ amount: bigNumberString, pct: z.string(), token: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, @@ -139,14 +139,14 @@ const bridgeStepSchema = z.object({ }); const allowanceCheckSchema = z.object({ - token: ethereumAddress, - spender: ethereumAddress, + token: anyChainAddress, + spender: anyChainAddress, actual: bigNumberString, expected: bigNumberString, }); const balanceCheckSchema = z.object({ - token: ethereumAddress, + token: anyChainAddress, actual: bigNumberString, expected: bigNumberString, }); @@ -154,7 +154,7 @@ const balanceCheckSchema = z.object({ const swapTxSchema = z.object({ simulationSuccess: z.boolean(), chainId: positiveInteger, - to: ethereumAddress, + to: anyChainAddress, data: z.string(), value: bigNumberString.optional(), gas: bigNumberString.optional(), @@ -167,7 +167,7 @@ const eip712Schema = z.object({ name: z.string(), version: z.string(), chainId: positiveInteger, - verifyingContract: ethereumAddress, + verifyingContract: anyChainAddress, }), types: z.record( z.array( @@ -197,7 +197,7 @@ export const swapApprovalResponseSchema = z.object({ .array( z.object({ chainId: positiveInteger, - to: ethereumAddress, + to: anyChainAddress, data: z.string(), }), ) @@ -208,21 +208,21 @@ export const swapApprovalResponseSchema = z.object({ destinationSwap: swapStepSchema.optional(), }), inputToken: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, name: z.string().optional(), }), outputToken: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, name: z.string().optional(), }), refundToken: z.object({ - address: ethereumAddress, + address: anyChainAddress, symbol: z.string(), decimals: positiveInteger, chainId: positiveInteger, diff --git a/packages/sdk/src/api/validators.ts b/packages/sdk/src/api/validators.ts index 66d6d04..dc185e2 100644 --- a/packages/sdk/src/api/validators.ts +++ b/packages/sdk/src/api/validators.ts @@ -7,6 +7,16 @@ export const ethereumAddress = z.string().regex(/^0x[a-fA-F0-9]{40}$/, { message: "Invalid Ethereum address format", }); +// Solana base58 address (32-44 chars) +export const solanaAddress = z + .string() + .regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/, { + message: "Invalid Solana address format", + }); + +// EVM or Solana address +export const anyChainAddress = z.union([ethereumAddress, solanaAddress]); + export const bigNumberString = z.string().regex(/^-?\d+$/, { message: "Invalid BigNumber string format", }); diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index 639d501..1a5fbc2 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -19,6 +19,15 @@ export type Status = keyof typeof STATUS; export type Amount = string | bigint; +/** + * Solana base58 address (32-44 chars, no 0/O/I/l). + * Branded to distinguish from plain strings at the type level. + */ +export type SolanaAddress = string & { readonly __brand: "SolanaAddress" }; + +/** EVM (viem Address) or Solana address. */ +export type AnyChainAddress = Address | SolanaAddress; + export type ConfiguredWalletClient = WalletClient; export type ConfiguredPublicClient = PublicClient; From d973e6d3523b1fcea0826e9d72b67398b12e188b Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Tue, 21 Apr 2026 13:04:40 -0700 Subject: [PATCH 2/3] chore: changeset --- .changeset/old-wolves-bake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/old-wolves-bake.md diff --git a/.changeset/old-wolves-bake.md b/.changeset/old-wolves-bake.md new file mode 100644 index 0000000..f02b756 --- /dev/null +++ b/.changeset/old-wolves-bake.md @@ -0,0 +1,5 @@ +--- +"@across-protocol/app-sdk": minor +--- + +fix: validate solana addresses for actions From 3a26a57a55d4eeb6cdedb50f982d26534740d9c7 Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Tue, 21 Apr 2026 13:12:40 -0700 Subject: [PATCH 3/3] fix: remove branded type for consumers --- packages/sdk/src/types/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index 1a5fbc2..6b783a5 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -19,14 +19,8 @@ export type Status = keyof typeof STATUS; export type Amount = string | bigint; -/** - * Solana base58 address (32-44 chars, no 0/O/I/l). - * Branded to distinguish from plain strings at the type level. - */ -export type SolanaAddress = string & { readonly __brand: "SolanaAddress" }; - /** EVM (viem Address) or Solana address. */ -export type AnyChainAddress = Address | SolanaAddress; +export type AnyChainAddress = Address | string; export type ConfiguredWalletClient = WalletClient; export type ConfiguredPublicClient = PublicClient;