From c822a5ad9c24d613dfadbd2741a775fcd23b6796 Mon Sep 17 00:00:00 2001 From: towan Date: Fri, 10 Oct 2025 14:44:29 +0400 Subject: [PATCH 01/10] chore: keystore transaction signing --- packages/core/src/index.ts | 14 +- packages/helpers/src/api/swapkitApi/types.ts | 64 ++++++- packages/helpers/src/modules/swapKitError.ts | 4 + packages/helpers/src/types/sdk.ts | 5 +- packages/plugins/package.json | 5 + packages/plugins/src/genericSwap/index.ts | 1 + packages/plugins/src/genericSwap/plugin.ts | 98 +++++++++++ packages/sdk/src/index.ts | 2 + .../toolboxes/src/cosmos/toolbox/cosmos.ts | 43 ++++- .../toolboxes/src/cosmos/toolbox/thorchain.ts | 1 + packages/toolboxes/src/near/toolbox.ts | 12 ++ packages/toolboxes/src/near/types.ts | 49 ------ packages/toolboxes/src/near/types/index.ts | 3 + packages/toolboxes/src/near/types/toolbox.ts | 50 +++++- packages/toolboxes/src/ripple/index.ts | 17 +- packages/toolboxes/src/solana/index.ts | 12 -- packages/toolboxes/src/solana/toolbox.ts | 7 +- packages/toolboxes/src/tron/toolbox.ts | 9 + .../toolboxes/src/utxo/toolbox/bitcoinCash.ts | 161 +++++++----------- packages/toolboxes/src/utxo/toolbox/index.ts | 5 +- packages/toolboxes/src/utxo/toolbox/utxo.ts | 66 ++++--- packages/toolboxes/src/utxo/toolbox/zcash.ts | 21 +++ .../src/keepkey/chains/utxo.ts | 5 +- packages/wallet-hardware/src/trezor/index.ts | 5 +- playgrounds/vite/src/Swap/index.tsx | 1 + 25 files changed, 447 insertions(+), 213 deletions(-) create mode 100644 packages/plugins/src/genericSwap/index.ts create mode 100644 packages/plugins/src/genericSwap/plugin.ts delete mode 100644 packages/toolboxes/src/near/types.ts create mode 100644 packages/toolboxes/src/near/types/index.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ef013c72f0..ed7bf37af9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,6 +16,7 @@ import { SwapKitError, type SwapParams, UTXOChains, + WalletOption, } from "@swapkit/helpers"; import type { EVMTransaction, QuoteResponseRoute } from "@swapkit/helpers/api"; import type { createPlugin } from "@swapkit/plugins"; @@ -201,7 +202,18 @@ export function SwapKit< return wallet; } - function swap({ route, pluginName, ...rest }: SwapParams) { + function swap({ route, pluginName, useApiTx, ...rest }: SwapParams) { + const fromChain = AssetValue.from({ asset: route.sellAsset }).chain; + + // only keystore supports straight signing of all chains + if (useApiTx && getWallet(fromChain)?.walletType === WalletOption.KEYSTORE) { + const plugin = getSwapKitPlugin("genericSwap"); + if ("swap" in plugin) { + // @ts-expect-error TODO: fix this + return plugin.swap({ ...rest, route }); + } + } + const plugin = getSwapKitPlugin(pluginName || route.providers[0]); if ("swap" in plugin) { diff --git a/packages/helpers/src/api/swapkitApi/types.ts b/packages/helpers/src/api/swapkitApi/types.ts index fb7fa1bf61..7a666aeed5 100644 --- a/packages/helpers/src/api/swapkitApi/types.ts +++ b/packages/helpers/src/api/swapkitApi/types.ts @@ -446,6 +446,35 @@ export const EVMTransactionSchema = object({ export type EVMTransaction = z.infer; +export const NEARTransactionSchema = z.object({ + details: z.object({ + blockHash: z.string().describe("Hash of the block"), + nonce: z.number().describe("Nonce of the transaction"), + signerId: z.string().describe("ID of the signer"), + }), + gas: z.string().describe("Gas limit for the transaction"), + gasPrice: z.string().describe("Gas price for the transaction"), + publicKey: z.string().describe("Public key of the signer"), + serialized: z.string().describe("Serialized transaction"), +}); + +export type NEARTransaction = z.infer; + +export const TronTransactionSchema = z.object({ + raw_data: z.object({ + contract: z.any(), + expiration: z.number(), + ref_block_bytes: z.string(), + ref_block_hash: z.string(), + timestamp: z.number(), + }), + raw_data_hex: z.string(), + txID: z.string(), + visible: z.boolean(), +}); + +export type TronTransaction = z.infer; + export const EVMTransactionDetailsParamsSchema = array( union([ string(), @@ -468,7 +497,36 @@ export const EVMTransactionDetailsSchema = object({ export type EVMTransactionDetails = z.infer; -const EncodeObjectSchema = object({ typeUrl: string(), value: unknown() }); +const ThorchainDepositMsgSchema = object({ + typeUrl: string("/types.MsgDeposit"), + value: object({ + coins: array( + object({ + amount: string(), + asset: object({ chain: string(), symbol: string(), synth: boolean(), ticker: string() }), + }), + ), + memo: string(), + signer: string(), + }), +}); + +export type ThorchainDepositMsg = z.infer; + +const CosmosSendMsgSchema = object({ + typeUrl: string("/types.MsgSend"), + value: object({ + amount: array(object({ amount: string(), denom: string() })), + fromAddress: string(), + toAddress: string(), + }), +}); + +export type CosmosSendMsg = z.infer; + +const EncodeObjectSchema = object({ typeUrl: string(), value: CosmosSendMsgSchema.or(ThorchainDepositMsgSchema) }); + +export type APICosmosEncodedObject = z.infer; const FeeSchema = object({ amount: array(object({ amount: string(), denom: string() })), gas: string() }); @@ -571,7 +629,9 @@ const QuoteResponseRouteItem = object({ sourceAddress: string().describe("Source address"), targetAddress: optional(string().describe("Target address")), totalSlippageBps: number().describe("Total slippage in bps"), - tx: optional(union([EVMTransactionSchema, CosmosTransactionSchema, string()])), + tx: optional( + union([EVMTransactionSchema, CosmosTransactionSchema, NEARTransactionSchema, TronTransactionSchema, string()]), + ), txType: optional(z.enum(RouteQuoteTxType)), warnings: RouteQuoteWarningSchema, }); diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index f46e4a38e7..734fb23df2 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -409,6 +409,10 @@ const errorCodes = { * Garden Plugin */ plugin_garden_missing_data: 42001, + /** + * Garden Plugin + */ + plugin_generic_swap_invalid_data: 43001, /** * SwapKit API */ diff --git a/packages/helpers/src/types/sdk.ts b/packages/helpers/src/types/sdk.ts index 74ae0523d0..9b125b65ff 100644 --- a/packages/helpers/src/types/sdk.ts +++ b/packages/helpers/src/types/sdk.ts @@ -8,7 +8,10 @@ export type GenericSwapParams = { route: T; }; -export type SwapParams = GenericSwapParams & { pluginName?: PluginNames }; +export type SwapParams = GenericSwapParams & { + pluginName?: PluginNames; + useApiTx?: boolean; +}; export enum FeeOption { Average = "average", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index db9af49595..3624a74c6c 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -32,6 +32,11 @@ "require": "./dist/garden/index.cjs", "types": "./dist/types/garden/index.d.ts" }, + "./genericSwap": { + "default": "./dist/genericSwap/index.js", + "require": "./dist/genericSwap/index.cjs", + "types": "./dist/types/genericSwap/index.d.ts" + }, "./near": { "default": "./dist/near/index.js", "require": "./dist/near/index.cjs", diff --git a/packages/plugins/src/genericSwap/index.ts b/packages/plugins/src/genericSwap/index.ts new file mode 100644 index 0000000000..1516cb2600 --- /dev/null +++ b/packages/plugins/src/genericSwap/index.ts @@ -0,0 +1 @@ +export { GenericSwapPlugin } from "./plugin"; diff --git a/packages/plugins/src/genericSwap/plugin.ts b/packages/plugins/src/genericSwap/plugin.ts new file mode 100644 index 0000000000..7dc5819b52 --- /dev/null +++ b/packages/plugins/src/genericSwap/plugin.ts @@ -0,0 +1,98 @@ +import type { ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; +import { AssetValue, Chain, CosmosChains, EVMChains, SwapKitError, type SwapParams } from "@swapkit/helpers"; +import { + CosmosTransactionSchema, + EVMTransactionSchema, + type QuoteResponseRoute, + TronTransactionSchema, +} from "@swapkit/helpers/api"; +import { match, P } from "ts-pattern"; +import { createPlugin } from "../utils"; + +const isEVMTransaction = (tx: unknown) => EVMTransactionSchema.safeParse(tx).success; +const isTronTransaction = (tx: unknown) => TronTransactionSchema.safeParse(tx).success; +const isCosmosTransaction = (tx: unknown) => CosmosTransactionSchema.safeParse(tx).success; + +export const GenericSwapPlugin = createPlugin({ + methods: ({ getWallet }) => ({ + swap: function genericSwapSwap({ route }: SwapParams<"genericSwap", QuoteResponseRoute>) { + const { sellAsset, tx } = route; + const sellAssetValue = AssetValue.from({ asset: sellAsset }); + const chain = sellAssetValue.chain; + + return match({ chain, tx }) + .returnType>() + .with( + { chain: P.union(Chain.Bitcoin, Chain.Dogecoin, Chain.Litecoin, Chain.Dash), tx: P.string }, + async ({ chain, tx }) => { + const { Psbt } = await import("bitcoinjs-lib"); + const wallet = await getWallet(chain); + const psbt = Psbt.fromBase64(tx); + if (chain === Chain.Dogecoin) psbt.setMaximumFeeRate(650000000); + + return wallet.signAndBroadcastTransaction(psbt); + }, + ) + .with({ chain: Chain.BitcoinCash, tx: P.string }, async ({ chain, tx }) => { + const { UtxoPsbt } = await import("@bitgo/utxo-lib/dist/src/bitgo"); + const { networks } = await import("@bitgo/utxo-lib"); + const wallet = await getWallet(chain); + const psbt = UtxoPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.bitcoincash }); + + return wallet.signAndBroadcastTransaction(psbt); + }) + .with({ chain: Chain.Zcash, tx: P.string }, async ({ chain, tx }) => { + const { ZcashPsbt } = await import("@bitgo/utxo-lib/dist/src/bitgo"); + const { networks } = await import("@bitgo/utxo-lib"); + const wallet = await getWallet(chain); + + const psbt = ZcashPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.zcash }); + + return wallet.signAndBroadcastTransaction(psbt as ZcashPsbt); + }) + .with({ chain: P.union(...EVMChains), tx: P.when(isEVMTransaction) }, async ({ chain, tx }) => { + const wallet = await getWallet(chain); + const transaction = EVMTransactionSchema.parse(tx); + + return wallet.sendTransaction({ ...transaction, value: BigInt(transaction.value || "0") }); + }) + .with({ chain: Chain.Solana, tx: P.string }, async ({ chain, tx }) => { + const { VersionedTransaction } = await import("@solana/web3.js"); + const wallet = await getWallet(chain); + + const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); + return wallet.signAndBroadcastTransaction(transaction); + }) + .with({ chain: P.union(...CosmosChains), tx: P.when(isCosmosTransaction) }, async ({ chain, tx }) => { + const wallet = await getWallet(chain); + const transaction = CosmosTransactionSchema.parse(tx); + + return wallet.signAndBroadcastTransaction(transaction); + }) + .with({ chain: Chain.Near, tx: P.string }, async ({ chain, tx }) => { + const { Transaction } = await import("@near-js/transactions"); + const wallet = await getWallet(chain); + + const transaction = Transaction.decode(Buffer.from(tx, "base64")); + + return wallet.signAndBroadcastTransaction(transaction); + }) + .with({ chain: Chain.Ripple, tx: P.string }, async ({ chain, tx }) => { + const wallet = await getWallet(chain); + + return wallet.signAndBroadcastTransaction(JSON.parse(tx)); + }) + .with({ chain: Chain.Tron, tx: P.when(isTronTransaction) }, async ({ chain, tx }) => { + const wallet = await getWallet(chain); + const transaction = TronTransactionSchema.parse(tx); + + return wallet.signAndBroadcastTransaction(transaction); + }) + .otherwise(() => { + throw new SwapKitError("plugin_generic_swap_invalid_data", { chain, tx }); + }); + }, + }), + name: "genericSwap", + properties: { supportedSwapkitProviders: [] }, +}); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 3f94de073e..355edbfc57 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -6,6 +6,7 @@ import { SwapKit } from "@swapkit/core"; import { ChainflipPlugin } from "@swapkit/plugins/chainflip"; import { EVMPlugin } from "@swapkit/plugins/evm"; import { GardenPlugin } from "@swapkit/plugins/garden"; +import { GenericSwapPlugin } from "@swapkit/plugins/genericSwap"; import { NearPlugin } from "@swapkit/plugins/near"; import { RadixPlugin } from "@swapkit/plugins/radix"; import { SolanaPlugin } from "@swapkit/plugins/solana"; @@ -85,6 +86,7 @@ export const defaultPlugins = { ...SolanaPlugin, ...NearPlugin, ...GardenPlugin, + ...GenericSwapPlugin, }; export const defaultWallets = { diff --git a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts index 7844bbc078..4d5f035aa3 100644 --- a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts +++ b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts @@ -21,7 +21,8 @@ import { type TCLikeChain, updateDerivationPath, } from "@swapkit/helpers"; -import { SwapKitApi } from "@swapkit/helpers/api"; +import { type CosmosTransaction, SwapKitApi } from "@swapkit/helpers/api"; +import type { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { match, P } from "ts-pattern"; import type { CosmosToolboxParams } from "../types"; import { @@ -136,6 +137,42 @@ export async function createCosmosToolbox({ chain, ...toolboxParams }: CosmosToo return base64.encode(account?.pubkey); } + async function signTransaction(transaction: CosmosTransaction): Promise { + const from = await getAddress(); + + if (!(signer && from)) { + throw new SwapKitError("toolbox_cosmos_signer_not_defined"); + } + + const signingClient = await createSigningStargateClient(rpcUrl, signer); + + const txRaw = await signingClient.sign(from, transaction.msgs, transaction.fee, transaction.memo, { + accountNumber: transaction.accountNumber, + chainId: transaction.chainId, + sequence: transaction.sequence, + }); + + return txRaw; + } + + async function signAndBroadcastTransaction(transaction: CosmosTransaction) { + const from = await getAddress(); + + if (!(signer && from)) { + throw new SwapKitError("toolbox_cosmos_signer_not_defined"); + } + + const signingClient = await createSigningStargateClient(rpcUrl, signer); + + const result = await signingClient.signAndBroadcast(from, transaction.msgs, transaction.fee, transaction.memo); + + if (result.code !== 0) { + throw new SwapKitError("core_swap_transaction_error", { code: result.code, message: result.rawLog }); + } + + return result.transactionHash; + } + async function transfer({ recipient, assetValue, @@ -207,6 +244,10 @@ export async function createCosmosToolbox({ chain, ...toolboxParams }: CosmosToo importedSigning.DirectSecp256k1Wallet ?? importedSigning.default?.DirectSecp256k1Wallet; return DirectSecp256k1Wallet.fromKey(privateKey, chainPrefix); }, + + signAndBroadcastTransaction, + signer, + signTransaction, transfer, validateAddress: getCosmosValidateAddress(chainPrefix), verifySignature: verifySignature(getAccount), diff --git a/packages/toolboxes/src/cosmos/toolbox/thorchain.ts b/packages/toolboxes/src/cosmos/toolbox/thorchain.ts index 9d288a4024..a618b9c207 100644 --- a/packages/toolboxes/src/cosmos/toolbox/thorchain.ts +++ b/packages/toolboxes/src/cosmos/toolbox/thorchain.ts @@ -242,6 +242,7 @@ export async function createThorchainToolbox({ chain, ...toolboxParams }: Cosmos derivationPath: derivationPathToString(derivationPath), prefix: chainPrefix, }), + signer, signMultisigTx: signMultisigTx(chain), signWithPrivateKey, transfer, diff --git a/packages/toolboxes/src/near/toolbox.ts b/packages/toolboxes/src/near/toolbox.ts index 65a555730d..eacf489123 100644 --- a/packages/toolboxes/src/near/toolbox.ts +++ b/packages/toolboxes/src/near/toolbox.ts @@ -192,6 +192,16 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams): Promise return result.transaction.hash; } + async function signAndBroadcastTransaction(transaction: Transaction) { + try { + const signedTransaction = await signTransaction(transaction); + + return await broadcastTransaction(signedTransaction); + } catch (error) { + throw new SwapKitError("toolbox_near_transfer_failed", { error }); + } + } + async function estimateTransactionFee(params: NearTransferParams | NearGasEstimateParams) { if ("assetValue" in params) { const baseTransferCost = "115123062500"; @@ -364,6 +374,8 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams): Promise nep141, provider, serializeTransaction, + signAndBroadcastTransaction, + signer, signTransaction, transfer, validateAddress: await getValidateNearAddress(), diff --git a/packages/toolboxes/src/near/types.ts b/packages/toolboxes/src/near/types.ts deleted file mode 100644 index e9b00b2562..0000000000 --- a/packages/toolboxes/src/near/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - ChainSigner, - DerivationPathArray, - GenericCreateTransactionParams, - GenericTransferParams, -} from "@swapkit/helpers"; -import type { KeyPairSigner, Signer, transactions } from "near-api-js"; - -interface NearKeyPairSigner - extends KeyPairSigner, - Omit, "signTransaction"> {} - -interface NearGeneralSigner - extends Signer, - Omit, "signTransaction"> {} - -export type NearSigner = NearKeyPairSigner | NearGeneralSigner; - -export type NearToolboxParams = - | { signer?: NearSigner; accountId?: string } - | { phrase?: string; index?: number; derivationPath?: DerivationPathArray }; - -export interface NearTransferParams extends GenericTransferParams {} - -export interface NearConfig { - networkId: "mainnet" | "testnet" | "betanet"; - nodeUrl: string; - walletUrl?: string; - helperUrl?: string; - keyStore?: any; -} - -export interface NearFunctionCallParams { - contractId: string; - methodName: string; - args: Uint8Array | Record; - deposit?: bigint | string | number; - gas?: bigint | string | number; -} - -export interface NearCreateTransactionParams extends Omit { - attachedDeposit?: string; - functionCall?: { methodName: string; args: object; attachedDeposit: string; gas: string; contractId: string }; -} - -export * from "./toolbox"; -export * from "./types/contract"; -export * from "./types/nep141"; -export * from "./types/toolbox"; diff --git a/packages/toolboxes/src/near/types/index.ts b/packages/toolboxes/src/near/types/index.ts new file mode 100644 index 0000000000..d14bed5046 --- /dev/null +++ b/packages/toolboxes/src/near/types/index.ts @@ -0,0 +1,3 @@ +export * from "./contract"; +export * from "./nep141"; +export * from "./toolbox"; diff --git a/packages/toolboxes/src/near/types/toolbox.ts b/packages/toolboxes/src/near/types/toolbox.ts index b5e5d3a064..46a36bb395 100644 --- a/packages/toolboxes/src/near/types/toolbox.ts +++ b/packages/toolboxes/src/near/types/toolbox.ts @@ -1,10 +1,52 @@ -import type { AssetValue, DerivationPathArray } from "@swapkit/helpers"; -import type { Account, Contract, providers } from "near-api-js"; +import type { + AssetValue, + ChainSigner, + DerivationPathArray, + GenericCreateTransactionParams, + GenericTransferParams, +} from "@swapkit/helpers"; +import type { Account, Contract, KeyPairSigner, providers, Signer, transactions } from "near-api-js"; import type { Action, SignedTransaction, Transaction } from "near-api-js/lib/transaction"; import type { NEP141Token } from "../helpers/nep141"; -import type { NearCreateTransactionParams, NearFunctionCallParams, NearSigner, NearTransferParams } from "../types"; import type { NearContractInterface, NearGasEstimateParams } from "../types/contract"; +interface NearKeyPairSigner + extends KeyPairSigner, + Omit, "signTransaction"> {} + +interface NearGeneralSigner + extends Signer, + Omit, "signTransaction"> {} + +export type NearSigner = NearKeyPairSigner | NearGeneralSigner; + +export type NearToolboxParams = + | { signer?: NearSigner; accountId?: string } + | { phrase?: string; index?: number; derivationPath?: DerivationPathArray }; + +export interface NearTransferParams extends GenericTransferParams {} + +export interface NearConfig { + networkId: "mainnet" | "testnet" | "betanet"; + nodeUrl: string; + walletUrl?: string; + helperUrl?: string; + keyStore?: any; +} + +export interface NearFunctionCallParams { + contractId: string; + methodName: string; + args: Uint8Array | Record; + deposit?: bigint | string | number; + gas?: bigint | string | number; +} + +export interface NearCreateTransactionParams extends Omit { + attachedDeposit?: string; + functionCall?: { methodName: string; args: object; attachedDeposit: string; gas: string; contractId: string }; +} + export interface BatchTransaction { receiverId: string; actions: Action[]; @@ -37,6 +79,8 @@ export interface NearToolbox { createContractFunctionCall: (params: ContractFunctionCallParams) => Promise; estimateTransactionFee: (params: NearTransferParams | NearGasEstimateParams) => Promise; broadcastTransaction: (signedTransaction: SignedTransaction) => Promise; + signAndBroadcastTransaction: (transaction: Transaction) => Promise; + signer: NearSigner | undefined; signTransaction: (transaction: Transaction) => Promise; getBalance: (address: string) => Promise; validateAddress: (address: string) => boolean; diff --git a/packages/toolboxes/src/ripple/index.ts b/packages/toolboxes/src/ripple/index.ts index 75c130ef0f..a2952e0a02 100644 --- a/packages/toolboxes/src/ripple/index.ts +++ b/packages/toolboxes/src/ripple/index.ts @@ -149,6 +149,16 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { throw new SwapKitError({ errorKey: "toolbox_ripple_broadcast_error", info: { chain: Chain.Ripple } }); }; + const signAndBroadcastTransaction = async (tx: Transaction) => { + try { + const signedTx = await signTransaction(tx); + + return await broadcastTransaction(signedTx.tx_blob); + } catch (error) { + throw new SwapKitError({ errorKey: "toolbox_ripple_broadcast_error", info: { chain: Chain.Ripple, error } }); + } + }; + const transfer = async (params: GenericTransferParams) => { if (!signer) { throw new SwapKitError({ errorKey: "toolbox_ripple_signer_not_found" }); @@ -163,15 +173,14 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { return { broadcastTransaction, - createSigner, // Expose the helper + createSigner, createTransaction, disconnect, estimateTransactionFee, - // Core methods getAddress, getBalance, - // Signer related - signer, // Expose the signer instance if created/provided + signAndBroadcastTransaction, + signer, signTransaction, transfer, validateAddress: rippleValidateAddress, diff --git a/packages/toolboxes/src/solana/index.ts b/packages/toolboxes/src/solana/index.ts index 3464027692..1ed96609ff 100644 --- a/packages/toolboxes/src/solana/index.ts +++ b/packages/toolboxes/src/solana/index.ts @@ -2,18 +2,6 @@ import type { PublicKey, Transaction, VersionedTransaction } from "@solana/web3. import type { GenericCreateTransactionParams, GenericTransferParams } from "@swapkit/helpers"; import type { getSolanaToolbox } from "./toolbox"; -// type DisplayEncoding = "utf8" | "hex"; - -// type PhantomRequestMethod = -// | "connect" -// | "disconnect" -// | "signAndSendTransaction" -// | "signAndSendTransactionV0" -// | "signAndSendTransactionV0WithLookupTable" -// | "signTransaction" -// | "signAllTransactions" -// | "signMessage"; - interface ConnectOpts { onlyIfTrusted: boolean; } diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index 94ae95dd40..99133543a5 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -131,6 +131,11 @@ export async function getSolanaToolbox( getBalance, getConnection, getPubkeyFromAddress, + signAndBroadcastTransaction: async (transaction: Transaction | VersionedTransaction) => { + const signedTx = await signTransaction(getConnection, signer)(transaction); + return broadcastTransaction(getConnection)(signedTx); + }, + signer, signTransaction: signTransaction(getConnection, signer), transfer: transfer(getConnection, signer), }; @@ -348,10 +353,10 @@ function broadcastTransaction(getConnection: () => Promise) { function signTransaction(getConnection: () => Promise, signer?: SolanaSigner) { return async (transaction: Transaction | VersionedTransaction) => { - const { VersionedTransaction } = await import("@solana/web3.js"); if (!signer) { throw new SwapKitError("toolbox_solana_no_signer"); } + const { VersionedTransaction } = await import("@solana/web3.js"); if (!(transaction instanceof VersionedTransaction)) { const connection = await getConnection(); diff --git a/packages/toolboxes/src/tron/toolbox.ts b/packages/toolboxes/src/tron/toolbox.ts index 0abc3d514b..8ff9f59419 100644 --- a/packages/toolboxes/src/tron/toolbox.ts +++ b/packages/toolboxes/src/tron/toolbox.ts @@ -117,6 +117,8 @@ export const createTronToolbox = async ( transfer: (params: TronTransferParams) => Promise; estimateTransactionFee: (params: TronTransferParams & { sender?: string }) => Promise; createTransaction: (params: TronCreateTransactionParams) => Promise; + signAndBroadcastTransaction: (transaction: TronTransaction) => Promise; + signer: TronSigner | undefined; signTransaction: (transaction: TronTransaction) => Promise; broadcastTransaction: (signedTransaction: TronSignedTransaction) => Promise; approve: (params: TronApproveParams) => Promise; @@ -510,6 +512,11 @@ export const createTronToolbox = async ( return txid; }; + const signAndBroadcastTransaction = async (transaction: TronTransaction) => { + const signedTx = await signTransaction(transaction); + return await broadcastTransaction(signedTx); + }; + const getApprovedAmount = async ({ assetAddress, spenderAddress, from }: TronApprovedParams) => { try { const contract = tronWeb.contract(trc20ABI, assetAddress); @@ -583,6 +590,8 @@ export const createTronToolbox = async ( getApprovedAmount, getBalance, isApproved, + signAndBroadcastTransaction, + signer, signTransaction, transfer, tronWeb, diff --git a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts index ce2eaf6508..3735484656 100644 --- a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts +++ b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts @@ -1,9 +1,5 @@ -import { - address as bchAddress, - Transaction, - TransactionBuilder, - // @ts-expect-error -} from "@psf/bitcoincashjs-lib"; +import { bitgo, networks } from "@bitgo/utxo-lib"; +import type { UtxoPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; import { Chain, type ChainSigner, @@ -14,45 +10,38 @@ import { SwapKitError, updateDerivationPath, } from "@swapkit/helpers"; -import { Psbt } from "bitcoinjs-lib"; -import { accumulative, compileMemo, getUtxoApi, getUtxoNetwork, toCashAddress, toLegacyAddress } from "../helpers"; -import type { - BchECPair, - TargetOutput, - TransactionBuilderType, - TransactionType, - UTXOBuildTxParams, - UTXOTransferParams, - UTXOType, -} from "../types"; +import { accumulative, compileMemo, getUtxoApi, toCashAddress, toLegacyAddress } from "../helpers"; +import type { TargetOutput, UTXOBuildTxParams, UTXOTransferParams, UTXOType } from "../types"; import type { UtxoToolboxParams } from "./index"; -import { createUTXOToolbox, getCreateKeysForPath } from "./utxo"; +import { addressFromKeysGetter, createUTXOToolbox, getCreateKeysForPath } from "./utxo"; import { bchValidateAddress, stripPrefix } from "./validators"; +type Psbt = UtxoPsbt; + const chain = Chain.BitcoinCash; +const network = networks.bitcoincash; export function stripToCashAddress(address: string) { return stripPrefix(toCashAddress(address)); } -function createSignerWithKeys(keys: BchECPair) { - function signTransaction({ builder, utxos }: { builder: TransactionBuilderType; utxos: UTXOType[] }) { - utxos.forEach((utxo, index) => { - builder.sign(index, keys, undefined, 0x41, utxo.witnessUtxo?.value); - }); +async function createSignerWithKeys({ phrase, derivationPath }: { phrase: string; derivationPath: string }) { + const keyPair = (await getCreateKeysForPath(chain))({ derivationPath, phrase }); - return builder.build(); + async function signTransaction(psbt: Psbt) { + await psbt.signAllInputs(keyPair); + return psbt; } - const getAddress = () => { - const address = keys.getAddress(0); - return Promise.resolve(stripToCashAddress(address)); - }; + async function getAddress() { + const addressGetter = await addressFromKeysGetter(chain); + return addressGetter(keyPair); + } return { getAddress, signTransaction }; } -export async function createBCHToolbox( +export async function createBCHToolbox( toolboxParams: UtxoToolboxParams[T] | { phrase?: string; derivationPath?: DerivationPathArray; index?: number }, ) { const phrase = "phrase" in toolboxParams ? toolboxParams.phrase : undefined; @@ -65,9 +54,11 @@ export async function createBCHToolbox( : updateDerivationPath(NetworkDerivationPath[chain], { index }), ); - const keys = phrase ? (await getCreateKeysForPath(chain))({ derivationPath, phrase }) : undefined; - - const signer = keys ? createSignerWithKeys(keys) : "signer" in toolboxParams ? toolboxParams.signer : undefined; + const signer = phrase + ? await createSignerWithKeys({ derivationPath, phrase }) + : "signer" in toolboxParams + ? toolboxParams.signer + : undefined; function getAddress() { return Promise.resolve(signer?.getAddress()); @@ -79,15 +70,31 @@ export async function createBCHToolbox( return getBalance(stripPrefix(toCashAddress(address))); } + async function signTransaction(psbt: Psbt) { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction(psbt); + return signedTx; + } + + async function signAndBroadcastTransaction(psbt: Psbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction(psbt); + const txHex = signedTx.toHex(); + return broadcastTx(txHex); + } + return { ...toolbox, broadcastTx, - buildTx, + // buildTx, createTransaction, getAddress, getAddressFromKeys, getBalance: handleGetBalance, getFeeRates, + signAndBroadcastTransaction, + signer, + signTransaction, stripPrefix, stripToCashAddress, transfer: transfer({ broadcastTx, getFeeRates, signer }), @@ -95,54 +102,6 @@ export async function createBCHToolbox( }; } -async function createTransaction({ assetValue, recipient, memo, feeRate, sender }: UTXOBuildTxParams) { - if (!bchValidateAddress(recipient)) throw new SwapKitError("toolbox_utxo_invalid_address", { address: recipient }); - - // Overestimate by 7500 byte * feeRate to ensure we have enough UTXOs for fees and change - const targetValue = Math.ceil(assetValue.getBaseValue("number") + feeRate * 7500); - - const utxos = await getUtxoApi(chain).getUtxos({ - address: stripToCashAddress(sender), - fetchTxHex: true, - targetValue, - }); - - const compiledMemo = memo ? await compileMemo(memo) : null; - - const targetOutputs: TargetOutput[] = []; - // output to recipient - targetOutputs.push({ address: recipient, value: assetValue.getBaseValue("number") }); - const { inputs, outputs } = accumulative({ chain, feeRate, inputs: utxos, outputs: targetOutputs }); - - // .inputs and .outputs will be undefined if no solution was found - if (!(inputs && outputs)) throw new SwapKitError("toolbox_utxo_insufficient_balance", { assetValue, sender }); - const getNetwork = await getUtxoNetwork(); - const builder = new TransactionBuilder(getNetwork(chain)) as TransactionBuilderType; - - await Promise.all( - inputs.map(async (utxo: UTXOType) => { - const txHex = await getUtxoApi(chain).getRawTx(utxo.hash); - - builder.addInput(Transaction.fromBuffer(Buffer.from(txHex, "hex")), utxo.index); - }), - ); - - for (const output of outputs) { - const address = "address" in output && output.address ? output.address : toLegacyAddress(sender); - const getNetwork = await getUtxoNetwork(); - const outputScript = bchAddress.toOutputScript(toLegacyAddress(address), getNetwork(chain)); - - builder.addOutput(outputScript, output.value); - } - - // add output for memo - if (compiledMemo) { - builder.addOutput(compiledMemo, 0); // Add OP_RETURN {script, value} - } - - return { builder, utxos: inputs }; -} - function transfer({ broadcastTx, getFeeRates, @@ -150,7 +109,7 @@ function transfer({ }: { broadcastTx: (txHash: string) => Promise; getFeeRates: () => Promise>; - signer?: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType>; + signer?: ChainSigner; }) { return async function transfer({ recipient, @@ -166,68 +125,68 @@ function transfer({ const feeRate = rest.feeRate || (await getFeeRates())[feeOptionKey]; // try out if psbt tx is faster/better/nicer - const { builder, utxos } = await createTransaction({ ...rest, assetValue, feeRate, recipient, sender: from }); + const { psbt } = await createTransaction({ ...rest, assetValue, feeRate, recipient, sender: from }); - const tx = await signer.signTransaction({ builder, utxos }); + const tx = await signer.signTransaction(psbt); const txHex = tx.toHex(); return broadcastTx(txHex); }; } -async function buildTx({ +async function createTransaction({ assetValue, recipient, memo, feeRate, sender, - setSigHashType, + setSigHashType = true, }: UTXOBuildTxParams & { setSigHashType?: boolean }) { const recipientCashAddress = toCashAddress(recipient); if (!bchValidateAddress(recipientCashAddress)) throw new SwapKitError("toolbox_utxo_invalid_address", { address: recipientCashAddress }); - // Overestimate by 7500 byte * feeRate to ensure we have enough UTXOs for fees and change const targetValue = Math.ceil(assetValue.getBaseValue("number") + feeRate * 7500); const utxos = await getUtxoApi(chain).getUtxos({ address: stripToCashAddress(sender), - fetchTxHex: false, + // Correctly fetch txHex for nonWitnessUtxo + fetchTxHex: true, targetValue, }); const feeRateWhole = Number(feeRate.toFixed(0)); const compiledMemo = memo ? await compileMemo(memo) : null; - const targetOutputs = [] as TargetOutput[]; - // output to recipient targetOutputs.push({ address: toLegacyAddress(recipient), value: assetValue.getBaseValue("number") }); - //2. add output memo to targets (optional) if (compiledMemo) { targetOutputs.push({ script: compiledMemo, value: 0 }); } const { inputs, outputs } = accumulative({ chain, feeRate: feeRateWhole, inputs: utxos, outputs: targetOutputs }); - // .inputs and .outputs will be undefined if no solution was found if (!(inputs && outputs)) throw new SwapKitError("toolbox_utxo_insufficient_balance", { assetValue, sender }); - const getNetwork = await getUtxoNetwork(); - const psbt = new Psbt({ network: getNetwork(chain) }); // Network-specific + + const psbt = new bitgo.UtxoPsbt({ network }); // Network-specific for (const { hash, index, witnessUtxo } of inputs) { - psbt.addInput({ hash, index, sighashType: setSigHashType ? 0x41 : undefined, witnessUtxo }); + psbt.addInput({ + hash, + index, + sighashType: setSigHashType + ? bitgo.UtxoTransaction.SIGHASH_ALL | bitgo.UtxoTransaction.SIGHASH_FORKID + : undefined, + ...(witnessUtxo && { witnessUtxo: { ...witnessUtxo, value: BigInt(witnessUtxo?.value) } }), + }); } - // Outputs for (const output of outputs) { - const address = "address" in output && output.address ? output.address : toLegacyAddress(sender); + const outAddress = "address" in output && output.address ? output.address : toLegacyAddress(sender); const params = output.script - ? compiledMemo - ? { script: compiledMemo, value: 0 } - : undefined - : { address, value: output.value }; + ? { script: output.script, value: 0n } + : { address: outAddress, value: BigInt(output.value) }; if (params) { psbt.addOutput(params); diff --git a/packages/toolboxes/src/utxo/toolbox/index.ts b/packages/toolboxes/src/utxo/toolbox/index.ts index fb97b3cef3..efddf096b4 100644 --- a/packages/toolboxes/src/utxo/toolbox/index.ts +++ b/packages/toolboxes/src/utxo/toolbox/index.ts @@ -1,7 +1,6 @@ -import type { ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; +import type { UtxoPsbt, ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; import { Chain, type ChainSigner, type DerivationPathArray, SwapKitError, type UTXOChain } from "@swapkit/helpers"; import type { Psbt } from "bitcoinjs-lib"; -import type { TransactionBuilderType, TransactionType, UTXOType } from "../types"; import { createBCHToolbox } from "./bitcoinCash"; import { createUTXOToolbox } from "./utxo"; import { createZcashToolbox } from "./zcash"; @@ -26,7 +25,7 @@ export type UTXOWallets = { }; export type UtxoToolboxParams = { - [Chain.BitcoinCash]: { signer: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType> }; + [Chain.BitcoinCash]: { signer: ChainSigner }; [Chain.Bitcoin]: { signer: ChainSigner }; [Chain.Dogecoin]: { signer: ChainSigner }; [Chain.Litecoin]: { signer: ChainSigner }; diff --git a/packages/toolboxes/src/utxo/toolbox/utxo.ts b/packages/toolboxes/src/utxo/toolbox/utxo.ts index e66ee1ef8c..809cb4c511 100644 --- a/packages/toolboxes/src/utxo/toolbox/utxo.ts +++ b/packages/toolboxes/src/utxo/toolbox/utxo.ts @@ -1,6 +1,4 @@ import secp256k1 from "@bitcoinerlab/secp256k1"; -// @ts-expect-error -import { ECPair, HDNode } from "@psf/bitcoincashjs-lib"; import { HDKey } from "@scure/bip32"; import { mnemonicToSeedSync } from "@scure/bip39"; import { @@ -8,7 +6,6 @@ import { applyFeeMultiplier, Chain, type ChainSigner, - DerivationPath, type DerivationPathArray, derivationPathToString, FeeOption, @@ -162,6 +159,40 @@ async function createSignerWithKeys({ return { getAddress, signTransaction }; } +function getSignTransaction({ chain, signer }: { chain: UTXOChain; signer?: ChainSigner }) { + return async function signTransaction(psbt: Psbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + // Check if this is a standard PSBT signer (not BCH) + if (chain !== Chain.BitcoinCash) { + const signedPsbt = await signer.signTransaction(psbt); + return signedPsbt; + } + // BCH uses a different transaction type, so we can't support PSBT signing + throw new SwapKitError("toolbox_utxo_invalid_params", { + chain, + error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", + }); + }; +} + +function getSignAndBroadcastTransaction({ chain, signer }: { chain: UTXOChain; signer?: ChainSigner }) { + return async function signAndBroadcastTransaction(psbt: Psbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + // Check if this is a standard PSBT signer (not BCH) + if (chain !== Chain.BitcoinCash) { + const signedPsbt = await signer.signTransaction(psbt); + signedPsbt.finalizeAllInputs(); + const txHex = signedPsbt.extractTransaction().toHex(); + return getUtxoApi(chain).broadcastTx(txHex); + } + // BCH uses a different transaction type, so we can't support PSBT signing + throw new SwapKitError("toolbox_utxo_invalid_params", { + chain, + error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", + }); + }; +} + export async function createUTXOToolbox({ chain, ...toolboxParams @@ -208,6 +239,9 @@ export async function createUTXOToolbox({ const keys = createKeysForPath(params); return keys.toWIF(); }, + signAndBroadcastTransaction: getSignAndBroadcastTransaction({ chain, signer: signer as ChainSigner }), + signer, + signTransaction: getSignTransaction({ chain, signer: signer as ChainSigner }), transfer: transfer(signer as UtxoToolboxParams["BTC"]["signer"]), validateAddress: (address: string) => validateAddress({ address, chain }), }; @@ -295,7 +329,7 @@ function estimateTransactionFee(chain: UTXOChain) { } type CreateKeysForPathReturnType = { - [Chain.BitcoinCash]: BchECPair; + [Chain.BitcoinCash]: ECPairInterface; [Chain.Bitcoin]: ECPairInterface; [Chain.Dash]: ECPairInterface; [Chain.Dogecoin]: ECPairInterface; @@ -309,30 +343,8 @@ export async function getCreateKeysForPath CreateKeysForPathReturnType[T]; - } case Chain.Bitcoin: + case Chain.BitcoinCash: case Chain.Dogecoin: case Chain.Litecoin: case Chain.Zcash: diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index bc413d0091..e6363cdcf0 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -234,11 +234,32 @@ export async function createZcashToolbox( return keys.toWIF(); } + function getSignTransaction(signer?: ZcashSigner) { + return async function signTransaction(psbt: ZcashPsbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + return signedPsbt; + }; + } + + function getSignAndBroadcastTransaction(signer?: ZcashSigner) { + return async function signAndBroadcastTransaction(psbt: ZcashPsbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + signedPsbt.finalizeAllInputs(); + const txHex = signedPsbt.extractTransaction().toHex(); + return baseToolbox.broadcastTx(txHex); + }; + } + return { ...baseToolbox, createKeysForPath, createTransaction, getPrivateKeyFromMnemonic, + signAndBroadcastTransaction: getSignAndBroadcastTransaction(signer), + signer, + signTransaction: getSignTransaction(signer), transfer, validateAddress: validateZcashAddress, }; diff --git a/packages/wallet-hardware/src/keepkey/chains/utxo.ts b/packages/wallet-hardware/src/keepkey/chains/utxo.ts index 0108729bc1..b829d55f1c 100644 --- a/packages/wallet-hardware/src/keepkey/chains/utxo.ts +++ b/packages/wallet-hardware/src/keepkey/chains/utxo.ts @@ -100,10 +100,7 @@ export const utxoWalletMethods = async ({ if (!recipient) throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Recipient address must be provided" }); - const createTxMethod = - chain === Chain.BitcoinCash - ? (toolbox as UTXOToolboxes["BCH"]).buildTx - : (toolbox as UTXOToolboxes["BTC"]).createTransaction; + const createTxMethod = (toolbox as UTXOToolboxes["BTC"]).createTransaction; const { psbt, inputs: rawInputs } = await createTxMethod({ ...rest, diff --git a/packages/wallet-hardware/src/trezor/index.ts b/packages/wallet-hardware/src/trezor/index.ts index 81130f6b7a..118555fa70 100644 --- a/packages/wallet-hardware/src/trezor/index.ts +++ b/packages/wallet-hardware/src/trezor/index.ts @@ -263,10 +263,7 @@ async function getTrezorWallet({ const feeRate = paramFeeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast]; - const createTxMethod = - chain === Chain.BitcoinCash - ? (toolbox as UTXOToolboxes["BCH"]).buildTx - : (toolbox as UTXOToolboxes["BTC"]).createTransaction; + const createTxMethod = (toolbox as UTXOToolboxes["BTC"]).createTransaction; const { psbt, inputs } = await createTxMethod({ ...rest, diff --git a/playgrounds/vite/src/Swap/index.tsx b/playgrounds/vite/src/Swap/index.tsx index 38821259f4..7495d60e13 100644 --- a/playgrounds/vite/src/Swap/index.tsx +++ b/playgrounds/vite/src/Swap/index.tsx @@ -23,6 +23,7 @@ export default function Swap({ const txHash = await skClient.swap({ feeOptionKey: FeeOption.Fast, route, + // useApiTx: true, // enable this to use the API transaction instead of the plugin transactionF ...(isChainflipBoost ? { maxBoostFeeBps: 10 } : {}), }); From caaa9c25df5242f0b2f751b23064702424edc856 Mon Sep 17 00:00:00 2001 From: towan Date: Fri, 10 Oct 2025 14:51:14 +0400 Subject: [PATCH 02/10] chore: correct quoteresponserouteitem --- packages/helpers/src/api/swapkitApi/types.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/helpers/src/api/swapkitApi/types.ts b/packages/helpers/src/api/swapkitApi/types.ts index 7a666aeed5..8bc8d6534e 100644 --- a/packages/helpers/src/api/swapkitApi/types.ts +++ b/packages/helpers/src/api/swapkitApi/types.ts @@ -446,20 +446,6 @@ export const EVMTransactionSchema = object({ export type EVMTransaction = z.infer; -export const NEARTransactionSchema = z.object({ - details: z.object({ - blockHash: z.string().describe("Hash of the block"), - nonce: z.number().describe("Nonce of the transaction"), - signerId: z.string().describe("ID of the signer"), - }), - gas: z.string().describe("Gas limit for the transaction"), - gasPrice: z.string().describe("Gas price for the transaction"), - publicKey: z.string().describe("Public key of the signer"), - serialized: z.string().describe("Serialized transaction"), -}); - -export type NEARTransaction = z.infer; - export const TronTransactionSchema = z.object({ raw_data: z.object({ contract: z.any(), @@ -629,9 +615,7 @@ const QuoteResponseRouteItem = object({ sourceAddress: string().describe("Source address"), targetAddress: optional(string().describe("Target address")), totalSlippageBps: number().describe("Total slippage in bps"), - tx: optional( - union([EVMTransactionSchema, CosmosTransactionSchema, NEARTransactionSchema, TronTransactionSchema, string()]), - ), + tx: optional(union([EVMTransactionSchema, CosmosTransactionSchema, TronTransactionSchema, string()])), txType: optional(z.enum(RouteQuoteTxType)), warnings: RouteQuoteWarningSchema, }); From ac9823fc5860bbac20303f5009d81aa3e91e5ff6 Mon Sep 17 00:00:00 2001 From: towan Date: Fri, 10 Oct 2025 14:55:51 +0400 Subject: [PATCH 03/10] chore: clean up --- packages/core/src/index.ts | 2 +- packages/helpers/src/modules/swapKitError.ts | 2 +- packages/plugins/package.json | 10 +++++----- packages/plugins/src/genericSwap/index.ts | 1 - packages/plugins/src/swap/index.ts | 1 + packages/plugins/src/{genericSwap => swap}/plugin.ts | 6 +++--- packages/sdk/src/index.ts | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 packages/plugins/src/genericSwap/index.ts create mode 100644 packages/plugins/src/swap/index.ts rename packages/plugins/src/{genericSwap => swap}/plugin.ts (96%) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ed7bf37af9..b0b8a1d7fa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -207,7 +207,7 @@ export function SwapKit< // only keystore supports straight signing of all chains if (useApiTx && getWallet(fromChain)?.walletType === WalletOption.KEYSTORE) { - const plugin = getSwapKitPlugin("genericSwap"); + const plugin = getSwapKitPlugin("swap"); if ("swap" in plugin) { // @ts-expect-error TODO: fix this return plugin.swap({ ...rest, route }); diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index 734fb23df2..f474728bb7 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -410,7 +410,7 @@ const errorCodes = { */ plugin_garden_missing_data: 42001, /** - * Garden Plugin + * Swap Plugin */ plugin_generic_swap_invalid_data: 43001, /** diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 3624a74c6c..46014ce1b4 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -32,11 +32,6 @@ "require": "./dist/garden/index.cjs", "types": "./dist/types/garden/index.d.ts" }, - "./genericSwap": { - "default": "./dist/genericSwap/index.js", - "require": "./dist/genericSwap/index.cjs", - "types": "./dist/types/genericSwap/index.d.ts" - }, "./near": { "default": "./dist/near/index.js", "require": "./dist/near/index.cjs", @@ -52,6 +47,11 @@ "require": "./dist/solana/index.cjs", "types": "./dist/types/solana/index.d.ts" }, + "./swap": { + "default": "./dist/swap/index.js", + "require": "./dist/swap/index.cjs", + "types": "./dist/types/swap/index.d.ts" + }, "./thorchain": { "default": "./dist/thorchain/index.js", "require": "./dist/thorchain/index.cjs", diff --git a/packages/plugins/src/genericSwap/index.ts b/packages/plugins/src/genericSwap/index.ts deleted file mode 100644 index 1516cb2600..0000000000 --- a/packages/plugins/src/genericSwap/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { GenericSwapPlugin } from "./plugin"; diff --git a/packages/plugins/src/swap/index.ts b/packages/plugins/src/swap/index.ts new file mode 100644 index 0000000000..92062a29be --- /dev/null +++ b/packages/plugins/src/swap/index.ts @@ -0,0 +1 @@ +export { SwapPlugin } from "./plugin"; diff --git a/packages/plugins/src/genericSwap/plugin.ts b/packages/plugins/src/swap/plugin.ts similarity index 96% rename from packages/plugins/src/genericSwap/plugin.ts rename to packages/plugins/src/swap/plugin.ts index 7dc5819b52..99362008c5 100644 --- a/packages/plugins/src/genericSwap/plugin.ts +++ b/packages/plugins/src/swap/plugin.ts @@ -13,9 +13,9 @@ const isEVMTransaction = (tx: unknown) => EVMTransactionSchema.safeParse(tx).suc const isTronTransaction = (tx: unknown) => TronTransactionSchema.safeParse(tx).success; const isCosmosTransaction = (tx: unknown) => CosmosTransactionSchema.safeParse(tx).success; -export const GenericSwapPlugin = createPlugin({ +export const SwapPlugin = createPlugin({ methods: ({ getWallet }) => ({ - swap: function genericSwapSwap({ route }: SwapParams<"genericSwap", QuoteResponseRoute>) { + swap: function swap({ route }: SwapParams<"swap", QuoteResponseRoute>) { const { sellAsset, tx } = route; const sellAssetValue = AssetValue.from({ asset: sellAsset }); const chain = sellAssetValue.chain; @@ -93,6 +93,6 @@ export const GenericSwapPlugin = createPlugin({ }); }, }), - name: "genericSwap", + name: "swap", properties: { supportedSwapkitProviders: [] }, }); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 355edbfc57..c6200444a4 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -6,10 +6,10 @@ import { SwapKit } from "@swapkit/core"; import { ChainflipPlugin } from "@swapkit/plugins/chainflip"; import { EVMPlugin } from "@swapkit/plugins/evm"; import { GardenPlugin } from "@swapkit/plugins/garden"; -import { GenericSwapPlugin } from "@swapkit/plugins/genericSwap"; import { NearPlugin } from "@swapkit/plugins/near"; import { RadixPlugin } from "@swapkit/plugins/radix"; import { SolanaPlugin } from "@swapkit/plugins/solana"; +import { SwapPlugin } from "@swapkit/plugins/swap"; import { MayachainPlugin, ThorchainPlugin } from "@swapkit/plugins/thorchain"; import { bitgetWallet } from "@swapkit/wallets/bitget"; @@ -86,7 +86,7 @@ export const defaultPlugins = { ...SolanaPlugin, ...NearPlugin, ...GardenPlugin, - ...GenericSwapPlugin, + ...SwapPlugin, }; export const defaultWallets = { From f32cf05ad10a0b011249ba67bcaa049b931dc5ea Mon Sep 17 00:00:00 2001 From: towan Date: Tue, 21 Oct 2025 16:35:21 +0400 Subject: [PATCH 04/10] chore: move nightly changes into branch --- .github/workflows/release.yml | 4 ---- packages/plugins/package.json | 36 +++++++++++++++++------------------ packages/plugins/src/index.ts | 4 ++++ packages/plugins/src/types.ts | 4 +++- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 757e5e4877..86cef01c1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,10 +27,6 @@ jobs: - run: bun lint - run: bun type-check:ci - - name: Test - if: github.ref_name == 'nightly' - run: bun test:ci - publish: needs: [build-lint-test] runs-on: ubuntu-latest diff --git a/packages/plugins/package.json b/packages/plugins/package.json index aa44a9544a..f2d5010633 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -16,45 +16,45 @@ "ts-pattern": "5.8.0" }, "exports": { - ".": { "default": "./dist/index.js", "require": "./dist/index.cjs", "types": "./dist/types/index.d.ts" }, + ".": { "default": "./dist/src/index.js", "require": "./dist/src/index.cjs", "types": "./dist/types/index.d.ts" }, "./chainflip": { - "default": "./dist/chainflip/index.js", - "require": "./dist/chainflip/index.cjs", + "default": "./dist/src/chainflip/index.js", + "require": "./dist/src/chainflip/index.cjs", "types": "./dist/types/chainflip/index.d.ts" }, "./evm": { - "default": "./dist/evm/index.js", - "require": "./dist/evm/index.cjs", + "default": "./dist/src/evm/index.js", + "require": "./dist/src/evm/index.cjs", "types": "./dist/types/evm/index.d.ts" }, "./garden": { - "default": "./dist/garden/index.js", - "require": "./dist/garden/index.cjs", + "default": "./dist/src/garden/index.js", + "require": "./dist/src/garden/index.cjs", "types": "./dist/types/garden/index.d.ts" }, "./near": { - "default": "./dist/near/index.js", - "require": "./dist/near/index.cjs", + "default": "./dist/src/near/index.js", + "require": "./dist/src/near/index.cjs", "types": "./dist/types/near/index.d.ts" }, "./radix": { - "default": "./dist/radix/index.js", - "require": "./dist/radix/index.cjs", + "default": "./dist/src/radix/index.js", + "require": "./dist/src/radix/index.cjs", "types": "./dist/types/radix/index.d.ts" }, "./solana": { - "default": "./dist/solana/index.js", - "require": "./dist/solana/index.cjs", + "default": "./dist/src/solana/index.js", + "require": "./dist/src/solana/index.cjs", "types": "./dist/types/solana/index.d.ts" }, "./swap": { - "default": "./dist/swap/index.js", - "require": "./dist/swap/index.cjs", + "default": "./dist/src/swap/index.js", + "require": "./dist/src/swap/index.cjs", "types": "./dist/types/swap/index.d.ts" }, "./thorchain": { - "default": "./dist/thorchain/index.js", - "require": "./dist/thorchain/index.cjs", + "default": "./dist/src/thorchain/index.js", + "require": "./dist/src/thorchain/index.cjs", "types": "./dist/types/thorchain/index.d.ts" } }, @@ -71,5 +71,5 @@ "type-check:go": "tsgo" }, "type": "module", - "version": "4.1.10" + "version": "4.1.6" } diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index 9f4b6d50d0..e00da3e04b 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -27,6 +27,10 @@ export async function loadPlugin

(pluginName: P) { const { SolanaPlugin } = await import("./solana"); return SolanaPlugin; }) + .with("swap", async () => { + const { SwapPlugin } = await import("./swap"); + return SwapPlugin; + }) .with("near", async () => { const { NearPlugin } = await import("./near"); return NearPlugin; diff --git a/packages/plugins/src/types.ts b/packages/plugins/src/types.ts index 5266abfdc5..24e3998141 100644 --- a/packages/plugins/src/types.ts +++ b/packages/plugins/src/types.ts @@ -5,6 +5,7 @@ import type { EVMPlugin } from "./evm"; import type { NearPlugin } from "./near"; import type { RadixPlugin } from "./radix"; import type { SolanaPlugin } from "./solana/plugin"; +import type { SwapPlugin } from "./swap"; import type { ThorchainPlugin } from "./thorchain"; export type * from "./chainflip/types"; @@ -15,7 +16,8 @@ export type SKPlugins = typeof ChainflipPlugin & typeof RadixPlugin & typeof SolanaPlugin & typeof EVMPlugin & - typeof NearPlugin; + typeof NearPlugin & + typeof SwapPlugin; export type PluginName = keyof SKPlugins; From 1f1e59e3cd569b469b6490b40b1062ae99cbbc76 Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 22 Oct 2025 17:28:35 +0400 Subject: [PATCH 05/10] chore: uses approve endpoint for plugins --- bun.lock | 2 +- .../helpers/src/api/swapkitApi/endpoints.ts | 24 +++++- packages/helpers/src/api/swapkitApi/types.ts | 32 ++++++++ packages/plugins/src/near/plugin.ts | 2 +- packages/plugins/src/swap/plugin.ts | 14 +++- packages/plugins/src/utils.ts | 80 +++++++++++++------ 6 files changed, 125 insertions(+), 29 deletions(-) diff --git a/bun.lock b/bun.lock index f68f410252..237d9b417e 100644 --- a/bun.lock +++ b/bun.lock @@ -84,7 +84,7 @@ }, "packages/plugins": { "name": "@swapkit/plugins", - "version": "4.1.10", + "version": "4.1.6", "dependencies": { "@polkadot/keyring": "~13.5.0", "@polkadot/util": "~13.5.0", diff --git a/packages/helpers/src/api/swapkitApi/endpoints.ts b/packages/helpers/src/api/swapkitApi/endpoints.ts index 943cca7f1a..7eee99f32d 100644 --- a/packages/helpers/src/api/swapkitApi/endpoints.ts +++ b/packages/helpers/src/api/swapkitApi/endpoints.ts @@ -1,4 +1,5 @@ import { + AssetValue, type Chain, type EVMChain, EVMChains, @@ -8,8 +9,9 @@ import { SKConfig, SwapKitError, } from "@swapkit/helpers"; - +import { match, P } from "ts-pattern"; import { + type ApproveResponse, type BalanceResponse, type BrokerDepositChannelParams, type DepositChannelResponse, @@ -93,6 +95,26 @@ export async function getChainBalance({ return scamFilter ? filterAssets(balances) : balances; } +export function getTokenApproval( + params: { spender: string; userWallet: string; assetValue: AssetValue } | { routeId: string }, +) { + return match(params) + .with({ routeId: P.string }, ({ routeId }) => { + const url = getApiUrl(`/approve?routeId=${routeId}`); + return SKRequestClient.get(url); + }) + .with( + { assetValue: P.instanceOf(AssetValue), spender: P.string, userWallet: P.string }, + ({ spender, userWallet, assetValue }) => { + const url = getApiUrl( + `/approve?tokenIdentifier=${assetValue.toString()}&userWalletAddress=${userWallet}&spender=${spender}&amount=${assetValue.getValue("string")}`, + ); + return SKRequestClient.get(url); + }, + ) + .exhaustive(); +} + export function getTokenListProviders() { const url = getApiUrl("/providers"); return SKRequestClient.get(url); diff --git a/packages/helpers/src/api/swapkitApi/types.ts b/packages/helpers/src/api/swapkitApi/types.ts index 8bc8d6534e..f2b103533f 100644 --- a/packages/helpers/src/api/swapkitApi/types.ts +++ b/packages/helpers/src/api/swapkitApi/types.ts @@ -610,6 +610,7 @@ const QuoteResponseRouteItem = object({ memo: optional(string().describe("Memo")), meta: RouteQuoteMetadataV2Schema, providers: array(z.enum(ProviderName)), + routeId: string().describe("Route ID"), sellAmount: string().describe("Sell amount"), sellAsset: string().describe("Asset to sell"), sourceAddress: string().describe("Source address"), @@ -665,3 +666,34 @@ const BalanceResponseSchema = array( ); export type BalanceResponse = z.infer; + +export const ApproveRequestParams = z.union([ + z.object({ amount: z.string(), spender: z.string(), tokenIdentifier: z.string(), userWalletAddress: z.string() }), + z.object({ + amount: z.string(), + chainId: z.enum(ChainId), + spender: z.string(), + tokenContractAddress: z.string(), + userWalletAddress: z.string(), + }), + z.object({ routeId: z.string() }), +]); + +export type ApproveRequest = z.infer; + +export const ApproveResponseSchema = z.object({ + approvalTransaction: z.optional( + z.object({ + data: z.string(), + from: z.string(), + gasLimit: z.optional(z.string()), + gasPrice: z.optional(z.string()), + to: z.string(), + value: z.string(), + }), + ), + approvedAmount: z.string(), + isApproved: z.boolean(), +}); + +export type ApproveResponse = z.infer; diff --git a/packages/plugins/src/near/plugin.ts b/packages/plugins/src/near/plugin.ts index a11d9cdc4e..ab9347cb25 100644 --- a/packages/plugins/src/near/plugin.ts +++ b/packages/plugins/src/near/plugin.ts @@ -194,7 +194,7 @@ export const NearPlugin = createPlugin({ sender: wallet.address, }); - return wallet.signAndSendTransaction(unsignedTransaction); + return wallet.signAndBroadcastTransaction(unsignedTransaction); } if (!wallet) { diff --git a/packages/plugins/src/swap/plugin.ts b/packages/plugins/src/swap/plugin.ts index 99362008c5..d3fdb5affb 100644 --- a/packages/plugins/src/swap/plugin.ts +++ b/packages/plugins/src/swap/plugin.ts @@ -1,5 +1,13 @@ import type { ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; -import { AssetValue, Chain, CosmosChains, EVMChains, SwapKitError, type SwapParams } from "@swapkit/helpers"; +import { + ApproveMode, + AssetValue, + Chain, + CosmosChains, + EVMChains, + SwapKitError, + type SwapParams, +} from "@swapkit/helpers"; import { CosmosTransactionSchema, EVMTransactionSchema, @@ -7,7 +15,7 @@ import { TronTransactionSchema, } from "@swapkit/helpers/api"; import { match, P } from "ts-pattern"; -import { createPlugin } from "../utils"; +import { approve, createPlugin } from "../utils"; const isEVMTransaction = (tx: unknown) => EVMTransactionSchema.safeParse(tx).success; const isTronTransaction = (tx: unknown) => TronTransactionSchema.safeParse(tx).success; @@ -15,6 +23,8 @@ const isCosmosTransaction = (tx: unknown) => CosmosTransactionSchema.safeParse(t export const SwapPlugin = createPlugin({ methods: ({ getWallet }) => ({ + approveAssetValue: approve({ approveMode: ApproveMode.Approve, getWallet }), + isAssetValueApproved: approve({ approveMode: ApproveMode.CheckOnly, getWallet }), swap: function swap({ route }: SwapParams<"swap", QuoteResponseRoute>) { const { sellAsset, tx } = route; const sellAssetValue = AssetValue.from({ asset: sellAsset }); diff --git a/packages/plugins/src/utils.ts b/packages/plugins/src/utils.ts index da82bba417..72233b9db2 100644 --- a/packages/plugins/src/utils.ts +++ b/packages/plugins/src/utils.ts @@ -1,5 +1,7 @@ import type { ApproveMode, ApproveReturnType, EVMChain, ProviderName } from "@swapkit/helpers"; -import { type AssetValue, EVMChains, SwapKitError } from "@swapkit/helpers"; +import { AssetValue, EVMChains, SwapKitError } from "@swapkit/helpers"; +import { type QuoteResponseRoute, SwapKitApi } from "@swapkit/helpers/api"; +import { match, P } from "ts-pattern"; import type { SwapKitPluginParams } from "./types"; export function createPlugin< @@ -15,28 +17,58 @@ export function createPlugin< } export function approve({ approveMode, getWallet }: { approveMode: T } & SwapKitPluginParams) { - return function approve({ assetValue, spenderAddress }: { spenderAddress: string; assetValue: AssetValue }) { - const evmChain = assetValue.chain as EVMChain; - const isEVMChain = EVMChains.includes(evmChain); - const isNativeEVM = isEVMChain && assetValue.isGasAsset; - - if (isNativeEVM || !isEVMChain || assetValue.isSynthetic) { - const isApproved = approveMode === "checkOnly" || "approved"; - return Promise.resolve(isApproved) as ApproveReturnType; - } - - const wallet = getWallet(evmChain); - const walletAction = approveMode === "checkOnly" ? wallet.isApproved : wallet.approve; - - if (!(assetValue.address && wallet.address)) { - throw new SwapKitError("core_approve_asset_address_or_from_not_found"); - } - - return walletAction({ - amount: assetValue.getBaseValue("bigint"), - assetAddress: assetValue.address, - from: wallet.address, - spenderAddress, - }); + return function approve(params: { spenderAddress: string; assetValue: AssetValue; route?: QuoteResponseRoute }) { + return match(params) + .with({ route: P.not(P.nullish) }, async ({ route }) => { + const assetValue = AssetValue.from({ asset: route.sellAsset, value: route.sellAmount }); + const isEVMChain = EVMChains.includes(assetValue.chain as EVMChain); + const isNativeEVM = isEVMChain && assetValue.isGasAsset; + if (isNativeEVM || !isEVMChain || assetValue.isSynthetic || approveMode === "checkOnly") { + return true; + } + + const response = await SwapKitApi.getTokenApproval({ routeId: route.routeId }); + + if (approveMode === "checkOnly") { + return response.isApproved; + } + + if (!response.approvalTransaction) { + throw new SwapKitError("core_approve_asset_target_invalid"); + } + + const wallet = getWallet(assetValue.chain as EVMChain); + return wallet.sendTransaction({ + data: response.approvalTransaction.data, + from: response.approvalTransaction.from, + to: response.approvalTransaction.to, + value: BigInt(assetValue.getBaseValue("bigint") || "0"), + }); + }) + .otherwise(({ spenderAddress, assetValue }) => { + const evmChain = assetValue.chain as EVMChain; + const isEVMChain = EVMChains.includes(evmChain); + const isNativeEVM = isEVMChain && assetValue.isGasAsset; + + if (isNativeEVM || !isEVMChain || assetValue.isSynthetic) { + const isApproved = approveMode === "checkOnly" ? true : "approved"; + return Promise.resolve(isApproved) as ApproveReturnType; + } + + const wallet = getWallet(evmChain); + + const walletAction = approveMode === "checkOnly" ? wallet.isApproved : wallet.approve; + + if (!(assetValue.address && wallet.address)) { + throw new SwapKitError("core_approve_asset_address_or_from_not_found"); + } + + return walletAction({ + amount: assetValue.getBaseValue("bigint"), + assetAddress: assetValue.address, + from: wallet.address, + spenderAddress, + }); + }); }; } From 8d30bfdaf5f7ecd8dc8332814b772edb90e32722 Mon Sep 17 00:00:00 2001 From: towan Date: Fri, 21 Nov 2025 20:15:11 +0400 Subject: [PATCH 06/10] chore: fix branch --- bun.lock | 15 ++++++++++--- packages/core/package.json | 3 ++- packages/plugins/package.json | 1 + packages/sdk/package.json | 3 ++- packages/toolboxes/src/utxo/toolbox/zcash.ts | 22 +++++++++++++++++++- packages/ui/package.json | 1 + packages/wallet-extensions/package.json | 2 ++ 7 files changed, 41 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index 6853eb76c9..1a55c2ff71 100644 --- a/bun.lock +++ b/bun.lock @@ -75,9 +75,11 @@ "@swapkit/plugins": "workspace:*", "@swapkit/toolboxes": "workspace:*", "@swapkit/wallets": "workspace:*", + "cosmjs-types": "0.10.1", "ts-pattern": "^5.9.0", }, "devDependencies": { + "cosmjs-types": "0.10.1", "ts-pattern": "5.9.0", }, }, @@ -105,6 +107,7 @@ "name": "@swapkit/plugins", "version": "4.2.2", "dependencies": { + "@near-js/transactions": "2.5.0", "@near-js/utils": "~2.5.0", "@polkadot/keyring": "~13.5.7", "@polkadot/util": "~13.5.7", @@ -131,6 +134,7 @@ "@swapkit/server": "workspace:*", "@swapkit/toolboxes": "workspace:*", "@swapkit/wallets": "workspace:*", + "cosmjs-types": "0.10.1", }, }, "packages/server": { @@ -293,6 +297,7 @@ "@swapkit/wallets": "workspace:*", "class-variance-authority": "0.7.1", "clsx": "2.1.1", + "cosmjs-types": "0.10.1", "lucide-react": "0.552.0", "react": "19.1.1", "react-hook-form": "7.65.0", @@ -324,6 +329,7 @@ "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", "@swapkit/wallet-core": "workspace:*", + "cosmjs-types": "0.10.1", "ethers": "^6.14.0", "sats-connect": "~1.0.0", "ts-pattern": "~5.9.0", @@ -334,6 +340,7 @@ "@near-js/crypto": "2.5.0", "@near-js/transactions": "2.5.0", "@solana/web3.js": "1.98.4", + "cosmjs-types": "0.10.1", "ethers": "6.15.0", "sats-connect": "1.0.0", "ts-pattern": "5.9.0", @@ -3741,7 +3748,7 @@ "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], - "oniguruma-to-es": ["oniguruma-to-es@4.3.3", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg=="], + "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], @@ -5263,6 +5270,10 @@ "@stacks/transactions/@noble/secp256k1": ["@noble/secp256k1@1.7.1", "", {}, "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw=="], + "@swapkit/wallet-hardware/@ledgerhq/hw-transport": ["@ledgerhq/hw-transport@6.31.13", "", { "dependencies": { "@ledgerhq/devices": "8.7.0", "@ledgerhq/errors": "^6.27.0", "@ledgerhq/logs": "^6.13.0", "events": "^3.3.0" } }, "sha512-MrJRDk74wY980ofiFPRpTHQBbRw1wDuKbdag1zqlO1xtJglymwwY03K2kvBNvkm1RTSCPUp/nAoNG+WThZuuew=="], + + "@swapkit/wallets/@near-wallet-selector/core": ["@near-wallet-selector/core@10.1.1", "", { "dependencies": { "@near-js/crypto": "^2.3.0", "@near-js/providers": "^2.3.0", "@near-js/signers": "^2.3.0", "@near-js/transactions": "^2.3.0", "@near-js/types": "^2.3.0", "borsh": "2.0.0", "events": "3.3.0", "js-sha256": "0.9.0", "rxjs": "7.8.1" } }, "sha512-g2dpqoYZLEN9H1SXagbaarPfCs3M4GaIMBjfEKh09fauvOgf8YFWpy3QOyom1A+8O3dfrUCIMpid3MlxdhIQAA=="], + "@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@thaunknown/simple-websocket/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], @@ -5333,8 +5344,6 @@ "abi-wan-kanabi/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "ajv-formats/ajv": ["ajv@8.11.2", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "aria-hidden/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], diff --git a/packages/core/package.json b/packages/core/package.json index b295433986..ec33e17587 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,10 +5,11 @@ "@swapkit/plugins": "workspace:*", "@swapkit/toolboxes": "workspace:*", "@swapkit/wallets": "workspace:*", + "cosmjs-types": "0.10.1", "ts-pattern": "^5.9.0" }, "description": "SwapKit - Core", - "devDependencies": { "ts-pattern": "5.9.0" }, + "devDependencies": { "cosmjs-types": "0.10.1", "ts-pattern": "5.9.0" }, "exports": { ".": { "bun": "./src/index.ts", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index d509115bd4..6d0b104971 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,7 @@ { "author": "swapkit-oss", "dependencies": { + "@near-js/transactions": "2.5.0", "@near-js/utils": "~2.5.0", "@polkadot/keyring": "~13.5.7", "@polkadot/util": "~13.5.7", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index d10b9dcdbd..b12dea3579 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -6,7 +6,8 @@ "@swapkit/plugins": "workspace:*", "@swapkit/server": "workspace:*", "@swapkit/toolboxes": "workspace:*", - "@swapkit/wallets": "workspace:*" + "@swapkit/wallets": "workspace:*", + "cosmjs-types": "0.10.1" }, "description": "SwapKit - SDK", "exports": { diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index 4392787526..e6363cdcf0 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -166,7 +166,6 @@ async function createTransaction(buildTxParams: UTXOBuildTxParams) { sender, }); - return { inputs: mappedInputs, outputs, psbt: mappedPsbt }; } @@ -235,11 +234,32 @@ export async function createZcashToolbox( return keys.toWIF(); } + function getSignTransaction(signer?: ZcashSigner) { + return async function signTransaction(psbt: ZcashPsbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + return signedPsbt; + }; + } + + function getSignAndBroadcastTransaction(signer?: ZcashSigner) { + return async function signAndBroadcastTransaction(psbt: ZcashPsbt): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + signedPsbt.finalizeAllInputs(); + const txHex = signedPsbt.extractTransaction().toHex(); + return baseToolbox.broadcastTx(txHex); + }; + } + return { ...baseToolbox, createKeysForPath, createTransaction, getPrivateKeyFromMnemonic, + signAndBroadcastTransaction: getSignAndBroadcastTransaction(signer), + signer, + signTransaction: getSignTransaction(signer), transfer, validateAddress: validateZcashAddress, }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 871e06c978..003b0cb5c5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -22,6 +22,7 @@ "@swapkit/wallets": "workspace:*", "class-variance-authority": "0.7.1", "clsx": "2.1.1", + "cosmjs-types": "0.10.1", "lucide-react": "0.552.0", "react": "19.1.1", "react-hook-form": "7.65.0", diff --git a/packages/wallet-extensions/package.json b/packages/wallet-extensions/package.json index 85bd9b2039..d804ea6516 100644 --- a/packages/wallet-extensions/package.json +++ b/packages/wallet-extensions/package.json @@ -9,6 +9,7 @@ "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", "@swapkit/wallet-core": "workspace:*", + "cosmjs-types": "0.10.1", "ethers": "^6.14.0", "sats-connect": "~1.0.0", "ts-pattern": "~5.9.0" @@ -20,6 +21,7 @@ "@near-js/crypto": "2.5.0", "@near-js/transactions": "2.5.0", "@solana/web3.js": "1.98.4", + "cosmjs-types": "0.10.1", "ethers": "6.15.0", "sats-connect": "1.0.0", "ts-pattern": "5.9.0" From b7689ed9b7a56baaf1d398b2e8510b5f3778102e Mon Sep 17 00:00:00 2001 From: towan Date: Tue, 25 Nov 2025 15:04:05 +0400 Subject: [PATCH 07/10] fix: approval endpoint changes --- packages/core/src/index.ts | 14 +++-- .../helpers/src/api/swapkitApi/endpoints.ts | 6 +- packages/helpers/src/modules/swapKitConfig.ts | 15 ++++- packages/helpers/src/types/wallet.ts | 60 ++++++++++++++++++- packages/helpers/src/utils/wallets.ts | 28 +++++++++ 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 76ac4b4ad0..44b34b8a5a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,8 +15,8 @@ import { type SKConfigState, SwapKitError, type SwapParams, + supportsV3SwapFlow, UTXOChains, - WalletOption, } from "@swapkit/helpers"; import type { EVMTransaction, QuoteResponseRoute } from "@swapkit/helpers/api"; import type { createPlugin } from "@swapkit/plugins"; @@ -202,11 +202,17 @@ export function SwapKit< return wallet; } - function swap({ route, pluginName, useApiTx, ...rest }: SwapParams) { + function swap({ route, pluginName, ...rest }: SwapParams) { const fromChain = AssetValue.from({ asset: route.sellAsset }).chain; + const wallet = getWallet(fromChain); - // only keystore supports straight signing of all chains - if (useApiTx && getWallet(fromChain)?.walletType === WalletOption.KEYSTORE) { + if (!wallet) { + throw new SwapKitError("core_wallet_connection_not_found"); + } + + const useV3Flow = supportsV3SwapFlow(wallet.walletType, fromChain) && route.tx; + + if (useV3Flow) { const plugin = getSwapKitPlugin("swap"); if ("swap" in plugin) { // @ts-expect-error TODO: fix this diff --git a/packages/helpers/src/api/swapkitApi/endpoints.ts b/packages/helpers/src/api/swapkitApi/endpoints.ts index 1cb74a2a47..118d21a8de 100644 --- a/packages/helpers/src/api/swapkitApi/endpoints.ts +++ b/packages/helpers/src/api/swapkitApi/endpoints.ts @@ -125,11 +125,11 @@ export async function getChainBalance({ } export function getTokenApproval( - params: { spender: string; userWallet: string; assetValue: AssetValue } | { routeId: string }, + params: { spender: string; userWallet: string; assetValue: AssetValue } | { routeId: string; spender: string }, ) { return match(params) - .with({ routeId: P.string }, ({ routeId }) => { - const url = getApiUrl(`/approve?routeId=${routeId}`); + .with({ routeId: P.string, spender: P.string }, ({ routeId, spender }) => { + const url = getApiUrl(`/approve?routeId=${routeId}&sourceAddress=${spender}`); return SKRequestClient.get(url); }) .with( diff --git a/packages/helpers/src/modules/swapKitConfig.ts b/packages/helpers/src/modules/swapKitConfig.ts index 1daac680c4..561dc904ba 100644 --- a/packages/helpers/src/modules/swapKitConfig.ts +++ b/packages/helpers/src/modules/swapKitConfig.ts @@ -12,6 +12,15 @@ import type { BalanceResponse, QuoteRequest, QuoteResponse, QuoteResponseRoute } import { WalletOption } from "../types"; import type { FeeMultiplierConfig } from "./feeMultiplier"; +export type V3SwapFlowConfig = { + /** + * Global flag to enable/disable V3 swap flow (raw tx signing from API). + * When disabled, always falls back to named plugins. + * @default true + */ + enabled: boolean; +}; + export type SKConfigIntegrations = { chainflip?: { useSDKBroker?: boolean; brokerUrl: string }; coinbase?: { @@ -80,6 +89,7 @@ const initialState = { requestOptions: { retry: { backoffMultiplier: 2, baseDelay: 300, maxDelay: 5000, maxRetries: 3 }, timeoutMs: 30000 }, rpcUrls, + v3SwapFlow: { enabled: true } as V3SwapFlowConfig, wallets: Object.values(WalletOption), }; type SKState = typeof initialState; @@ -89,10 +99,11 @@ export type SKConfigState = { chains?: SKState["chains"]; endpoints?: Partial; envs?: Partial; + feeMultipliers?: FeeMultiplierConfig; integrations?: Partial; rpcUrls?: Partial; + v3SwapFlow?: Partial; wallets?: SKState["wallets"]; - feeMultipliers?: FeeMultiplierConfig; }; type SwapKitConfigStore = SKState & { @@ -122,6 +133,7 @@ export const useSwapKitStore = create((set) => ({ feeMultipliers: config?.feeMultipliers || s.feeMultipliers, integrations: { ...s.integrations, ...config?.integrations }, rpcUrls: { ...s.rpcUrls, ...config?.rpcUrls }, + v3SwapFlow: { ...s.v3SwapFlow, ...config?.v3SwapFlow }, wallets: s.wallets.concat(config?.wallets || []), })), setEndpoint: (key, endpoint) => set((s) => ({ endpoints: { ...s.endpoints, [key]: endpoint } })), @@ -149,6 +161,7 @@ export const useSwapKitConfig = () => feeMultipliers: state?.feeMultipliers, integrations: state?.integrations, rpcUrls: state?.rpcUrls, + v3SwapFlow: state?.v3SwapFlow, wallets: state?.wallets, })), ); diff --git a/packages/helpers/src/types/wallet.ts b/packages/helpers/src/types/wallet.ts index abc93e7ccf..48a91852dc 100644 --- a/packages/helpers/src/types/wallet.ts +++ b/packages/helpers/src/types/wallet.ts @@ -101,7 +101,6 @@ export type EIP6963ProviderDetail = { info: EIP6963ProviderInfo; provider: Eip11 export type EIP6963Provider = { info: EIP6963ProviderInfo; provider: Eip1193Provider }; -// This type represents the structure of an event dispatched by a wallet to announce its presence based on EIP-6963. export type EIP6963AnnounceProviderEvent = Event & { detail: EIP6963Provider }; export type ChainSigner = { @@ -122,3 +121,62 @@ export type GenericCreateTransactionParams = Omit>>> = { + [WalletOption.KEYSTORE]: { + Arbitrum: true, + Aurora: true, + Avalanche: true, + Base: true, + Berachain: true, + BinanceSmartChain: true, + Bitcoin: true, + BitcoinCash: true, + Botanix: true, + Cardano: true, + Chainflip: true, + Core: true, + Corn: true, + Cosmos: true, + Cronos: true, + Dash: true, + Dogecoin: true, + Ethereum: true, + Gnosis: true, + Hyperevm: true, + Kujira: true, + Litecoin: true, + Maya: true, + MegaETH: true, + Monad: true, + Near: true, + Noble: true, + Optimism: true, + Polkadot: true, + Polygon: true, + Ripple: true, + Solana: true, + Sonic: true, + Sui: true, + THORChain: true, + Ton: true, + Tron: true, + Unichain: true, + XLayer: true, + Zcash: true, + } as Partial>, + + // TODO: Add other wallets after testing + // [WalletOption.LEDGER]: { ... }, + // [WalletOption.TREZOR]: { ... }, + // [WalletOption.VULTISIG]: { ... }, +}; diff --git a/packages/helpers/src/utils/wallets.ts b/packages/helpers/src/utils/wallets.ts index 71ab8abaf9..d66e5c3dc6 100644 --- a/packages/helpers/src/utils/wallets.ts +++ b/packages/helpers/src/utils/wallets.ts @@ -1,11 +1,13 @@ import { type Chain, getChainConfig } from "@swapkit/types"; import type { BrowserProvider, JsonRpcProvider } from "ethers"; +import { SKConfig } from "../modules/swapKitConfig"; import { SwapKitError } from "../modules/swapKitError"; import { type EIP6963AnnounceProviderEvent, type EIP6963Provider, type EthereumWindowProvider, type NetworkParams, + V3SwapFlowSupport, WalletOption, } from "../types"; import { warnOnce } from "./others"; @@ -235,3 +237,29 @@ export function providerRequest({ const providerParams = params ? (Array.isArray(params) ? params : [params]) : []; return provider.send(method, providerParams); } + +/** + * Check if a wallet supports V3 swap flow (raw tx signing from API) for a specific chain. + * + * The V3 swap flow allows signing raw transactions returned by the API instead of + * having plugins build transactions themselves. This is more efficient but requires + * wallet support for signing arbitrary transactions. + * + * Takes into account: + * 1. Global v3SwapFlow.enabled flag + * 2. Per-chain capability in V3SwapFlowSupport registry + * + * @param walletType - The wallet type (e.g., WalletOption.KEYSTORE) + * @param chain - The chain to check support for + * @returns true if the wallet supports V3 flow for the chain, false otherwise + */ +export function supportsV3SwapFlow(walletType: WalletOption, chain: Chain): boolean { + const config = SKConfig.get("v3SwapFlow"); + + if (!config?.enabled) { + return false; + } + + const walletCapabilities = V3SwapFlowSupport[walletType]; + return walletCapabilities?.[chain] === true; +} From 3cefbb497c0035c7b0b3f2b55dc2fb722dc80e11 Mon Sep 17 00:00:00 2001 From: towan Date: Tue, 25 Nov 2025 15:29:09 +0400 Subject: [PATCH 08/10] chore: cleans up ts errors --- bun.lock | 1 + packages/core/src/index.ts | 3 ++- packages/plugins/src/utils.ts | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 3307cb90b6..628be8a564 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "swapkit-monorepo", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 44b34b8a5a..a11a3dae6c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,6 +17,7 @@ import { type SwapParams, supportsV3SwapFlow, UTXOChains, + type WalletOption, } from "@swapkit/helpers"; import type { EVMTransaction, QuoteResponseRoute } from "@swapkit/helpers/api"; import type { createPlugin } from "@swapkit/plugins"; @@ -210,7 +211,7 @@ export function SwapKit< throw new SwapKitError("core_wallet_connection_not_found"); } - const useV3Flow = supportsV3SwapFlow(wallet.walletType, fromChain) && route.tx; + const useV3Flow = supportsV3SwapFlow(wallet.walletType as WalletOption, fromChain) && route.tx; if (useV3Flow) { const plugin = getSwapKitPlugin("swap"); diff --git a/packages/plugins/src/utils.ts b/packages/plugins/src/utils.ts index 0244d2df4e..3ababc890c 100644 --- a/packages/plugins/src/utils.ts +++ b/packages/plugins/src/utils.ts @@ -19,7 +19,7 @@ export function createPlugin< export function approve({ approveMode, getWallet }: { approveMode: T } & SwapKitPluginParams) { return function approve(params: { spenderAddress: string; assetValue: AssetValue; route?: QuoteResponseRoute }) { return match(params) - .with({ route: P.not(P.nullish) }, async ({ route }) => { + .with({ route: P.not(P.nullish), spenderAddress: P.string }, async ({ route, spenderAddress }) => { const assetValue = AssetValue.from({ asset: route.sellAsset, value: route.sellAmount }); const isEVMChain = EVMChains.includes(assetValue.chain as EVMChain); const isNativeEVM = isEVMChain && assetValue.isGasAsset; @@ -27,8 +27,7 @@ export function approve({ approveMode, getWallet }: { app return true; } - const response = await SwapKitApi.getTokenApproval({ routeId: route.routeId }); - + const response = await SwapKitApi.getTokenApproval({ routeId: route.routeId, spender: spenderAddress }); if (approveMode === "checkOnly") { return response.isApproved; } From 777dd5b878b5111946d33e10c00d62857a6eba20 Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 26 Nov 2025 13:08:57 +0400 Subject: [PATCH 09/10] chore: update signing decision logic + adds btc to phantom --- bun.lock | 1 - packages/helpers/src/types/wallet.ts | 141 +++++++++++------- packages/helpers/src/utils/wallets.ts | 3 +- .../wallet-extensions/src/phantom/index.ts | 14 +- 4 files changed, 102 insertions(+), 57 deletions(-) diff --git a/bun.lock b/bun.lock index 628be8a564..3307cb90b6 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "swapkit-monorepo", diff --git a/packages/helpers/src/types/wallet.ts b/packages/helpers/src/types/wallet.ts index 48a91852dc..f642f6110a 100644 --- a/packages/helpers/src/types/wallet.ts +++ b/packages/helpers/src/types/wallet.ts @@ -1,4 +1,5 @@ -import type { Chain, getChainConfig } from "@swapkit/types"; +import type { getChainConfig } from "@swapkit/types"; +import { Chain } from "@swapkit/types"; import type { BrowserProvider, Eip1193Provider } from "ethers"; import type { AssetValue } from "../modules/assetValue"; @@ -125,58 +126,92 @@ export type GenericCreateTransactionParams = Omit>>> = { - [WalletOption.KEYSTORE]: { - Arbitrum: true, - Aurora: true, - Avalanche: true, - Base: true, - Berachain: true, - BinanceSmartChain: true, - Bitcoin: true, - BitcoinCash: true, - Botanix: true, - Cardano: true, - Chainflip: true, - Core: true, - Corn: true, - Cosmos: true, - Cronos: true, - Dash: true, - Dogecoin: true, - Ethereum: true, - Gnosis: true, - Hyperevm: true, - Kujira: true, - Litecoin: true, - Maya: true, - MegaETH: true, - Monad: true, - Near: true, - Noble: true, - Optimism: true, - Polkadot: true, - Polygon: true, - Ripple: true, - Solana: true, - Sonic: true, - Sui: true, - THORChain: true, - Ton: true, - Tron: true, - Unichain: true, - XLayer: true, - Zcash: true, - } as Partial>, - - // TODO: Add other wallets after testing - // [WalletOption.LEDGER]: { ... }, - // [WalletOption.TREZOR]: { ... }, - // [WalletOption.VULTISIG]: { ... }, + +// Wallet groups for easier maintenance +const EVMWallets = [ + WalletOption.BITGET, + WalletOption.BRAVE, + WalletOption.COINBASE_MOBILE, + WalletOption.COINBASE_WEB, + WalletOption.COSMOSTATION, + WalletOption.CTRL, + WalletOption.EIP6963, + WalletOption.KEEPKEY, + WalletOption.KEEPKEY_BEX, + WalletOption.KEYSTORE, + WalletOption.LEDGER, + WalletOption.METAMASK, + WalletOption.OKX, + WalletOption.OKX_MOBILE, + WalletOption.PASSKEYS, + WalletOption.PHANTOM, + WalletOption.TALISMAN, + WalletOption.TREZOR, + WalletOption.TRUSTWALLET_WEB, + WalletOption.VULTISIG, + WalletOption.WALLETCONNECT, +] as const; + +// Chain → Wallets mapping +export const V3SwapFlowSupport: Partial> = { + // EVM Chains - all EVM wallets support via eth_sendTransaction + [Chain.Arbitrum]: EVMWallets, + [Chain.Aurora]: EVMWallets, + [Chain.Avalanche]: EVMWallets, + [Chain.Base]: EVMWallets, + [Chain.Berachain]: EVMWallets, + [Chain.BinanceSmartChain]: EVMWallets, + [Chain.Botanix]: EVMWallets, + [Chain.Chainflip]: EVMWallets, + [Chain.Core]: EVMWallets, + [Chain.Corn]: EVMWallets, + [Chain.Cronos]: EVMWallets, + [Chain.Ethereum]: EVMWallets, + [Chain.Gnosis]: EVMWallets, + [Chain.Hyperevm]: EVMWallets, + [Chain.MegaETH]: EVMWallets, + [Chain.Monad]: EVMWallets, + [Chain.Optimism]: EVMWallets, + [Chain.Polygon]: EVMWallets, + [Chain.Sonic]: EVMWallets, + [Chain.Unichain]: EVMWallets, + [Chain.XLayer]: EVMWallets, + + // UTXO Chains - only wallets with PSBT signing + [Chain.Bitcoin]: [ + WalletOption.BITGET, + WalletOption.EXODUS, + WalletOption.KEYSTORE, + WalletOption.OKX, + WalletOption.ONEKEY, + WalletOption.PASSKEYS, + WalletOption.PHANTOM, + ], + [Chain.BitcoinCash]: [WalletOption.KEYSTORE], + [Chain.Dash]: [WalletOption.KEYSTORE], + [Chain.Dogecoin]: [WalletOption.KEYSTORE], + [Chain.Litecoin]: [WalletOption.KEYSTORE], + [Chain.Zcash]: [WalletOption.KEYSTORE], + + // Cosmos Chains - only wallets with proto signing + [Chain.Cosmos]: [WalletOption.KEYSTORE], + [Chain.Kujira]: [WalletOption.KEYSTORE], + [Chain.Maya]: [WalletOption.KEYSTORE], + [Chain.Noble]: [WalletOption.KEYSTORE], + [Chain.THORChain]: [WalletOption.KEYSTORE], + + // Other Chains + [Chain.Cardano]: [WalletOption.KEYSTORE], + [Chain.Near]: [WalletOption.KEYSTORE, WalletOption.LEDGER], + [Chain.Polkadot]: [WalletOption.KEYSTORE], + [Chain.Ripple]: [WalletOption.KEYSTORE, WalletOption.LEDGER], + [Chain.Solana]: [WalletOption.KEYSTORE, WalletOption.PASSKEYS, WalletOption.EXODUS, WalletOption.PHANTOM], + [Chain.Sui]: [WalletOption.KEYSTORE], + [Chain.Ton]: [WalletOption.KEYSTORE], + [Chain.Tron]: [WalletOption.KEYSTORE, WalletOption.LEDGER], }; diff --git a/packages/helpers/src/utils/wallets.ts b/packages/helpers/src/utils/wallets.ts index d66e5c3dc6..2bef8ed582 100644 --- a/packages/helpers/src/utils/wallets.ts +++ b/packages/helpers/src/utils/wallets.ts @@ -260,6 +260,5 @@ export function supportsV3SwapFlow(walletType: WalletOption, chain: Chain): bool return false; } - const walletCapabilities = V3SwapFlowSupport[walletType]; - return walletCapabilities?.[chain] === true; + return V3SwapFlowSupport[chain]?.includes(walletType) ?? false; } diff --git a/packages/wallet-extensions/src/phantom/index.ts b/packages/wallet-extensions/src/phantom/index.ts index 7be5544106..d1ea53259b 100644 --- a/packages/wallet-extensions/src/phantom/index.ts +++ b/packages/wallet-extensions/src/phantom/index.ts @@ -48,8 +48,20 @@ async function getWalletMethods(chain: PhantomSupportedChain) { } const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo"); + const { Psbt } = await import("bitcoinjs-lib"); const [{ address }] = await provider.requestAccounts(); - const toolbox = await getUtxoToolbox(chain); + + async function signTransaction(psbt: InstanceType) { + const psbtBytes = psbt.toBuffer(); + const signedPsbtBytes = await provider.signPSBT(psbtBytes, { + inputsToSign: [{ address, signingIndexes: psbt.txInputs.map((_, index) => index) }], + }); + + return Psbt.fromBuffer(Buffer.from(signedPsbtBytes)); + } + + const signer = { getAddress: () => Promise.resolve(address), signTransaction }; + const toolbox = await getUtxoToolbox(chain, { signer }); return { ...toolbox, address }; } From fc155f9e6cd005cc69216452e7be60cc7c01a63a Mon Sep 17 00:00:00 2001 From: towan Date: Mon, 1 Dec 2025 15:38:29 +0400 Subject: [PATCH 10/10] chore: adds sui sign and broadcast and support in trading plugin --- bun.lock | 1 + packages/core/src/index.ts | 2 +- packages/plugins/package.json | 13 ++++----- packages/plugins/src/index.ts | 6 ++--- packages/plugins/src/swap/index.ts | 1 - packages/plugins/src/trading/index.ts | 1 + .../plugins/src/{swap => trading}/plugin.ts | 11 +++++--- packages/plugins/src/types.ts | 4 +-- packages/sdk/src/index.ts | 4 +-- packages/toolboxes/src/sui/toolbox.ts | 27 +++++++++++++++++++ 10 files changed, 52 insertions(+), 18 deletions(-) delete mode 100644 packages/plugins/src/swap/index.ts create mode 100644 packages/plugins/src/trading/index.ts rename packages/plugins/src/{swap => trading}/plugin.ts (93%) diff --git a/bun.lock b/bun.lock index 0050f9101f..c7a58c00dc 100644 --- a/bun.lock +++ b/bun.lock @@ -107,6 +107,7 @@ "name": "@swapkit/plugins", "version": "4.2.9", "dependencies": { + "@mysten/sui": "1.44.0", "@near-js/transactions": "2.5.0", "@near-js/utils": "~2.5.0", "@polkadot/keyring": "~13.5.7", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a11a3dae6c..97d4dd97ab 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -214,7 +214,7 @@ export function SwapKit< const useV3Flow = supportsV3SwapFlow(wallet.walletType as WalletOption, fromChain) && route.tx; if (useV3Flow) { - const plugin = getSwapKitPlugin("swap"); + const plugin = getSwapKitPlugin("trading"); if ("swap" in plugin) { // @ts-expect-error TODO: fix this return plugin.swap({ ...rest, route }); diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 9badb7290e..c136dfc9f0 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,7 @@ { "author": "swapkit-oss", "dependencies": { + "@mysten/sui": "1.44.0", "@near-js/transactions": "2.5.0", "@near-js/utils": "~2.5.0", "@polkadot/keyring": "~13.5.7", @@ -61,17 +62,17 @@ "require": "./dist/src/solana/index.cjs", "types": "./dist/types/solana/index.d.ts" }, - "./swap": { - "bun": "./src/swap/index.ts", - "default": "./dist/src/swap/index.js", - "require": "./dist/src/swap/index.cjs", - "types": "./dist/types/swap/index.d.ts" - }, "./thorchain": { "bun": "./src/thorchain/index.ts", "default": "./dist/src/thorchain/index.js", "require": "./dist/src/thorchain/index.cjs", "types": "./dist/types/thorchain/index.d.ts" + }, + "./trading": { + "bun": "./src/trading/index.ts", + "default": "./dist/src/trading/index.js", + "require": "./dist/src/trading/index.cjs", + "types": "./dist/types/trading/index.d.ts" } }, "files": ["dist/", "src/"], diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index e00da3e04b..3ee1d6e4c4 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -27,9 +27,9 @@ export async function loadPlugin

(pluginName: P) { const { SolanaPlugin } = await import("./solana"); return SolanaPlugin; }) - .with("swap", async () => { - const { SwapPlugin } = await import("./swap"); - return SwapPlugin; + .with("trading", async () => { + const { TradingPlugin } = await import("./trading"); + return TradingPlugin; }) .with("near", async () => { const { NearPlugin } = await import("./near"); diff --git a/packages/plugins/src/swap/index.ts b/packages/plugins/src/swap/index.ts deleted file mode 100644 index 92062a29be..0000000000 --- a/packages/plugins/src/swap/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SwapPlugin } from "./plugin"; diff --git a/packages/plugins/src/trading/index.ts b/packages/plugins/src/trading/index.ts new file mode 100644 index 0000000000..14b07d679e --- /dev/null +++ b/packages/plugins/src/trading/index.ts @@ -0,0 +1 @@ +export { TradingPlugin } from "./plugin"; diff --git a/packages/plugins/src/swap/plugin.ts b/packages/plugins/src/trading/plugin.ts similarity index 93% rename from packages/plugins/src/swap/plugin.ts rename to packages/plugins/src/trading/plugin.ts index d3fdb5affb..eaf4e0fbd5 100644 --- a/packages/plugins/src/swap/plugin.ts +++ b/packages/plugins/src/trading/plugin.ts @@ -21,11 +21,11 @@ const isEVMTransaction = (tx: unknown) => EVMTransactionSchema.safeParse(tx).suc const isTronTransaction = (tx: unknown) => TronTransactionSchema.safeParse(tx).success; const isCosmosTransaction = (tx: unknown) => CosmosTransactionSchema.safeParse(tx).success; -export const SwapPlugin = createPlugin({ +export const TradingPlugin = createPlugin({ methods: ({ getWallet }) => ({ approveAssetValue: approve({ approveMode: ApproveMode.Approve, getWallet }), isAssetValueApproved: approve({ approveMode: ApproveMode.CheckOnly, getWallet }), - swap: function swap({ route }: SwapParams<"swap", QuoteResponseRoute>) { + swap: function swap({ route }: SwapParams<"trading", QuoteResponseRoute>) { const { sellAsset, tx } = route; const sellAssetValue = AssetValue.from({ asset: sellAsset }); const chain = sellAssetValue.chain; @@ -98,11 +98,16 @@ export const SwapPlugin = createPlugin({ return wallet.signAndBroadcastTransaction(transaction); }) + .with({ chain: Chain.Sui, tx: P.string }, async ({ chain, tx }) => { + const wallet = await getWallet(chain); + + return wallet.signAndBroadcastTransaction(tx); + }) .otherwise(() => { throw new SwapKitError("plugin_generic_swap_invalid_data", { chain, tx }); }); }, }), - name: "swap", + name: "trading", properties: { supportedSwapkitProviders: [] }, }); diff --git a/packages/plugins/src/types.ts b/packages/plugins/src/types.ts index 24e3998141..73c3600308 100644 --- a/packages/plugins/src/types.ts +++ b/packages/plugins/src/types.ts @@ -5,8 +5,8 @@ import type { EVMPlugin } from "./evm"; import type { NearPlugin } from "./near"; import type { RadixPlugin } from "./radix"; import type { SolanaPlugin } from "./solana/plugin"; -import type { SwapPlugin } from "./swap"; import type { ThorchainPlugin } from "./thorchain"; +import type { TradingPlugin } from "./trading"; export type * from "./chainflip/types"; export type * from "./thorchain/types"; @@ -17,7 +17,7 @@ export type SKPlugins = typeof ChainflipPlugin & typeof SolanaPlugin & typeof EVMPlugin & typeof NearPlugin & - typeof SwapPlugin; + typeof TradingPlugin; export type PluginName = keyof SKPlugins; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 657873dcbb..4b10834ffc 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -10,8 +10,8 @@ import { GardenPlugin } from "@swapkit/plugins/garden"; import { NearPlugin } from "@swapkit/plugins/near"; import { RadixPlugin } from "@swapkit/plugins/radix"; import { SolanaPlugin } from "@swapkit/plugins/solana"; -import { SwapPlugin } from "@swapkit/plugins/swap"; import { MayachainPlugin, ThorchainPlugin } from "@swapkit/plugins/thorchain"; +import { TradingPlugin } from "@swapkit/plugins/trading"; import type { createWallet } from "@swapkit/wallets"; import { bitgetWallet } from "@swapkit/wallets/bitget"; @@ -93,7 +93,7 @@ export const defaultPlugins = { ...SolanaPlugin, ...NearPlugin, ...GardenPlugin, - ...SwapPlugin, + ...TradingPlugin, }; export const defaultWallets = { diff --git a/packages/toolboxes/src/sui/toolbox.ts b/packages/toolboxes/src/sui/toolbox.ts index 72797a1e74..786e88707c 100644 --- a/packages/toolboxes/src/sui/toolbox.ts +++ b/packages/toolboxes/src/sui/toolbox.ts @@ -1,3 +1,4 @@ +import type { Transaction } from "@mysten/sui/transactions"; import { AssetValue, Chain, getChainConfig, SwapKitError } from "@swapkit/helpers"; import { match, P } from "ts-pattern"; import type { SuiCreateTransactionParams, SuiToolboxParams, SuiTransferParams } from "./types"; @@ -154,11 +155,37 @@ export async function getSuiToolbox({ provider: providerParam, ...signerParams } return txHash; } + async function broadcastTransaction(signedTransaction: { bytes: string; signature: string }) { + const suiClient = await getSuiClient(); + const { digest: txHash } = await suiClient.executeTransactionBlock({ + signature: signedTransaction.signature, + transactionBlock: signedTransaction.bytes, + }); + + return txHash; + } + + async function signAndBroadcastTransaction(transaction: Transaction | Uint8Array | string) { + if (!signer) { + throw new SwapKitError("toolbox_sui_no_signer"); + } + + const suiClient = await getSuiClient(); + + const txBytes = typeof transaction === "string" ? Uint8Array.from(Buffer.from(transaction, "base64")) : transaction; + + const { digest: txHash } = await suiClient.signAndExecuteTransaction({ signer, transaction: txBytes }); + + return txHash; + } + return { + broadcastTransaction, createTransaction, estimateTransactionFee, getAddress, getBalance, + signAndBroadcastTransaction, signTransaction, transfer, validateAddress,