From e0ce4f1d12567adffaf64d8ad84460fceb26cdc8 Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 13:25:04 +0400 Subject: [PATCH 01/15] feat: add unified sign and signAndBroadcast methods to toolboxes - Add sign and signAndBroadcast methods to UTXO toolboxes (Bitcoin, Litecoin, Dogecoin, Zcash) - Add sign and signAndBroadcast methods to EVM toolbox - Add unified sign and signAndBroadcast methods to Solana toolbox - Handle BitcoinCash special case with TransactionBuilder instead of PSBT - Create implementation plans for full wallet integration --- CTRL_WALLET_IMPLEMENTATION_PLAN.md | 393 ++++++++++++++++++ SIGNING_IMPLEMENTATION_PLAN.md | 305 ++++++++++++++ .../src/evm/toolbox/baseEVMToolbox.ts | 27 ++ packages/toolboxes/src/solana/toolbox.ts | 8 + .../toolboxes/src/utxo/toolbox/bitcoinCash.ts | 17 + packages/toolboxes/src/utxo/toolbox/utxo.ts | 31 ++ packages/toolboxes/src/utxo/toolbox/zcash.ts | 15 + 7 files changed, 796 insertions(+) create mode 100644 CTRL_WALLET_IMPLEMENTATION_PLAN.md create mode 100644 SIGNING_IMPLEMENTATION_PLAN.md diff --git a/CTRL_WALLET_IMPLEMENTATION_PLAN.md b/CTRL_WALLET_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000000..ed5141f192 --- /dev/null +++ b/CTRL_WALLET_IMPLEMENTATION_PLAN.md @@ -0,0 +1,393 @@ +# CTRL Wallet Signing Implementation Plan + +## Executive Summary + +The CTRL wallet presents unique challenges for implementing standardized `sign` and `signAndBroadcast` methods. Unlike traditional wallets that expose signing capabilities, CTRL uses a high-level transaction API that abstracts signing details. This plan outlines strategies to work within CTRL's constraints while providing a consistent interface. + +## Current CTRL Implementation Analysis + +### Chain Support Overview + +| Chain Type | Current Implementation | Signing Method | +|------------|----------------------|----------------| +| **UTXO** (BTC, LTC, DOGE, BCH) | `walletTransfer` API | No direct signing exposed | +| **EVM** (ETH, BSC, AVAX, etc.) | Standard signer via provider | `signer.signTransaction` available | +| **Solana** | Standard toolbox with provider | `signTransaction` available | +| **Cosmos** (ATOM, KUJI, NOBLE) | Offline signer | Standard signing available | +| **THORChain/Maya** | Custom `walletTransfer` | No direct signing | +| **Near** | Custom transaction API | `signAndSendTransaction` only | + +### Key Findings + +1. **UTXO Chains Limitation**: CTRL does not expose PSBT signing for UTXO chains. It only provides: + - `walletTransfer`: High-level transfer method + - `deposit`: Special method for THORChain/Maya deposits + - No access to raw signing capabilities + +2. **EVM Chains**: Full signing support through ethers.js signer + - Can implement standard `signTransaction` + - Can implement `signAndBroadcastTransaction` + +3. **Solana**: Full signing support through provider + - Already has `signTransaction` + - Can add unified `signAndBroadcastTransaction` + +4. **Cosmos Chains**: Standard signing through offline signer + - Can implement standard signing methods + +## Implementation Strategy + +### Approach 1: Hybrid Implementation (Recommended) + +Create a wrapper that provides the best available functionality for each chain: + +```typescript +interface CtrlSigningCapabilities { + // Unified interface - same method names across all chains + + // UTXO - Will throw error directing to use transfer method + sign?: (transaction: Psbt) => Promise; + signAndBroadcast?: (transaction: Psbt) => Promise; + + // EVM - Full signing support + sign?: (tx: EVMTxParams) => Promise; + signAndBroadcast?: (tx: EVMTxParams) => Promise; + + // Solana - Full signing support + sign?: (tx: Transaction | VersionedTransaction) => Promise; + signAndBroadcast?: (tx: Transaction | VersionedTransaction) => Promise; + + // CTRL-specific transfer fallback for UTXO + transfer?: (params: UTXOTransferParams) => Promise; +} +``` + +### Approach 2: Request CTRL API Enhancement + +Work with CTRL team to expose: +1. PSBT signing endpoint for UTXO chains +2. Raw transaction signing for Near +3. Separate sign and broadcast methods + +### Approach 3: Transaction Builder Pattern + +For chains without signing access, implement a transaction builder that: +1. Builds unsigned transactions +2. Returns transaction objects for CTRL to process +3. Uses CTRL's transfer API as the execution mechanism + +## Detailed Implementation Plan + +### Phase 1: Document Current Capabilities + +Create clear documentation of what's possible with CTRL: + +```typescript +// packages/wallets/src/ctrl/capabilities.ts +export const CTRL_CAPABILITIES = { + bitcoin: { + sign: false, + signAndBroadcast: false, + transfer: true, + buildTransaction: true, + }, + ethereum: { + sign: true, + signAndBroadcast: true, + transfer: true, + buildTransaction: true, + }, + solana: { + sign: true, + signAndBroadcast: true, + transfer: true, + buildTransaction: true, + }, + // ... other chains +}; +``` + +### Phase 2: Implement Available Signing Methods + +#### 2.1 EVM Implementation +```typescript +// packages/wallets/src/ctrl/evm-signing.ts +export async function getEVMSigningMethods(provider: BrowserProvider, chain: EVMChain) { + const signer = await provider.getSigner(); + + return { + sign: async (tx: EVMTxParams): Promise => { + // Remove send, just sign + const signedTx = await signer.signTransaction(tx); + return signedTx; + }, + + signAndBroadcast: async (tx: EVMTxParams): Promise => { + const response = await signer.sendTransaction(tx); + return response.hash; + }, + }; +} +``` + +#### 2.2 Solana Implementation +```typescript +// packages/wallets/src/ctrl/solana-signing.ts +export function getSolanaSigningMethods(provider: SolanaProvider) { + return { + sign: async (transaction: Transaction | VersionedTransaction) => { + return provider.signTransaction(transaction); + }, + + signAndBroadcast: async (transaction: Transaction | VersionedTransaction) => { + const signed = await provider.signTransaction(transaction); + const connection = await getConnection(); + const signature = await connection.sendRawTransaction(signed.serialize()); + return signature; + }, + }; +} +``` + +#### 2.3 UTXO Workaround Implementation +```typescript +// packages/wallets/src/ctrl/utxo-builder.ts +export function getUTXOBuilderMethods(chain: UTXOChain) { + return { + // Can build but not sign PSBTs + buildPSBT: async (params: UTXOBuildTxParams): Promise => { + const { createTransaction } = await getUtxoToolbox(chain); + const { psbt } = await createTransaction(params); + return psbt; + }, + + // Use CTRL's transfer API directly + transfer: async (params: UTXOTransferParams): Promise => { + return walletTransfer(params); + }, + + // Throw informative error for signing attempts + sign: async (): Promise => { + throw new SwapKitError("wallet_ctrl_psbt_signing_not_supported", { + solution: "Use transfer method instead or contact CTRL support for PSBT signing", + chain, + }); + }, + + // Throw informative error for sign and broadcast attempts + signAndBroadcast: async (): Promise => { + throw new SwapKitError("wallet_ctrl_psbt_signing_not_supported", { + solution: "Use transfer method instead or contact CTRL support for PSBT signing", + chain, + }); + }, + }; +} +``` + +### Phase 3: Update Wallet Integration + +Modify `/packages/wallets/src/ctrl/index.ts`: + +```typescript +async function getWalletMethods(chain: Chain) { + switch (chain) { + case Chain.Bitcoin: + case Chain.BitcoinCash: + case Chain.Dogecoin: + case Chain.Litecoin: { + const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo"); + const { getUTXOBuilderMethods } = await import("./utxo-builder"); + + const toolbox = await getUtxoToolbox(chain); + const builderMethods = getUTXOBuilderMethods(chain); + + return { + ...toolbox, + ...builderMethods, + transfer: walletTransfer, + // Override signing to show clear error + sign: builderMethods.sign, + signAndBroadcast: builderMethods.signAndBroadcast, + }; + } + + // EVM chains - full signing support + case Chain.Ethereum: + case Chain.BinanceSmartChain: + // ... other EVM chains + { + const provider = new BrowserProvider(ethereumWindowProvider, "any"); + const signer = await provider.getSigner(); + const toolbox = await getEvmToolbox(chain, { provider, signer }); + const signingMethods = await getEVMSigningMethods(provider, chain); + + return { + ...toolbox, + ...ctrlMethods, + ...signingMethods, + }; + } + + // Solana - full signing support + case Chain.Solana: { + const solanaProvider = window.xfi?.solana; + const toolbox = getSolanaToolbox({ signer: solanaProvider }); + const signingMethods = getSolanaSigningMethods(solanaProvider); + + return { + ...toolbox, + ...signingMethods, + }; + } + } +} +``` + +### Phase 4: Error Handling & User Guidance + +Create clear error messages when unsupported operations are attempted: + +```typescript +export class CTRLWalletError extends SwapKitError { + constructor(operation: string, chain: Chain, alternative?: string) { + super({ + errorKey: "wallet_ctrl_operation_not_supported", + info: { + operation, + chain, + message: `CTRL wallet does not support ${operation} for ${chain}`, + alternative: alternative || "Use the transfer method instead", + documentation: "https://docs.swapkit.dev/wallets/ctrl#limitations", + }, + }); + } +} +``` + +### Phase 5: Documentation + +Create comprehensive documentation: + +1. **Capabilities Matrix** + ```markdown + ## CTRL Wallet Capabilities + + | Chain | Sign | Sign & Broadcast | Transfer | Build Tx | + |-------|------|-----------------|----------|----------| + | Bitcoin | ❌ | ❌ | ✅ | ✅ | + | Ethereum | ✅ | ✅ | ✅ | ✅ | + | Solana | ✅ | ✅ | ✅ | ✅ | + ``` + +2. **Migration Guide** + ```typescript + // Before (attempting PSBT signing) + try { + const signed = await wallet.signPSBT(psbt); + } catch (error) { + // Handle error + } + + // After (using CTRL transfer) + if (wallet.type === WalletOption.CTRL && isUTXOChain(chain)) { + // Use transfer API directly + const txHash = await wallet.transfer(params); + } else { + // Standard signing flow + const signed = await wallet.signPSBT(psbt); + } + ``` + +3. **Best Practices** + - Always check wallet capabilities before attempting operations + - Provide fallback mechanisms for unsupported operations + - Guide users to alternative methods when signing isn't available + +## Testing Strategy + +### Unit Tests +```typescript +describe("CTRL Wallet", () => { + describe("UTXO Chains", () => { + it("should throw informative error for PSBT signing", async () => { + const wallet = await connectCtrl([Chain.Bitcoin]); + await expect(wallet.signPSBT(psbt)).rejects.toThrow( + "CTRL wallet does not support PSBT signing" + ); + }); + + it("should successfully transfer using CTRL API", async () => { + const wallet = await connectCtrl([Chain.Bitcoin]); + const txHash = await wallet.transfer(params); + expect(txHash).toBeDefined(); + }); + }); + + describe("EVM Chains", () => { + it("should sign transactions", async () => { + const wallet = await connectCtrl([Chain.Ethereum]); + const signed = await wallet.signTransaction(tx); + expect(signed).toBeDefined(); + }); + }); +}); +``` + +### Integration Tests +- Test with CTRL wallet browser extension +- Verify transfer operations work correctly +- Ensure error messages are helpful +- Test fallback mechanisms + +## Timeline + +### Week 1: Foundation +- Day 1-2: Implement capability detection system +- Day 3-4: Create EVM and Solana signing methods +- Day 5: Build UTXO transaction builder + +### Week 2: Integration +- Day 1-2: Update CTRL wallet integration +- Day 3: Implement error handling +- Day 4-5: Write documentation + +### Week 3: Testing & Refinement +- Day 1-2: Unit tests +- Day 3: Integration tests +- Day 4-5: User testing and feedback + +## Future Enhancements + +### Short-term (1-2 months) +1. Work with CTRL team to request PSBT signing API +2. Implement transaction status monitoring +3. Add transaction fee estimation for CTRL transfers + +### Long-term (3-6 months) +1. Full PSBT signing support (pending CTRL API) +2. Batch transaction support +3. Advanced transaction building with custom scripts + +## Risk Mitigation + +### Technical Risks +- **Missing PSBT signing**: Users expect signing capability + - *Mitigation*: Clear documentation, helpful errors, transfer fallback + +- **API changes**: CTRL might change their API + - *Mitigation*: Version detection, compatibility layer + +### User Experience Risks +- **Confusion about capabilities**: Users don't understand limitations + - *Mitigation*: Clear UI indicators, capability checks, guided flows + +## Conclusion + +While CTRL wallet doesn't expose direct signing for UTXO chains, we can provide a functional implementation that: +1. Uses CTRL's transfer API for UTXO operations +2. Provides full signing for EVM and Solana +3. Clearly communicates limitations +4. Guides users to appropriate alternatives + +This approach maintains SwapKit's consistent interface while working within CTRL's constraints. Future CTRL API enhancements could enable full signing support. \ No newline at end of file diff --git a/SIGNING_IMPLEMENTATION_PLAN.md b/SIGNING_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000000..2fce2a03bb --- /dev/null +++ b/SIGNING_IMPLEMENTATION_PLAN.md @@ -0,0 +1,305 @@ +# SwapKit Universal Signing Implementation Plan + +## Executive Summary +Implement standardized `sign` and `signAndBroadcast` methods across all toolboxes and wallets to support: +- **PSBT signing** for UTXO chains +- **EVM transaction signing** for EVM chains +- **Base64 encoded versioned transaction signing** for Solana + +## Current State Analysis + +### UTXO Chains (Bitcoin, Litecoin, Dogecoin, BitcoinCash, Zcash) +- ✅ Have `createTransaction` that builds PSBTs +- ✅ Have internal `signer.signTransaction(psbt)` capability +- ❌ Missing exposed `sign` method for PSBT-only signing +- ❌ Missing exposed `signAndBroadcast` method +- ✅ Have `transfer` that combines sign + broadcast + +### EVM Chains (Ethereum, BSC, Avalanche, Polygon, Arbitrum, Optimism, Base) +- ✅ Have `sendTransaction` that handles sign + broadcast +- ✅ Have internal `signer.signTransaction` capability +- ❌ Missing exposed `sign` method for transaction objects +- ❌ Missing exposed `signAndBroadcast` method +- ✅ Have `transfer` that combines sign + broadcast + +### Solana +- ✅ Have `signTransaction` method exposed +- ✅ Have `broadcastTransaction` method exposed +- ❌ Missing unified `signAndBroadcast` method +- ✅ Supports versioned transactions + +## Implementation Tasks + +### Phase 1: Core Toolbox Updates + +#### 1.1 UTXO Toolbox Enhancement +**Files to modify:** +- `packages/toolboxes/src/utxo/toolbox/utxo.ts` +- `packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts` +- `packages/toolboxes/src/utxo/toolbox/zcash.ts` +- `packages/toolboxes/src/utxo/types.ts` + +**Tasks:** +1. Add `sign` method to toolbox interface + ```typescript + sign: (psbt: Psbt) => Promise + ``` +2. Add `signAndBroadcast` method + ```typescript + signAndBroadcast: (psbt: Psbt) => Promise + ``` +3. Update type definitions in `utxo/types.ts` +4. Implement methods in each UTXO toolbox variant + +#### 1.2 EVM Toolbox Enhancement +**Files to modify:** +- `packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` +- `packages/toolboxes/src/evm/toolbox/evm.ts` +- `packages/toolboxes/src/evm/toolbox/op.ts` +- `packages/toolboxes/src/evm/types.ts` + +**Tasks:** +1. Add `sign` method to toolbox interface + ```typescript + sign: (tx: EVMTxParams) => Promise + ``` +2. Add `signAndBroadcast` method + ```typescript + signAndBroadcast: (tx: EVMTxParams) => Promise + ``` +3. Refactor existing `sendTransaction` to use new methods +4. Update type definitions + +#### 1.3 Solana Toolbox Enhancement +**Files to modify:** +- `packages/toolboxes/src/solana/toolbox.ts` +- `packages/toolboxes/src/solana/index.ts` + +**Tasks:** +1. Keep existing `signTransaction` as `sign` for consistency + ```typescript + sign: ( + transaction: Transaction | VersionedTransaction + ) => Promise + ``` +2. Add unified `signAndBroadcast` method + ```typescript + signAndBroadcast: ( + transaction: Transaction | VersionedTransaction + ) => Promise + ``` +3. Ensure versioned transaction support is documented +4. Add base64 encoding/decoding utilities + +### Phase 2: Wallet Integration Updates + +#### 2.1 Hardware Wallets +**Wallets:** Ledger, Trezor, KeepKey + +**Tasks per wallet:** +1. Implement PSBT signing for UTXO chains +2. Implement EVM transaction signing +3. Add Solana support where applicable +4. Update wallet interfaces + +#### 2.2 Browser Extension Wallets +**Wallets:** MetaMask, Coinbase, OKX, Phantom, Talisman, Exodus + +**Tasks per wallet:** +1. Expose native signing methods through standardized interface +2. Handle chain-specific signing requirements +3. Add proper error handling and user cancellation + +#### 2.3 Mobile Wallets +**Wallets:** WalletConnect, Xaman, Coinbase Mobile, OKX Mobile + +**Tasks per wallet:** +1. Implement QR code or deep-link based signing +2. Handle async signing flows +3. Add timeout and retry logic + +#### 2.4 Keystore Wallet +**Files:** `packages/wallets/src/keystore/` + +**Tasks:** +1. Implement all signing methods directly +2. Add secure key management +3. Support all chain types + +### Phase 3: Type System Updates + +#### 3.1 Core Types +**Files to modify:** +- `packages/helpers/src/types/wallet.ts` +- `packages/toolboxes/src/types.ts` + +**New interfaces:** +```typescript +interface SigningCapabilities { + // Unified signing interface - same method names across all chains + sign?: ( + transaction: Psbt | EVMTxParams | Transaction | VersionedTransaction + ) => Promise; + + signAndBroadcast?: ( + transaction: Psbt | EVMTxParams | Transaction | VersionedTransaction + ) => Promise; +} +``` + +### Phase 4: Testing & Documentation + +#### 4.1 Unit Tests +**Files to create/modify:** +- `packages/toolboxes/src/utxo/__tests__/signing.test.ts` +- `packages/toolboxes/src/evm/__tests__/signing.test.ts` +- `packages/toolboxes/src/solana/__tests__/signing.test.ts` + +#### 4.2 Integration Tests +- Test each wallet with each supported chain +- Test error scenarios (user rejection, timeout, etc.) +- Test with mainnet forks + +#### 4.3 Documentation +- Update API documentation +- Create migration guide for existing integrations +- Add code examples for each signing method + +## Implementation Order + +### Week 1: Core Toolbox Updates +1. **Day 1-2:** UTXO toolbox signing methods +2. **Day 3-4:** EVM toolbox signing methods +3. **Day 5:** Solana toolbox refinements + +### Week 2: Wallet Integrations (Priority 1) +1. **Day 1-2:** Ledger wallet +2. **Day 3:** MetaMask & Coinbase wallets +3. **Day 4:** Phantom wallet +4. **Day 5:** WalletConnect + +### Week 3: Wallet Integrations (Priority 2) +1. **Day 1:** OKX wallet +2. **Day 2:** Trezor & KeepKey +3. **Day 3:** Keystore wallet +4. **Day 4-5:** Remaining wallets + +### Week 4: Testing & Documentation +1. **Day 1-2:** Unit test implementation +2. **Day 3:** Integration test implementation +3. **Day 4-5:** Documentation and examples + +## Success Criteria + +1. **Consistency:** All toolboxes expose the same signing interface +2. **Compatibility:** All wallets work with all supported chains +3. **Type Safety:** Full TypeScript coverage with no `any` types +4. **Testing:** >90% code coverage for new methods +5. **Documentation:** Complete API docs and migration guide + +## Risk Mitigation + +### Technical Risks +- **Hardware wallet limitations:** Some hardware wallets may not support all chains + - *Mitigation:* Document supported chains per wallet + +- **Breaking changes:** Existing integrations might break + - *Mitigation:* Keep existing methods, deprecate gradually + +- **Async signing flows:** Mobile wallets have complex async patterns + - *Mitigation:* Implement robust timeout and retry logic + +### Timeline Risks +- **Wallet vendor changes:** External wallet APIs might change + - *Mitigation:* Pin wallet SDK versions, monitor changelogs + +- **Testing complexity:** Testing all combinations is time-consuming + - *Mitigation:* Prioritize critical paths, use test matrices + +## Monitoring & Rollout + +### Rollout Strategy +1. **Beta release:** Release as `@next` tag for early adopters +2. **Gradual migration:** Provide compatibility layer +3. **Full release:** After 2 weeks of beta testing + +### Monitoring +- Track signing success rates per wallet/chain +- Monitor error rates and types +- Collect user feedback through GitHub issues + +## Appendix: Detailed File Changes + +### UTXO Toolbox Changes + +#### `/packages/toolboxes/src/utxo/toolbox/utxo.ts` +```typescript +// Add to toolbox return object: +sign: async (psbt: Psbt): Promise => { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + return signedPsbt; +}, + +signAndBroadcast: async (psbt: Psbt): Promise => { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + signedPsbt.finalizeAllInputs(); + const txHex = signedPsbt.extractTransaction().toHex(); + return broadcastTx(txHex); +}, +``` + +### EVM Toolbox Changes + +#### `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` +```typescript +// Add to BaseEVMToolbox return object: +sign: async (tx: EVMTxParams): Promise => { + if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); + const signedTx = await signer.signTransaction(tx); + return signedTx; +}, + +signAndBroadcast: async (tx: EVMTxParams): Promise => { + const signedTx = await sign(tx); + const response = await provider.broadcastTransaction(signedTx); + return response.hash; +}, +``` + +### Solana Toolbox Changes + +#### `/packages/toolboxes/src/solana/toolbox.ts` +```typescript +// Note: signTransaction already exists, rename it to sign for consistency +// Add to toolbox return object: +sign: signTransaction(getConnection, signer), // Alias existing signTransaction + +signAndBroadcast: async ( + transaction: Transaction | VersionedTransaction +): Promise => { + const signedTx = await signTransaction(getConnection, signer)(transaction); + return broadcastTransaction(getConnection)(signedTx); +}, + +// Add base64 utilities: +serializeTransaction: (tx: Transaction | VersionedTransaction): string => { + return Buffer.from(tx.serialize()).toString('base64'); +}, + +deserializeTransaction: ( + base64: string +): Transaction | VersionedTransaction => { + const buffer = Buffer.from(base64, 'base64'); + // Detect and deserialize based on version + return VersionedTransaction.deserialize(buffer); +}, +``` + +## Notes + +- All signing methods should be optional on interfaces to maintain backward compatibility +- Error messages should be chain-specific for better debugging +- Consider adding signing method capability detection +- Hardware wallet support matrix should be documented clearly diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index 30a4c8e13b..27c955ee75 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -86,6 +86,33 @@ export function BaseEVMToolbox< signMessage: signer?.signMessage, transfer: getTransfer({ provider, signer, isEIP1559Compatible, chain }), validateAddress: (address: string) => evmValidateAddress({ address }), + + // New unified signing methods for EVM + sign: async (tx: EVMTxParams): Promise => { + if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); + const { from, to, data, value, ...transaction } = tx; + if (!to) throw new SwapKitError("toolbox_evm_no_to_address"); + + const address = from || (await signer.getAddress()); + const nonce = tx.nonce || (await provider.getTransactionCount(address)); + const chainId = (await provider.getNetwork()).chainId; + + const parsedTxObject = { + ...transaction, + data: data || "0x", + to, + from: address, + value: BigInt(value || 0), + chainId, + nonce, + type: isEIP1559Compatible ? 2 : 0, + }; + + const signedTx = await signer.signTransaction(parsedTxObject); + return signedTx; + }, + + signAndBroadcast: getSendTransaction({ provider, signer, isEIP1559Compatible, chain }), }; } diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index b974601899..b073cac0ef 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -145,6 +145,14 @@ export async function getSolanaToolbox( getAddressValidator: getSolanaAddressValidator, signTransaction: signTransaction(getConnection, signer), estimateTransactionFee: estimateTransactionFee(getConnection), + + // New unified signing methods for Solana + sign: signTransaction(getConnection, signer), + + signAndBroadcast: async (transaction: Transaction | VersionedTransaction): Promise => { + const signedTx = await signTransaction(getConnection, signer)(transaction); + return broadcastTransaction(getConnection)(signedTx); + }, }; } diff --git a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts index 8b2b01d461..d4f31c214d 100644 --- a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts +++ b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts @@ -128,6 +128,23 @@ export async function createBCHToolbox( stripToCashAddress, validateAddress: bchValidateAddress, transfer: transfer({ getFeeRates, broadcastTx, signer }), + + // New unified signing methods for BCH + sign: async ({ builder, utxos }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction({ builder, utxos }); + return signedTx; + }, + + signAndBroadcast: async ({ + builder, + utxos, + }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction({ builder, utxos }); + const txHex = signedTx.toHex(); + return broadcastTx(txHex); + }, }; } diff --git a/packages/toolboxes/src/utxo/toolbox/utxo.ts b/packages/toolboxes/src/utxo/toolbox/utxo.ts index 8bc36ad3d7..4aaadc14d4 100644 --- a/packages/toolboxes/src/utxo/toolbox/utxo.ts +++ b/packages/toolboxes/src/utxo/toolbox/utxo.ts @@ -244,6 +244,37 @@ export async function createUTXOToolbox({ return keys.toWIF(); }, + // New unified signing methods + sign: async (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 as ChainSigner).signTransaction(psbt); + return signedPsbt; + } + // BCH uses a different transaction type, so we can't support PSBT signing + throw new SwapKitError("toolbox_utxo_invalid_params", { + error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific sign method.", + chain, + }); + }, + + signAndBroadcast: async (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 as ChainSigner).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", { + error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific sign method.", + chain, + }); + }, + getBalance: getBalance(chain), estimateTransactionFee: estimateTransactionFee(chain), estimateMaxSendableAmount: estimateMaxSendableAmount(chain), diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index 1637ce20fc..12b5eae55f 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -322,5 +322,20 @@ export async function createZcashToolbox( createKeysForPath, getPrivateKeyFromMnemonic, validateAddress: validateZcashAddress, + + // New unified signing methods for Zcash + sign: async (psbt: ZcashPsbt): Promise => { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedPsbt = await signer.signTransaction(psbt); + return signedPsbt; + }, + + signAndBroadcast: async (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); + }, }; } From 92ee149a586392c5831cd9755c0f8d608a9a8c1e Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 13:49:19 +0400 Subject: [PATCH 02/15] refactor: extract EVM sign methods into separate functions - Create getSignTransaction and getSignAndBroadcastTransaction functions - Use ethers.js TransactionRequest type for incoming transactions - Ensure sign and signAndBroadcast have matching signatures - Clean separation of concerns for signing logic --- .../src/evm/toolbox/baseEVMToolbox.ts | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index 27c955ee75..a96ecdce3c 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -23,6 +23,7 @@ import { type JsonRpcSigner, type Provider, type Signer, + type TransactionRequest, getAddress, } from "ethers"; @@ -50,6 +51,35 @@ export const MAX_APPROVAL = BigInt( "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ); +function getSignTransaction({ signer }: { signer?: Signer }) { + return async function signTransaction(tx: TransactionRequest): Promise { + if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); + + // Sign the transaction without broadcasting + const signedTx = await signer.signTransaction(tx); + return signedTx; + }; +} + +function getSignAndBroadcastTransaction({ + provider, + signer, +}: { + provider: Provider | BrowserProvider; + signer?: Signer; +}) { + return async function signAndBroadcastTransaction(tx: TransactionRequest): Promise { + if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); + + // Sign the transaction + const signedTx = await signer.signTransaction(tx); + + // Broadcast the signed transaction + const response = await provider.broadcastTransaction(signedTx); + return response.hash; + }; +} + export function BaseEVMToolbox< P extends Provider | BrowserProvider, S extends @@ -87,32 +117,9 @@ export function BaseEVMToolbox< transfer: getTransfer({ provider, signer, isEIP1559Compatible, chain }), validateAddress: (address: string) => evmValidateAddress({ address }), - // New unified signing methods for EVM - sign: async (tx: EVMTxParams): Promise => { - if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); - const { from, to, data, value, ...transaction } = tx; - if (!to) throw new SwapKitError("toolbox_evm_no_to_address"); - - const address = from || (await signer.getAddress()); - const nonce = tx.nonce || (await provider.getTransactionCount(address)); - const chainId = (await provider.getNetwork()).chainId; - - const parsedTxObject = { - ...transaction, - data: data || "0x", - to, - from: address, - value: BigInt(value || 0), - chainId, - nonce, - type: isEIP1559Compatible ? 2 : 0, - }; - - const signedTx = await signer.signTransaction(parsedTxObject); - return signedTx; - }, - - signAndBroadcast: getSendTransaction({ provider, signer, isEIP1559Compatible, chain }), + // New unified signing methods for EVM using ethers TransactionRequest + sign: getSignTransaction({ signer }), + signAndBroadcast: getSignAndBroadcastTransaction({ provider, signer }), }; } From 673fd15c974b4166e680fc3442060f73271ef6cc Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 13:52:24 +0400 Subject: [PATCH 03/15] refactor: rename signing methods for clarity - Rename 'sign' to 'signTransaction' across all toolboxes - Rename 'signAndBroadcast' to 'signAndSendTransaction' across all toolboxes - Better naming convention that clearly indicates the action being performed - Consistent naming across UTXO, EVM, and Solana toolboxes --- packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts | 4 ++-- packages/toolboxes/src/solana/toolbox.ts | 9 +++++---- packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts | 7 +++++-- packages/toolboxes/src/utxo/toolbox/utxo.ts | 10 ++++++---- packages/toolboxes/src/utxo/toolbox/zcash.ts | 4 ++-- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index a96ecdce3c..333b393588 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -118,8 +118,8 @@ export function BaseEVMToolbox< validateAddress: (address: string) => evmValidateAddress({ address }), // New unified signing methods for EVM using ethers TransactionRequest - sign: getSignTransaction({ signer }), - signAndBroadcast: getSignAndBroadcastTransaction({ provider, signer }), + signTransaction: getSignTransaction({ signer }), + signAndSendTransaction: getSignAndBroadcastTransaction({ provider, signer }), }; } diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index b073cac0ef..48dfc01a65 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -146,10 +146,11 @@ export async function getSolanaToolbox( signTransaction: signTransaction(getConnection, signer), estimateTransactionFee: estimateTransactionFee(getConnection), - // New unified signing methods for Solana - sign: signTransaction(getConnection, signer), - - signAndBroadcast: async (transaction: Transaction | VersionedTransaction): Promise => { + // Keep original signTransaction for backward compatibility + // New unified naming for consistency across toolboxes + signAndSendTransaction: async ( + transaction: Transaction | VersionedTransaction, + ): Promise => { const signedTx = await signTransaction(getConnection, signer)(transaction); return broadcastTransaction(getConnection)(signedTx); }, diff --git a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts index d4f31c214d..fbe69c4a79 100644 --- a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts +++ b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts @@ -130,13 +130,16 @@ export async function createBCHToolbox( transfer: transfer({ getFeeRates, broadcastTx, signer }), // New unified signing methods for BCH - sign: async ({ builder, utxos }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { + signTransaction: async ({ + builder, + utxos, + }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); const signedTx = await signer.signTransaction({ builder, utxos }); return signedTx; }, - signAndBroadcast: async ({ + signAndSendTransaction: async ({ builder, utxos, }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { diff --git a/packages/toolboxes/src/utxo/toolbox/utxo.ts b/packages/toolboxes/src/utxo/toolbox/utxo.ts index 4aaadc14d4..5c2580ba01 100644 --- a/packages/toolboxes/src/utxo/toolbox/utxo.ts +++ b/packages/toolboxes/src/utxo/toolbox/utxo.ts @@ -245,7 +245,7 @@ export async function createUTXOToolbox({ }, // New unified signing methods - sign: async (psbt: Psbt): Promise => { + signTransaction: async (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) { @@ -254,12 +254,13 @@ export async function createUTXOToolbox({ } // BCH uses a different transaction type, so we can't support PSBT signing throw new SwapKitError("toolbox_utxo_invalid_params", { - error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific sign method.", + error: + "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", chain, }); }, - signAndBroadcast: async (psbt: Psbt): Promise => { + signAndSendTransaction: async (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) { @@ -270,7 +271,8 @@ export async function createUTXOToolbox({ } // BCH uses a different transaction type, so we can't support PSBT signing throw new SwapKitError("toolbox_utxo_invalid_params", { - error: "PSBT signing is not supported for BitcoinCash. Use the BCH-specific sign method.", + error: + "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", chain, }); }, diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index 12b5eae55f..e61290640f 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -324,13 +324,13 @@ export async function createZcashToolbox( validateAddress: validateZcashAddress, // New unified signing methods for Zcash - sign: async (psbt: ZcashPsbt): Promise => { + signTransaction: async (psbt: ZcashPsbt): Promise => { if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); const signedPsbt = await signer.signTransaction(psbt); return signedPsbt; }, - signAndBroadcast: async (psbt: ZcashPsbt): Promise => { + signAndSendTransaction: async (psbt: ZcashPsbt): Promise => { if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); const signedPsbt = await signer.signTransaction(psbt); signedPsbt.finalizeAllInputs(); From a09e90ab491375dc05d522f77101c6d288a8feab Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 14:11:04 +0400 Subject: [PATCH 04/15] chore: renames toolbox methods for signingn --- .../src/evm/toolbox/baseEVMToolbox.ts | 2 +- packages/toolboxes/src/solana/toolbox.ts | 4 +- .../toolboxes/src/utxo/toolbox/bitcoinCash.ts | 53 ++++++++----- packages/toolboxes/src/utxo/toolbox/utxo.ts | 78 +++++++++++-------- packages/toolboxes/src/utxo/toolbox/zcash.ts | 33 ++++---- 5 files changed, 104 insertions(+), 66 deletions(-) diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index 333b393588..8db3eae50d 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -119,7 +119,7 @@ export function BaseEVMToolbox< // New unified signing methods for EVM using ethers TransactionRequest signTransaction: getSignTransaction({ signer }), - signAndSendTransaction: getSignAndBroadcastTransaction({ provider, signer }), + signAndBroadcastTransaction: getSignAndBroadcastTransaction({ provider, signer }), }; } diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index 48dfc01a65..03c02e9e94 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -148,9 +148,7 @@ export async function getSolanaToolbox( // Keep original signTransaction for backward compatibility // New unified naming for consistency across toolboxes - signAndSendTransaction: async ( - transaction: Transaction | VersionedTransaction, - ): Promise => { + signAndBroadcastTransaction: async (transaction: Transaction | VersionedTransaction) => { const signedTx = await signTransaction(getConnection, signer)(transaction); return broadcastTransaction(getConnection)(signedTx); }, diff --git a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts index fbe69c4a79..61e6d11531 100644 --- a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts +++ b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts @@ -115,6 +115,39 @@ export async function createBCHToolbox( return getBalance(stripPrefix(toCashAddress(address))); } + function getSignTransaction( + signer?: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType>, + ) { + return async function signTransaction({ + builder, + utxos, + }: { builder: TransactionBuilderType; utxos: UTXOType[] }): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction({ builder, utxos }); + return signedTx; + }; + } + + function getSignAndBroadcastTransaction({ + signer, + broadcastTx, + }: { + signer: + | ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType> + | undefined; + broadcastTx: (txHash: string) => Promise; + }) { + return async function signAndBroadcastTransaction({ + builder, + utxos, + }: { builder: TransactionBuilderType; utxos: UTXOType[] }): Promise { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction({ builder, utxos }); + const txHex = signedTx.toHex(); + return broadcastTx(txHex); + }; + } + return { ...toolbox, getAddress, @@ -130,24 +163,8 @@ export async function createBCHToolbox( transfer: transfer({ getFeeRates, broadcastTx, signer }), // New unified signing methods for BCH - signTransaction: async ({ - builder, - utxos, - }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedTx = await signer.signTransaction({ builder, utxos }); - return signedTx; - }, - - signAndSendTransaction: async ({ - builder, - utxos, - }: { builder: TransactionBuilderType; utxos: UTXOType[] }) => { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedTx = await signer.signTransaction({ builder, utxos }); - const txHex = signedTx.toHex(); - return broadcastTx(txHex); - }, + signTransaction: getSignTransaction(signer), + signAndBroadcastTransaction: getSignAndBroadcastTransaction({ signer, broadcastTx }), }; } diff --git a/packages/toolboxes/src/utxo/toolbox/utxo.ts b/packages/toolboxes/src/utxo/toolbox/utxo.ts index 5c2580ba01..bee0beb830 100644 --- a/packages/toolboxes/src/utxo/toolbox/utxo.ts +++ b/packages/toolboxes/src/utxo/toolbox/utxo.ts @@ -191,6 +191,48 @@ async function createSignerWithKeys({ }; } +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", { + error: + "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", + chain, + }); + }; +} + +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", { + error: + "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", + chain, + }); + }; +} + export async function createUTXOToolbox({ chain, ...toolboxParams @@ -245,37 +287,11 @@ export async function createUTXOToolbox({ }, // New unified signing methods - signTransaction: async (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 as ChainSigner).signTransaction(psbt); - return signedPsbt; - } - // BCH uses a different transaction type, so we can't support PSBT signing - throw new SwapKitError("toolbox_utxo_invalid_params", { - error: - "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", - chain, - }); - }, - - signAndSendTransaction: async (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 as ChainSigner).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", { - error: - "PSBT signing is not supported for BitcoinCash. Use the BCH-specific signTransaction method.", - chain, - }); - }, + signTransaction: getSignTransaction({ chain, signer: signer as ChainSigner }), + signAndBroadcastTransaction: getSignAndBroadcastTransaction({ + chain, + signer: signer as ChainSigner, + }), getBalance: getBalance(chain), estimateTransactionFee: estimateTransactionFee(chain), diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index e61290640f..094daf0048 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -315,27 +315,34 @@ export async function createZcashToolbox( return keys.toWIF(); } - return { - ...baseToolbox, - transfer, - createTransaction, - createKeysForPath, - getPrivateKeyFromMnemonic, - validateAddress: validateZcashAddress, - - // New unified signing methods for Zcash - signTransaction: async (psbt: ZcashPsbt): Promise => { + 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; - }, + }; + } - signAndSendTransaction: async (psbt: ZcashPsbt): Promise => { + 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, + transfer, + createTransaction, + createKeysForPath, + getPrivateKeyFromMnemonic, + validateAddress: validateZcashAddress, + + // New unified signing methods for Zcash + signTransaction: getSignTransaction(signer), + signAndBroadcastTransaction: getSignAndBroadcastTransaction(signer), }; } From 5f93a49c4ac891e70bd311b3ee7112486de07d08 Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 15:41:00 +0400 Subject: [PATCH 05/15] chore: adds swapkit plugin --- SOLUTION_SUMMARY.md | 136 +++++++++ packages/plugins/src/swapkit/USAGE.md | 186 ++++++++++++ packages/plugins/src/swapkit/index.ts | 2 + packages/plugins/src/swapkit/plugin.ts | 285 ++++++++++++++++++ packages/plugins/src/swapkit/types.ts | 51 ++++ .../src/swapkit/wallet-signing-analysis.md | 113 +++++++ packages/plugins/src/types.ts | 3 + packages/plugins/src/utils.ts | 4 + 8 files changed, 780 insertions(+) create mode 100644 SOLUTION_SUMMARY.md create mode 100644 packages/plugins/src/swapkit/USAGE.md create mode 100644 packages/plugins/src/swapkit/index.ts create mode 100644 packages/plugins/src/swapkit/plugin.ts create mode 100644 packages/plugins/src/swapkit/types.ts create mode 100644 packages/plugins/src/swapkit/wallet-signing-analysis.md diff --git a/SOLUTION_SUMMARY.md b/SOLUTION_SUMMARY.md new file mode 100644 index 0000000000..6185bb6678 --- /dev/null +++ b/SOLUTION_SUMMARY.md @@ -0,0 +1,136 @@ +# SwapKit API Integration Solution Summary + +## Overview + +We've implemented a comprehensive solution for integrating the SwapKit API with wallet signing capabilities across all supported wallets. The solution intelligently handles wallets with and without transaction signing support. + +## What Was Accomplished + +### 1. Unified Transaction Signing Interface + +- Added `signTransaction` and `signAndBroadcastTransaction` methods to all toolboxes +- Extracted signing logic into separate functions for cleaner code architecture +- Maintained backward compatibility with existing implementations + +### 2. Wallet Analysis + +Identified wallets that need special handling: + +**Wallets WITHOUT signing support:** +- CTRL Wallet +- Vultisig Wallet +- KeepKey-BEX Wallet + +**Wallets WITH signing support:** +- Exodus, Phantom, OKX, OneKey, TronLink +- WalletConnect, Coinbase, Bitget +- Hardware wallets (Ledger, Trezor) +- All standard EVM wallets (MetaMask, etc.) +- Keplr, Cosmostation, and other chain-specific wallets + +### 3. SwapKit Plugin Implementation + +Created a new SwapKit plugin (`packages/plugins/src/swapkit/`) with: + +- **Quote method**: Fetches quotes from SwapKit API +- **Swap method**: Executes swaps with intelligent routing: + - Primary: Uses `signAndBroadcastTransaction` for supported wallets + - Fallback: Uses `transfer` method for non-supporting wallets +- **Automatic detection** of wallet capabilities +- **Multi-chain support** for all major blockchain types + +## How It Works + +### Flow Diagram + +``` +User Request Swap + ↓ +SwapKit API Quote + ↓ +Get Transaction Data + ↓ +Check Wallet Type + ↓ + ┌───┴───┐ + │ │ +Supports Doesn't +Signing Support + │ │ + ↓ ↓ +signAnd transfer() +Broadcast with API +Transaction params + │ │ + └───┬───┘ + ↓ +Return TX Hash +``` + +### Implementation Details + +1. **For Supporting Wallets**: + - SwapKit API provides built transaction (PSBT, EVM tx, Solana tx) + - Plugin calls `wallet.signAndBroadcastTransaction(tx)` + - Transaction is signed and broadcast + +2. **For Non-Supporting Wallets**: + - SwapKit API provides transfer parameters + - Plugin calls `wallet.transfer(params)` + - Wallet executes transfer with memo/data as needed + +## Key Files Modified + +### Toolboxes +- `/packages/toolboxes/src/utxo/toolbox/utxo.ts` - UTXO signing methods +- `/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts` - BCH signing methods +- `/packages/toolboxes/src/utxo/toolbox/zcash.ts` - Zcash signing methods +- `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` - EVM signing methods +- `/packages/toolboxes/src/solana/toolbox.ts` - Solana signing methods + +### Plugin Files (New) +- `/packages/plugins/src/swapkit/plugin.ts` - Main plugin implementation +- `/packages/plugins/src/swapkit/types.ts` - TypeScript types +- `/packages/plugins/src/swapkit/index.ts` - Module exports +- `/packages/plugins/src/swapkit/USAGE.md` - Usage documentation + +### Documentation +- `/packages/plugins/swapkit/wallet-signing-analysis.md` - Wallet capabilities analysis + +## Benefits + +1. **Unified Interface**: All wallets now have consistent `signTransaction` and `signAndBroadcastTransaction` methods +2. **Automatic Fallback**: Wallets without signing automatically use transfer method +3. **No Breaking Changes**: Existing code continues to work +4. **Future-Proof**: Easy to add new wallets or modify behavior +5. **Developer-Friendly**: Simple API for executing swaps regardless of wallet type + +## Usage Example + +```typescript +// Initialize with plugin +const swapKit = createSwapKit({ + plugins: { ...SwapKitPlugin } +}); + +// Get quote +const quote = await swapKit.swapkit.quote({ + sellAsset: 'ETH.ETH', + buyAsset: 'BTC.BTC', + sellAmount: '1000000000000000000' +}); + +// Execute swap (works with ANY wallet) +const txHash = await swapKit.swapkit.swap({ route: quote }); +``` + +## Next Steps + +1. **Testing**: Test with each wallet type to ensure proper behavior +2. **SwapKit API Integration**: Ensure API returns proper transaction data and transfer params +3. **Error Handling**: Add specific error messages for each failure case +4. **Monitoring**: Add telemetry to track which wallets use which method + +## Conclusion + +This solution provides a robust, maintainable way to integrate the SwapKit API with all supported wallets, automatically handling the differences in wallet capabilities while maintaining a clean, unified interface for developers. \ No newline at end of file diff --git a/packages/plugins/src/swapkit/USAGE.md b/packages/plugins/src/swapkit/USAGE.md new file mode 100644 index 0000000000..b424089c1f --- /dev/null +++ b/packages/plugins/src/swapkit/USAGE.md @@ -0,0 +1,186 @@ +# SwapKit Plugin Usage Guide + +The SwapKit plugin enables swapping through the SwapKit API with automatic handling of transaction signing and broadcasting. + +## Features + +- **Quote fetching** from SwapKit API +- **Automatic transaction signing** for supported wallets +- **Fallback to transfer method** for wallets without signing support (CTRL, Vultisig, KeepKey-BEX) +- **Multi-chain support** (Bitcoin, Ethereum, Solana, Cosmos, etc.) + +## Basic Usage + +```typescript +import { createSwapKit } from '@swapkit/core'; +import { SwapKitPlugin } from '@swapkit/plugins/swapkit'; + +// Initialize SwapKit with the plugin +const swapKit = createSwapKit({ + plugins: { + ...SwapKitPlugin, + }, + config: { + // Your config + } +}); + +// Connect wallet +await swapKit.connectWallet(WalletOption.METAMASK, [Chain.Ethereum]); + +// Get a quote +const quote = await swapKit.swapkit.quote({ + sellAsset: 'ETH.ETH', + buyAsset: 'BTC.BTC', + sellAmount: '1000000000000000000', // 1 ETH in wei + sourceAddress: '0x...', // Your ETH address + destinationAddress: 'bc1q...', // Your BTC address + slippage: 3, // 3% slippage + affiliate: 'YOUR_AFFILIATE', + affiliateBasisPoints: 100, // 1% +}); + +// Execute the swap +const txHash = await swapKit.swapkit.swap({ + route: quote, + recipient: 'bc1q...', // Optional, uses route's destination by default +}); + +console.log('Swap transaction:', txHash); +``` + +## Wallet-Specific Behavior + +### Wallets with Full Signing Support + +For wallets like MetaMask, Ledger, Trezor, Exodus, Phantom, OKX, OneKey, etc.: + +```typescript +// The plugin will automatically use signAndBroadcastTransaction +const txHash = await swapKit.swapkit.swap({ route }); +// Transaction is signed and broadcast directly +``` + +### Wallets without Signing Support + +For wallets like CTRL, Vultisig, and KeepKey-BEX: + +```typescript +// The plugin automatically falls back to the transfer method +const txHash = await swapKit.swapkit.swap({ route }); +// Behind the scenes, it uses wallet.transfer() with params from SwapKit API +``` + +## Advanced Usage + +### Checking Wallet Capabilities + +```typescript +// Check if a wallet supports signing +const supportsSigniing = swapKit.swapkit.walletSupportsSignAndBroadcast(WalletOption.CTRL); +console.log('CTRL supports signing:', supportsSigniing); // false +``` + +### Override SignAndBroadcast for Non-Supporting Wallets + +```typescript +// Get wallet instance +const wallet = swapKit.getWallet(Chain.Bitcoin); + +// Override signAndBroadcastTransaction if needed +const enhancedWallet = swapKit.swapkit.overrideSignAndBroadcastForWallet( + wallet, + Chain.Bitcoin +); +``` + +## Transaction Types by Chain + +### UTXO Chains (Bitcoin, Litecoin, Dogecoin, etc.) + +```typescript +// SwapKit API provides PSBT (Partially Signed Bitcoin Transaction) +// Plugin handles: +const psbt = Psbt.fromHex(transactionData.psbt); +await wallet.signAndBroadcastTransaction(psbt); +``` + +### EVM Chains (Ethereum, Avalanche, BSC, etc.) + +```typescript +// SwapKit API provides transaction parameters +// Plugin creates ethers.js TransactionRequest: +const txRequest = { + to: transactionData.to, + data: transactionData.data, + value: BigInt(transactionData.value), + gasLimit: BigInt(transactionData.gas), + gasPrice: BigInt(transactionData.gasPrice), +}; +await wallet.signAndBroadcastTransaction(txRequest); +``` + +### Solana + +```typescript +// SwapKit API provides base64 encoded transaction +// Plugin handles versioned and legacy transactions: +const tx = VersionedTransaction.deserialize(buffer); +await wallet.signAndBroadcastTransaction(tx); +``` + +### Cosmos Chains (THORChain, Maya, Cosmos, etc.) + +```typescript +// Typically uses transfer with memo +await wallet.transfer({ + recipient: route.destination, + assetValue: sellAsset, + memo: route.memo, +}); +``` + +## Error Handling + +```typescript +try { + const quote = await swapKit.swapkit.quote({ + sellAsset: 'ETH.ETH', + buyAsset: 'BTC.BTC', + sellAmount: '1000000000000000000', + }); + + const txHash = await swapKit.swapkit.swap({ route: quote }); + console.log('Success:', txHash); +} catch (error) { + if (error.errorKey === 'core_quote_failed') { + console.error('Failed to get quote:', error); + } else if (error.errorKey === 'core_swap_failed') { + console.error('Failed to execute swap:', error); + } else if (error.errorKey === 'core_wallet_feature_not_supported') { + console.error('Wallet doesn\'t support this operation:', error); + } +} +``` + +## Testing with Different Wallets + +```typescript +// Test with a wallet that supports signing +await swapKit.connectWallet(WalletOption.METAMASK, [Chain.Ethereum]); +const txHash1 = await swapKit.swapkit.swap({ route: ethRoute }); + +// Test with a wallet that doesn't support signing +await swapKit.connectWallet(WalletOption.CTRL, [Chain.Bitcoin]); +const txHash2 = await swapKit.swapkit.swap({ route: btcRoute }); +// Automatically uses transfer fallback +``` + +## Configuration + +The plugin automatically detects wallet capabilities and uses the appropriate method: + +1. **Primary method**: `signAndBroadcastTransaction` for wallets with full signing support +2. **Fallback method**: `transfer` for wallets without signing support + +No additional configuration is needed - the plugin handles everything automatically based on the connected wallet type. \ No newline at end of file diff --git a/packages/plugins/src/swapkit/index.ts b/packages/plugins/src/swapkit/index.ts new file mode 100644 index 0000000000..7941ca330a --- /dev/null +++ b/packages/plugins/src/swapkit/index.ts @@ -0,0 +1,2 @@ +export * from "./plugin"; +export * from "./types"; diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts new file mode 100644 index 0000000000..13bd3eac3e --- /dev/null +++ b/packages/plugins/src/swapkit/plugin.ts @@ -0,0 +1,285 @@ +import { + AssetValue, + Chain, + type CryptoChain, + type ProviderName, + SwapKitError, + WalletOption, +} from "@swapkit/helpers"; +import { type QuoteResponseRoute, SwapKitApi } from "@swapkit/helpers/api"; +import { Psbt } from "bitcoinjs-lib"; +import type { TransactionRequest } from "ethers"; +import type { SwapKitPluginParams } from "../types"; +import { createPlugin } from "../utils"; +import type { SwapKitQuoteParams, SwapKitSwapParams } from "./types"; + +// Wallets that don't support signing and need special handling +const WALLETS_WITHOUT_SIGNING = [ + WalletOption.CTRL, + WalletOption.VULTISIG, + WalletOption.KEEPKEY_BEX, +]; + +// Check if wallet supports signAndBroadcastTransaction +function walletSupportsSignAndBroadcast(walletType: WalletOption | string): boolean { + return !WALLETS_WITHOUT_SIGNING.includes(walletType as WalletOption); +} + +export const SwapKitPlugin = createPlugin({ + name: "swapkit", + properties: { + supportedSwapkitProviders: [ + // Add SwapKit provider names when they become available + ] as ProviderName[], + }, + methods: (params: SwapKitPluginParams) => { + const { getWallet } = params; + + /** + * Get a quote from the SwapKit API + */ + async function quote(quoteParams: SwapKitQuoteParams): Promise { + try { + const response = await SwapKitApi.getSwapQuote({ + sellAsset: quoteParams.sellAsset, + buyAsset: quoteParams.buyAsset, + sellAmount: quoteParams.sellAmount || "0", + sourceAddress: quoteParams.sourceAddress, + destinationAddress: quoteParams.destinationAddress, + slippage: quoteParams.slippage, + providers: quoteParams.providers, + affiliate: quoteParams.affiliate, + affiliateFee: quoteParams.affiliateBasisPoints, + }); + + if (!response || !response.routes || response.routes.length === 0) { + throw new SwapKitError("core_swap_invalid_params", { + error: "No routes available for this swap", + }); + } + + // Return the best route (first one) + const route = response.routes[0]; + if (!route) { + throw new SwapKitError("core_swap_invalid_params", { + error: "No valid route found", + }); + } + return route; + } catch (error) { + throw new SwapKitError("core_swap_invalid_params", { error }); + } + } + + /** + * Execute a swap using the SwapKit API route + */ + async function swap(swapParams: SwapKitSwapParams): Promise { + const { route } = swapParams; + + // Extract transaction data from the route + // TODO: Update when SwapKit API provides transaction data + const transactionData = (route as any).transaction || {}; + + // Determine the source chain from the sell asset + const sellAsset = AssetValue.from({ asset: route.sellAsset }); + const chain = sellAsset.chain as CryptoChain; + + // Get the wallet for the source chain + const wallet = getWallet(chain); + if (!wallet) { + throw new SwapKitError("core_wallet_connection_not_found", { chain }); + } + + // Check if wallet supports signAndBroadcastTransaction + const walletType = (wallet as any).walletType || ""; + const supportsSignAndBroadcast = walletSupportsSignAndBroadcast(walletType); + + try { + // Handle different chain types + switch (chain) { + case Chain.Bitcoin: + case Chain.BitcoinCash: + case Chain.Dogecoin: + case Chain.Litecoin: + case Chain.Zcash: { + // UTXO chains - use PSBT + const anyWallet = wallet as any; + + // Try signing first if wallet is expected to support it + if (supportsSignAndBroadcast && transactionData.psbt) { + try { + // Parse PSBT from hex or base64 + const psbt = Psbt.fromHex(transactionData.psbt); + return await anyWallet.signAndBroadcastTransaction(psbt); + } catch (signError) { + // If signing fails, try transfer fallback + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + throw signError; + } + } + + // Direct to transfer for wallets known not to support signing + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + + throw new SwapKitError("core_swap_invalid_params", { + error: "Unable to execute UTXO transaction", + }); + } + + case Chain.Ethereum: + case Chain.Avalanche: + case Chain.BinanceSmartChain: + case Chain.Polygon: + case Chain.Arbitrum: + case Chain.Optimism: + case Chain.Base: { + // EVM chains + const anyWallet = wallet as any; + + // Try signing first if wallet is expected to support it + if (supportsSignAndBroadcast && transactionData.to) { + try { + // Create ethers TransactionRequest + const txRequest: TransactionRequest = { + to: transactionData.to, + data: transactionData.data, + value: transactionData.value ? BigInt(transactionData.value) : undefined, + gasLimit: transactionData.gas ? BigInt(transactionData.gas) : undefined, + gasPrice: transactionData.gasPrice ? BigInt(transactionData.gasPrice) : undefined, + }; + return await anyWallet.signAndBroadcastTransaction(txRequest); + } catch (signError) { + // If signing fails, try transfer fallback + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + throw signError; + } + } + + // Direct to transfer for wallets known not to support signing + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + + throw new SwapKitError("core_swap_invalid_params", { + error: "Unable to execute EVM transaction", + }); + } + + case Chain.Solana: { + // Solana + const anyWallet = wallet as any; + + // Try signing first if wallet is expected to support it + if (supportsSignAndBroadcast && transactionData.transaction) { + try { + // Parse Solana transaction from base64 + const { Transaction, VersionedTransaction } = await import("@solana/web3.js"); + + // Try to parse as VersionedTransaction first + try { + const buffer = Buffer.from(transactionData.transaction, "base64"); + const tx = VersionedTransaction.deserialize(buffer); + return await anyWallet.signAndBroadcastTransaction(tx); + } catch { + // Fall back to legacy Transaction + const tx = Transaction.from(Buffer.from(transactionData.transaction, "base64")); + return await anyWallet.signAndBroadcastTransaction(tx); + } + } catch (signError) { + // If signing fails, try transfer fallback + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + throw signError; + } + } + + // Direct to transfer for wallets known not to support signing + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + + throw new SwapKitError("core_swap_invalid_params", { + error: "Unable to execute Solana transaction", + }); + } + + case Chain.THORChain: + case Chain.Maya: + case Chain.Cosmos: + case Chain.Kujira: + case Chain.Noble: { + // Cosmos-based chains - typically use transfer with memo + const anyWallet = wallet as any; + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + throw new SwapKitError("core_swap_invalid_params", { + error: "Unable to execute Cosmos transaction", + }); + } + + case Chain.Near: { + // Near - uses transfer + const anyWallet = wallet as any; + if (transactionData.transferParams) { + return await anyWallet.transfer(transactionData.transferParams); + } + throw new SwapKitError("core_swap_invalid_params", { + error: "Unable to execute Near transaction", + }); + } + + default: + throw new SwapKitError("core_swap_invalid_params", { + error: `Chain ${chain} is not supported for swaps`, + }); + } + } catch (error) { + if (error instanceof SwapKitError) throw error; + throw new SwapKitError("core_swap_invalid_params", { error }); + } + } + + /** + * Override signAndBroadcastTransaction for wallets that don't support it + */ + function overrideSignAndBroadcastForWallet(wallet: any, chain: CryptoChain) { + const walletType = wallet.walletType || ""; + + if (!walletSupportsSignAndBroadcast(walletType)) { + // Override signAndBroadcastTransaction to use transfer as fallback + wallet.signAndBroadcastTransaction = async (tx: any) => { + // Extract transfer params from the transaction + // This would need to be customized based on the transaction type + console.warn(`Wallet ${walletType} doesn't support signing, falling back to transfer`); + + // The SwapKit API should provide transferParams for these wallets + if (tx.transferParams) { + return await wallet.transfer(tx.transferParams); + } + + throw new SwapKitError("core_swap_invalid_params", { + error: `Wallet ${walletType} doesn't support transaction signing for ${chain}`, + }); + }; + } + + return wallet; + } + + return { + quote, + swap, + overrideSignAndBroadcastForWallet, + walletSupportsSignAndBroadcast, + }; + }, +}); diff --git a/packages/plugins/src/swapkit/types.ts b/packages/plugins/src/swapkit/types.ts new file mode 100644 index 0000000000..ff58b6ba51 --- /dev/null +++ b/packages/plugins/src/swapkit/types.ts @@ -0,0 +1,51 @@ +import type { AssetValue, Chain, FeeOption, GenericTransferParams } from "@swapkit/helpers"; +import type { QuoteResponseRoute } from "@swapkit/helpers/api"; + +export type SwapKitQuoteParams = { + sellAsset: string; + buyAsset: string; + sellAmount?: string; + buyAmount?: string; + sourceAddress?: string; + destinationAddress?: string; + slippage?: number; + providers?: string[]; + affiliate?: string; + affiliateBasisPoints?: number; +}; + +export type SwapKitSwapParams = { + route: QuoteResponseRoute; + recipient?: string; + feeOptionKey?: FeeOption; +}; + +export type SwapKitTransactionParams = { + // Transaction data from SwapKit API + data?: string; + to: string; + value?: string; + gas?: string; + gasPrice?: string; + + // PSBT for UTXO chains + psbt?: string; + + // Solana transaction + transaction?: string; + + // Cosmos transaction + msg?: any; + + // Fallback transfer params + transferParams?: GenericTransferParams & { + assetValue: AssetValue; + memo?: string; + }; +}; + +export type WalletCapabilities = { + supportsSignAndBroadcast: boolean; + walletType: string; + chain: Chain; +}; diff --git a/packages/plugins/src/swapkit/wallet-signing-analysis.md b/packages/plugins/src/swapkit/wallet-signing-analysis.md new file mode 100644 index 0000000000..c33f0a3e95 --- /dev/null +++ b/packages/plugins/src/swapkit/wallet-signing-analysis.md @@ -0,0 +1,113 @@ +# Wallet Signing Capabilities Analysis + +## Wallets that DON'T support signing (need special handling) + +### 1. **CTRL Wallet** +- **Chains affected**: Bitcoin, BitcoinCash, Dogecoin, Litecoin, THORChain, Maya, Near +- **Implementation**: Uses `walletTransfer` for UTXO chains, custom transfer methods for others +- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params + +### 2. **Vultisig Wallet** +- **Chains affected**: All supported chains +- **Implementation**: Uses `walletTransfer` throughout +- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params + +### 3. **KeepKey-BEX Wallet** +- **Chains affected**: All supported chains +- **Implementation**: Uses `walletTransfer` +- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params + +## Wallets that DO support signing + +### 1. **Exodus** +- **Bitcoin**: ✅ Has `signTransaction` via sats-connect +- **EVM chains**: ✅ Standard ethers.js signer +- **Special handling needed**: No + +### 2. **Phantom** +- **Bitcoin**: ✅ Uses toolbox directly (has signing) +- **Ethereum**: ✅ Standard ethers.js signer +- **Solana**: ✅ Uses provider.signTransaction +- **Special handling needed**: No + +### 3. **OKX** +- **Bitcoin**: ✅ Has `signTransaction` via wallet.signPsbt +- **EVM chains**: ✅ Standard ethers.js signer +- **Cosmos**: ⚠️ Uses custom cosmosTransfer +- **Near**: ✅ Has signer +- **Solana**: ✅ Has signing +- **Special handling needed**: Only for Cosmos + +### 4. **OneKey** +- **All chains**: ✅ Has signing capabilities +- **Special handling needed**: No + +### 5. **TronLink** +- **Tron**: ✅ Has signing capabilities +- **Special handling needed**: No + +### 6. **WalletConnect** +- **EVM chains**: ✅ Has EVM signer +- **Other chains**: ✅ Standard signing +- **Special handling needed**: No + +### 7. **Coinbase** +- **All supported chains**: ✅ Has signer implementation +- **Special handling needed**: No + +### 8. **Bitget** +- **All supported chains**: ✅ Has signing helpers +- **Special handling needed**: No + +### 9. **Ledger** (Hardware) +- **All chains**: ✅ Full signing support via hardware +- **Special handling needed**: No + +### 10. **Trezor** (Hardware) +- **All chains**: ✅ Full signing support via hardware +- **Special handling needed**: No + +### 11. **Keplr** +- **Cosmos chains**: ✅ Has signing +- **Special handling needed**: No + +### 12. **Cosmostation** +- **Cosmos chains**: ✅ Has signing +- **Special handling needed**: No + +### 13. **Xaman** +- **XRP/Ripple**: ✅ Has signing +- **Special handling needed**: No + +### 14. **Polkadot.js** +- **Polkadot/Substrate**: ✅ Has signing +- **Special handling needed**: No + +### 15. **Talisman** +- **Polkadot/Substrate**: ✅ Has signing +- **Special handling needed**: No + +### 16. **Radix** +- **Radix**: ✅ Has signing +- **Special handling needed**: No + +### 17. **Keystore** +- **All chains**: ✅ Has signing (software wallet) +- **Special handling needed**: No + +### 18. **EVM Extensions** (MetaMask, etc.) +- **EVM chains**: ✅ Standard ethers.js signer +- **Special handling needed**: No + +## Summary + +**Wallets needing special handling:** +- CTRL +- Vultisig +- KeepKey-BEX +- OKX (Cosmos only) + +For these wallets, we'll need to: +1. Detect if the wallet doesn't support `signAndBroadcastTransaction` +2. Fall back to using the `transfer` method with parameters from SwapKit API +3. Override the `signAndBroadcastTransaction` method to use the fallback \ No newline at end of file diff --git a/packages/plugins/src/types.ts b/packages/plugins/src/types.ts index 47d3fd6964..50cb05b27e 100644 --- a/packages/plugins/src/types.ts +++ b/packages/plugins/src/types.ts @@ -5,15 +5,18 @@ import type { EVMPlugin } from "./evm"; import type { NearPlugin } from "./near"; import type { RadixPlugin } from "./radix"; import type { SolanaPlugin } from "./solana/plugin"; +import type { SwapKitPlugin } from "./swapkit"; import type { ThorchainPlugin } from "./thorchain"; export type * from "./chainflip/types"; +export type * from "./swapkit/types"; export type * from "./thorchain/types"; export type SKPlugins = typeof ChainflipPlugin & typeof ThorchainPlugin & typeof RadixPlugin & typeof SolanaPlugin & + typeof SwapKitPlugin & typeof EVMPlugin & typeof NearPlugin; diff --git a/packages/plugins/src/utils.ts b/packages/plugins/src/utils.ts index 85b11c5b39..4779d0e247 100644 --- a/packages/plugins/src/utils.ts +++ b/packages/plugins/src/utils.ts @@ -37,6 +37,10 @@ export async function loadPlugin

(pluginName: P) { const { SolanaPlugin } = await import("./solana"); return SolanaPlugin; }) + .with("swapkit", async () => { + const { SwapKitPlugin } = await import("./swapkit"); + return SwapKitPlugin; + }) .with("near", async () => { const { NearPlugin } = await import("./near"); return NearPlugin; From 8c70a26d19b99c5e32dffcbeab0a600775fbe79a Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 20 Aug 2025 15:41:47 +0400 Subject: [PATCH 06/15] chore: adds swapkit plugin --- packages/plugins/src/swapkit/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts index 13bd3eac3e..fd09b76090 100644 --- a/packages/plugins/src/swapkit/plugin.ts +++ b/packages/plugins/src/swapkit/plugin.ts @@ -52,7 +52,7 @@ export const SwapKitPlugin = createPlugin({ affiliateFee: quoteParams.affiliateBasisPoints, }); - if (!response || !response.routes || response.routes.length === 0) { + if (!response?.routes || response.routes.length === 0) { throw new SwapKitError("core_swap_invalid_params", { error: "No routes available for this swap", }); From 5349cdea426d3925aea161fcdac4f89de87431ae Mon Sep 17 00:00:00 2001 From: towan Date: Thu, 4 Sep 2025 12:42:32 +0400 Subject: [PATCH 07/15] WIP - signing tx --- TOOLBOX_SIGNING_METHODS_SUMMARY.md | 104 +++ WALLET_SIGNER_ANALYSIS.md | 168 +++++ bun.lock | 2 +- packages/helpers/src/modules/swapKitError.ts | 616 +++++++++--------- packages/helpers/src/types/chains.ts | 2 +- packages/plugins/src/chainflip/plugin.ts | 8 +- packages/toolboxes/src/solana/index.ts | 35 +- packages/toolboxes/src/solana/toolbox.ts | 2 +- packages/toolboxes/src/substrate/substrate.ts | 5 +- .../src/ledger/clients/utxo.ts | 67 +- packages/wallet-hardware/src/ledger/types.ts | 5 +- packages/wallets/package.json | 17 +- packages/wallets/src/evm-extensions/index.ts | 43 +- packages/wallets/src/exodus/index.ts | 40 +- packages/wallets/src/walletconnect/index.ts | 44 +- playgrounds/vite/src/App.tsx | 6 +- playgrounds/vite/src/Send/index.tsx | 8 +- radix-toolbox-signing-methods-analysis.md | 94 +++ solana-toolbox-signing-methods-analysis.md | 70 ++ substrate-toolbox-signing-methods-analysis.md | 112 ++++ test_ripple_methods.js | 31 + test_substrate_methods.js | 38 ++ tron-signing-methods-analysis.md | 70 ++ 23 files changed, 1097 insertions(+), 490 deletions(-) create mode 100644 TOOLBOX_SIGNING_METHODS_SUMMARY.md create mode 100644 WALLET_SIGNER_ANALYSIS.md create mode 100644 radix-toolbox-signing-methods-analysis.md create mode 100644 solana-toolbox-signing-methods-analysis.md create mode 100644 substrate-toolbox-signing-methods-analysis.md create mode 100644 test_ripple_methods.js create mode 100644 test_substrate_methods.js create mode 100644 tron-signing-methods-analysis.md diff --git a/TOOLBOX_SIGNING_METHODS_SUMMARY.md b/TOOLBOX_SIGNING_METHODS_SUMMARY.md new file mode 100644 index 0000000000..96a01b1eb9 --- /dev/null +++ b/TOOLBOX_SIGNING_METHODS_SUMMARY.md @@ -0,0 +1,104 @@ +# SwapKit Toolbox Signing Methods Summary + +## Overview +This document summarizes the analysis of signing methods across all SwapKit toolboxes. The goal is to standardize the following three methods across all chains: + +1. **signTransaction** - Signs a transaction and returns the signed transaction +2. **signAndBroadcastTransaction** - Signs and broadcasts a transaction, returns the tx hash +3. **signMessage** - Signs a message for authentication/verification + +## Reference Implementation (EVM & UTXO) + +### EVM Toolbox ✅ Complete +```typescript +signTransaction: (tx: TransactionRequest) => Promise +signAndBroadcastTransaction: (tx: TransactionRequest) => Promise +signMessage: (message: string) => Promise +``` + +### UTXO Toolbox ✅ Complete +```typescript +signTransaction: (psbt: Psbt) => Promise +signAndBroadcastTransaction: (psbt: Psbt) => Promise +// Note: BCH uses different signatures with TransactionBuilderType +``` + +## Toolbox Status Summary + +| Toolbox | signTransaction | signAndBroadcastTransaction | signMessage | Notes | +|---------|----------------|----------------------------|-------------|-------| +| **EVM** | ✅ Implemented | ✅ Implemented | ✅ Implemented | Complete implementation | +| **UTXO** | ✅ Implemented | ✅ Implemented | ❌ Missing | No signMessage method | +| **Solana** | ✅ Implemented | ✅ Implemented | ❌ Missing | signMessage in provider but not toolbox | +| **Cosmos** | ❌ Missing | ✅ Implemented (THORChain only) | ❌ Missing | Explicitly omitted from types | +| **Tron** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | +| **Near** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | +| **Ripple** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | +| **Substrate** | ✅ `sign` | ✅ `signAndBroadcast` | ❌ Missing | Different naming convention | +| **Radix** | ❌ Missing | ❌ Missing* | ❌ Missing | *Has error-throwing placeholder | + +## Implementation Priority + +### High Priority (Core Functionality Missing) +1. **Radix** - Needs all three methods implemented +2. **Cosmos** - Needs signTransaction and signMessage + +### Medium Priority (Convenience Methods) +1. **Tron** - Add signAndBroadcastTransaction, signMessage +2. **Near** - Add signAndBroadcastTransaction, signMessage +3. **Ripple** - Add signAndBroadcastTransaction, signMessage + +### Low Priority (Message Signing) +1. **UTXO** - Add signMessage +2. **Solana** - Expose signMessage from provider to toolbox +3. **Substrate** - Add signMessage (consider renaming to match standard) + +## Recommended Method Signatures + +### signTransaction +```typescript +// Generic signature +signTransaction: (transaction: ChainSpecificTransaction) => Promise + +// Examples: +// Tron: (transaction: TronTransaction) => Promise +// Near: (transaction: Transaction) => Promise +// Ripple: (tx: Transaction) => Promise<{ tx_blob: string; hash: string }> +``` + +### signAndBroadcastTransaction +```typescript +// Generic signature +signAndBroadcastTransaction: (transaction: ChainSpecificTransaction) => Promise + +// Should combine existing signTransaction + broadcastTransaction +``` + +### signMessage +```typescript +// Generic signature +signMessage: (message: string) => Promise + +// Returns signature as hex/base64 string +``` + +## Implementation Notes + +1. **Error Handling**: All methods should throw `SwapKitError` with appropriate error keys when signer is not available +2. **Signer Validation**: Check for signer existence before attempting any signing operation +3. **Return Values**: signAndBroadcastTransaction should always return transaction hash as string +4. **Async/Await**: All signing methods should be async functions +5. **Type Safety**: Maintain chain-specific transaction types + +## Files Analyzed + +- `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` +- `/packages/toolboxes/src/utxo/toolbox/utxo.ts` +- `/packages/toolboxes/src/solana/toolbox.ts` +- `/packages/toolboxes/src/cosmos/toolbox/cosmos.ts` +- `/packages/toolboxes/src/cosmos/toolbox/thorchain.ts` +- `/packages/toolboxes/src/tron/toolbox.ts` +- `/packages/toolboxes/src/near/toolbox.ts` +- `/packages/toolboxes/src/ripple/index.ts` +- `/packages/toolboxes/src/substrate/toolbox.ts` +- `/packages/toolboxes/src/radix/toolbox.ts` \ No newline at end of file diff --git a/WALLET_SIGNER_ANALYSIS.md b/WALLET_SIGNER_ANALYSIS.md new file mode 100644 index 0000000000..bd1651611c --- /dev/null +++ b/WALLET_SIGNER_ANALYSIS.md @@ -0,0 +1,168 @@ +# SwapKit Wallet Signer Implementation Analysis + +## Executive Summary + +Analysis of 19 wallet packages to identify custom signer implementations and toolbox method overrides. + +## Classification by Implementation Pattern + +### 🔴 Full Custom Signer Implementations + +These wallets implement comprehensive custom signer methods: + +| Wallet | Custom Signers | Key Methods | Notes | +|--------|---------------|-------------|-------| +| **Bitget** | BTC, Cosmos, Solana, Tron | `signTransaction`, `getAddress` | Most comprehensive multi-chain custom signers | +| **Coinbase** | EVM (AbstractSigner) | `signTransaction`, `signMessage`, `getAddress` | Custom EVM signer class | +| **WalletConnect** | EVM, NEAR, Tron | `signTransaction`, `signMessage`, `getAddress` | Protocol-based signers | +| **OneKey** | BTC (sats-connect), EVM | `signTransaction`, `signMessage`, `getAddress` | Custom EVM AbstractSigner | +| **OKX** | BTC, Tron, Near | `signTransaction`, `getAddress` | Multi-chain custom implementations | +| **TronLink** | Tron | `signTransaction`, `getAddress` | Specialized Tron signer | + +### 🟡 Partial Custom Implementations + +These wallets have selective custom implementations: + +| Wallet | Custom Elements | Standard Elements | Notes | +|--------|----------------|-------------------|-------| +| **CTRL** | Near signer wrapper | EVM, Cosmos use standard | Extensive method overrides | +| **Exodus** | BTC (sats-connect) | EVM, Solana standard | Mixed approach | +| **KeepKey BEX** | BTC/UTXO signing | Uses provider signers | Custom wallet transfer methods | +| **Vultisig** | Custom transfer methods | Provider signers | Heavy toolbox overrides | + +### 🟢 Standard Implementations + +These wallets use standard toolbox signers: + +| Wallet | Signer Type | Toolbox Overrides | Notes | +|--------|------------|-------------------|-------| +| **Cosmostation** | Keplr + window.ethereum | None | Standard patterns | +| **Keplr** | OfflineSignerOnlyAmino | None | Cosmos-only, minimal customization | +| **Phantom** | Standard providers | Solana transfer override | Minimal customization | +| **PolkadotJS** | Extension signer | None | Single-chain standard | +| **Talisman** | Standard toolbox signers | None | Multi-chain standard | + +### 🔵 Special Cases + +| Wallet | Pattern | Explanation | +|--------|---------|-------------| +| **Radix** | No standard toolbox | Custom implementation with `signAndBroadcast` | +| **Xaman** | App-specific flow | Uses QR/deep link signing, no standard signer | + +## Toolbox Method Override Analysis + +### Heavy Override Pattern +Wallets that extensively modify toolbox behavior: + +1. **CTRL** + - Overrides: `transfer`, `deposit` (THORChain/Maya/BTC/UTXO/Near) + - Custom: `walletTransfer`, `createTransaction` methods + +2. **Vultisig** + - Overrides: `transfer` (all chains), `deposit` (THORChain/Maya) + - Custom: `approve`, `call`, `sendTransaction` for EVM + +3. **KeepKey BEX** + - Overrides: `transfer`, `deposit` (Cosmos/THORChain) + - Custom: `getBalance` for BTC + +### Selective Override Pattern +Wallets with specific customizations: + +1. **Exodus**: Solana `transfer` override +2. **Phantom**: Solana `transfer` with custom validation +3. **OKX**: Cosmos `transfer` override +4. **WalletConnect**: THORChain `transfer` and `deposit` overrides + +### No Override Pattern +Wallets using standard toolboxes: +- Cosmostation, Keplr, OneKey, PolkadotJS, Talisman, TronLink + +## Chain Support Matrix + +### Multi-Chain Leaders (15+ chains) +1. **CTRL**: 21 chains +2. **KeepKey BEX**: 18 chains +3. **Vultisig**: 17 chains +4. **OKX**: 15 chains +5. **WalletConnect**: 15 chains + +### Moderate Support (5-14 chains) +1. **Bitget**: 14 chains +2. **Cosmostation**: 11 chains +3. **OneKey**: 11 chains +4. **Exodus**: ~11 chains +5. **Talisman**: 9 chains +6. **Coinbase**: 7 chains + +### Specialized/Limited (1-4 chains) +1. **Keplr**: 4 chains (Cosmos ecosystem) +2. **Phantom**: 3 chains +3. **PolkadotJS**: 1 chain (Polkadot) +4. **Radix**: 1 chain (Radix) +5. **TronLink**: 1 chain (Tron) +6. **Xaman**: 1 chain (Ripple) + +## Key Findings + +### Signer Implementation Patterns + +1. **Custom Signer Pattern** (30% of wallets) + - Implement full `ChainSigner` interface + - Methods: `getAddress()`, `signTransaction()`, `signMessage()` + - Examples: Bitget, Coinbase, WalletConnect + +2. **Provider Wrapper Pattern** (40% of wallets) + - Wrap wallet-specific providers into SwapKit interfaces + - Minimal custom implementation + - Examples: Keplr, Talisman, PolkadotJS + +3. **Hybrid Pattern** (20% of wallets) + - Mix custom signers for some chains, standard for others + - Examples: CTRL, Exodus, OKX + +4. **Special Pattern** (10% of wallets) + - Non-standard implementations + - Examples: Radix (custom API), Xaman (app-specific) + +### Toolbox Override Patterns + +1. **Heavy Override** (~15% of wallets): CTRL, Vultisig, KeepKey BEX +2. **Selective Override** (~25% of wallets): Exodus, Phantom, OKX, WalletConnect +3. **No Override** (~60% of wallets): Most wallets use standard toolboxes + +### Common Override Methods + +Most frequently overridden methods: +1. `transfer` - Custom transaction flows +2. `deposit` - THORChain/Maya specific +3. `getBalance` - Custom balance fetching +4. `signTransaction` - Chain-specific signing + +## Recommendations + +### For Standardization + +1. **High Priority Wallets** (extensive custom implementations): + - CTRL: Consider standardizing transfer methods + - Vultisig: Evaluate if custom methods can use standard patterns + - KeepKey BEX: Review necessity of custom balance methods + +2. **Medium Priority** (partial customizations): + - Standardize Solana transfer overrides (Exodus, Phantom) + - Unify THORChain/Maya deposit patterns + +3. **Low Priority** (working as intended): + - Standard implementation wallets + - Single-chain specialized wallets + +### Architecture Patterns to Preserve + +1. **Good Patterns**: + - Custom signers for chains requiring special handling (BTC, Tron) + - Provider wrapper pattern for standard chains + - Clear separation of concerns + +2. **Patterns to Review**: + - Extensive method overrides that duplicate toolbox functionality + - Inconsistent error handling across custom implementations \ No newline at end of file diff --git a/bun.lock b/bun.lock index 1a503915e8..e16cf3872c 100644 --- a/bun.lock +++ b/bun.lock @@ -302,7 +302,7 @@ "@cosmjs/proto-signing": "~0.33.0", "@keplr-wallet/types": "~0.12.238", "@passkeys/core": "^4.0.0", - "@passkeys/react": "^3.0.0", + "@passkeys/react": "^3.0.1", "@radixdlt/babylon-gateway-api-sdk": "~1.10.0", "@radixdlt/radix-dapp-toolkit": "~2.2.0", "@scure/base": "~1.2.0", diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index 21c3a2d82a..f9ebbd0d4f 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -1,335 +1,237 @@ +/** biome-ignore-all assist/source/useSortedKeys: its sorted by type */ const errorCodes = { - api_v2_invalid_method_key_hash: 60003, - /** - * SwapKit API - */ - api_v2_invalid_response: 60001, - api_v2_server_error: 60002, - chainflip_broker_fund_invalid_address: 30107, - chainflip_broker_fund_only_flip_supported: 30106, - /** - * Chainflip - Broker - */ - chainflip_broker_invalid_params: 30101, - chainflip_broker_recipient_error: 30102, - chainflip_broker_register: 30103, - chainflip_broker_tx_error: 30104, - chainflip_broker_withdraw: 30105, - - /** - * Chainflip - */ - chainflip_channel_error: 30001, - chainflip_unknown_asset: 30002, - core_approve_asset_address_or_from_not_found: 10004, - core_approve_asset_target_invalid: 10007, - core_chain_halted: 10010, /** * Core */ core_estimated_max_spendable_chain_not_supported: 10001, - core_explorer_unsupported_chain: 10008, core_extend_error: 10002, core_inbound_data_not_found: 10003, + core_approve_asset_address_or_from_not_found: 10004, core_plugin_not_found: 10005, core_plugin_swap_not_found: 10006, - core_swap_asset_not_recognized: 10203, - core_swap_contract_not_found: 10204, - core_swap_contract_not_supported: 10206, + core_approve_asset_target_invalid: 10007, + core_explorer_unsupported_chain: 10008, + core_verify_message_not_supported: 10009, + core_chain_halted: 10010, + /** + * Core - Wallet + */ + core_wallet_connection_not_found: 10101, + core_wallet_ctrl_not_installed: 10102, + core_wallet_evmwallet_not_installed: 10103, + core_wallet_walletconnect_not_installed: 10104, + core_wallet_keystore_not_installed: 10105, + core_wallet_ledger_not_installed: 10106, + core_wallet_trezor_not_installed: 10107, + core_wallet_keplr_not_installed: 10108, + core_wallet_okx_not_installed: 10109, + core_wallet_keepkey_not_installed: 10110, + core_wallet_talisman_not_installed: 10111, + core_wallet_not_keypair_wallet: 10112, + core_wallet_sign_message_not_supported: 10113, + core_wallet_connection_failed: 10114, /** * Core - Swap */ core_swap_invalid_params: 10201, - core_swap_quote_mode_not_supported: 10208, core_swap_route_not_complete: 10202, + core_swap_asset_not_recognized: 10203, + core_swap_contract_not_found: 10204, core_swap_route_transaction_not_found: 10205, + core_swap_contract_not_supported: 10206, core_swap_transaction_error: 10207, - core_transaction_add_liquidity_asset_error: 10308, - core_transaction_add_liquidity_base_address: 10306, - core_transaction_add_liquidity_base_error: 10307, - core_transaction_add_liquidity_invalid_params: 10305, - core_transaction_create_liquidity_asset_error: 10303, - core_transaction_create_liquidity_base_error: 10302, - core_transaction_create_liquidity_invalid_params: 10304, + core_swap_quote_mode_not_supported: 10208, /** * Core - Transaction */ core_transaction_deposit_error: 10301, - core_transaction_deposit_gas_error: 10312, + core_transaction_create_liquidity_base_error: 10302, + core_transaction_create_liquidity_asset_error: 10303, + core_transaction_create_liquidity_invalid_params: 10304, + core_transaction_add_liquidity_invalid_params: 10305, + core_transaction_add_liquidity_base_address: 10306, + core_transaction_add_liquidity_base_error: 10307, + core_transaction_add_liquidity_asset_error: 10308, + core_transaction_withdraw_error: 10309, + core_transaction_deposit_to_pool_error: 10310, core_transaction_deposit_insufficient_funds_error: 10311, + core_transaction_deposit_gas_error: 10312, + core_transaction_invalid_sender_address: 10313, core_transaction_deposit_server_error: 10314, - core_transaction_deposit_to_pool_error: 10310, + core_transaction_user_rejected: 10315, core_transaction_failed: 10316, core_transaction_invalid_recipient_address: 10317, - core_transaction_invalid_sender_address: 10313, - core_transaction_user_rejected: 10315, - core_transaction_withdraw_error: 10309, - core_verify_message_not_supported: 10009, - core_wallet_connection_failed: 10114, /** - * Core - Wallet + * Wallets - General */ - core_wallet_connection_not_found: 10101, - core_wallet_ctrl_not_installed: 10102, - core_wallet_evmwallet_not_installed: 10103, - core_wallet_keepkey_not_installed: 10110, - core_wallet_keplr_not_installed: 10108, - core_wallet_keystore_not_installed: 10105, - core_wallet_ledger_not_installed: 10106, - core_wallet_not_keypair_wallet: 10112, - core_wallet_okx_not_installed: 10109, - core_wallet_sign_message_not_supported: 10113, - core_wallet_talisman_not_installed: 10111, - core_wallet_trezor_not_installed: 10107, - core_wallet_walletconnect_not_installed: 10104, - helpers_chain_not_supported: 70009, - helpers_failed_to_switch_network: 70007, - helpers_invalid_asset_identifier: 70005, - helpers_invalid_asset_url: 70004, - helpers_invalid_identifier: 70003, - helpers_invalid_memo_type: 70006, + wallet_connection_rejected_by_user: 20001, + wallet_missing_api_key: 20002, + wallet_chain_not_supported: 20003, + wallet_missing_params: 20004, + wallet_provider_not_found: 20005, + wallet_failed_to_add_or_switch_network: 20006, + wallet_locked: 20007, /** - * Helpers + * Wallets - Ledger */ - helpers_invalid_number_different_decimals: 70001, - helpers_invalid_number_of_years: 70002, - helpers_invalid_params: 70010, - helpers_invalid_response: 70011, - helpers_not_found_provider: 70008, + wallet_ledger_connection_error: 20101, + wallet_ledger_connection_claimed: 20102, + wallet_ledger_get_address_error: 20103, + wallet_ledger_device_not_found: 20104, + wallet_ledger_device_locked: 20105, + wallet_ledger_transport_error: 20106, + wallet_ledger_public_key_error: 20107, + wallet_ledger_derivation_path_error: 20108, + wallet_ledger_signing_error: 20109, + wallet_ledger_app_not_open: 20110, + wallet_ledger_invalid_response: 20111, + wallet_ledger_method_not_supported: 20112, + wallet_ledger_invalid_params: 20113, + wallet_ledger_invalid_signature: 20114, + wallet_ledger_no_provider: 20115, + wallet_ledger_pubkey_not_found: 20116, + wallet_ledger_transport_not_defined: 20117, + wallet_ledger_webusb_not_supported: 20118, + wallet_ledger_chain_not_supported: 20119, + wallet_ledger_invalid_asset: 20120, + wallet_ledger_invalid_account: 20121, + wallet_ledger_address_not_found: 20122, + wallet_ledger_failed_to_get_address: 20123, /** - * Anything else + * Wallets - Phantom */ - not_implemented: 99999, + wallet_phantom_not_found: 20201, /** - * NEAR Plugin + * Wallets - Ctrl */ - plugin_near_invalid_name: 41001, - plugin_near_name_unavailable: 41003, - plugin_near_no_connection: 41002, - plugin_near_registration_failed: 41004, - plugin_near_transfer_failed: 41005, - thorchain_asset_is_not_tcy: 40003, + wallet_ctrl_not_found: 20301, + wallet_ctrl_send_transaction_no_address: 20302, + wallet_ctrl_contract_address_not_provided: 20303, + wallet_ctrl_asset_not_defined: 20304, /** - * THORChain + * Wallets - WalletConnect */ - thorchain_chain_halted: 40001, - thorchain_preferred_asset_payout_required: 40105, - thorchain_swapin_memo_required: 40103, + wallet_walletconnect_project_id_not_specified: 20401, + wallet_walletconnect_connection_not_established: 20402, + wallet_walletconnect_namespace_not_supported: 20403, + wallet_walletconnect_chain_not_supported: 20404, + wallet_walletconnect_invalid_method: 20405, + wallet_walletconnect_method_not_supported: 20406, /** - * THORChain - Swap + * Wallets - Trezor */ - thorchain_swapin_router_required: 40101, - thorchain_swapin_token_required: 40104, - thorchain_swapin_vault_required: 40102, - thorchain_trading_halted: 40002, + wallet_trezor_failed_to_sign_transaction: 20501, + wallet_trezor_derivation_path_not_supported: 20502, + wallet_trezor_failed_to_get_address: 20503, + wallet_trezor_transport_error: 20504, + wallet_trezor_method_not_supported: 20505, /** - * Toolboxes - Cosmos + * Wallets - Talisman */ - toolbox_cosmos_account_not_found: 50101, - toolbox_cosmos_invalid_fee: 50102, - toolbox_cosmos_invalid_params: 50103, - toolbox_cosmos_no_signer: 50104, - toolbox_cosmos_not_supported: 50105, - toolbox_cosmos_signer_not_defined: 50106, - toolbox_cosmos_validate_address_prefix_not_found: 50107, - toolbox_cosmos_verify_signature_no_pubkey: 50108, + wallet_talisman_not_enabled: 20601, + wallet_talisman_not_found: 20602, /** - * Toolboxes - EVM + * Wallets - Polkadot */ - toolbox_evm_error_estimating_gas_limit: 50201, - toolbox_evm_error_sending_transaction: 50202, - toolbox_evm_gas_estimation_error: 50203, - toolbox_evm_invalid_gas_asset_address: 50204, - toolbox_evm_invalid_params: 50205, - toolbox_evm_invalid_transaction: 50206, - toolbox_evm_no_abi_fragment: 50207, - toolbox_evm_no_contract_address: 50208, - toolbox_evm_no_fee_data: 50209, - toolbox_evm_no_from_address: 50210, - toolbox_evm_no_gas_price: 50211, - toolbox_evm_no_signer: 50213, - toolbox_evm_no_signer_address: 50212, - toolbox_evm_no_to_address: 50214, - toolbox_evm_not_supported: 50215, - toolbox_evm_provider_not_eip1193_compatible: 50216, - toolbox_near_access_key_error: 90605, - toolbox_near_balance_failed: 90608, - toolbox_near_empty_batch: 90607, - toolbox_near_invalid_address: 90602, - toolbox_near_invalid_amount: 90603, - toolbox_near_invalid_gas_params: 90612, - toolbox_near_invalid_name: 90609, - toolbox_near_missing_contract_address: 90610, - toolbox_near_no_account: 90611, - toolbox_near_no_rpc_url: 90606, + wallet_polkadot_not_found: 20701, /** - * Toolboxes - Near + * Wallets - Radix */ - toolbox_near_no_signer: 90601, - toolbox_near_transfer_failed: 90604, + wallet_radix_not_found: 20801, + wallet_radix_transaction_failed: 20802, + wallet_radix_invalid_manifest: 20803, + wallet_radix_method_not_supported: 20804, + wallet_radix_no_account: 20805, /** - * Toolboxes - General + * Wallets - KeepKey */ - toolbox_not_supported: 59901, + wallet_keepkey_not_found: 20901, + wallet_keepkey_asset_not_defined: 20902, + wallet_keepkey_contract_address_not_provided: 20903, + wallet_keepkey_send_transaction_no_address: 20904, + wallet_keepkey_derivation_path_error: 20905, + wallet_keepkey_signing_error: 20906, + wallet_keepkey_transport_error: 20907, + wallet_keepkey_unsupported_chain: 20908, + wallet_keepkey_invalid_response: 20909, + wallet_keepkey_chain_not_supported: 20910, + wallet_keepkey_signer_not_found: 20911, + wallet_keepkey_no_accounts: 20912, + wallet_keepkey_method_not_supported: 20913, + wallet_keepkey_invalid_params: 20914, + wallet_keepkey_config_not_found: 20915, + wallet_keepkey_no_provider: 20916, + wallet_keepkey_account_not_found: 20917, /** - * Toolboxes - Radix + * Wallets - BitKeep/BitGet */ - toolbox_radix_method_not_supported: 50601, - toolbox_ripple_asset_not_supported: 50704, - toolbox_ripple_broadcast_error: 50705, + wallet_bitkeep_not_found: 21001, + wallet_bitkeep_failed_to_switch_network: 21002, + wallet_bitkeep_no_accounts: 21003, /** - * Toolboxes - Ripple - */ - toolbox_ripple_get_balance_error: 50701, - toolbox_ripple_rpc_not_configured: 50702, - toolbox_ripple_signer_not_found: 50703, - toolbox_solana_fee_estimation_failed: 50402, - /** - * Toolboxes - Solana - */ - toolbox_solana_no_signer: 50401, - /** - * Toolboxes - Substrate - */ - toolbox_substrate_not_supported: 50501, - toolbox_tron_allowance_check_failed: 50809, - toolbox_tron_approve_failed: 50807, - toolbox_tron_fee_estimation_failed: 50805, - toolbox_tron_invalid_token_contract: 50808, - toolbox_tron_invalid_token_identifier: 50802, - /** - * Toolboxes - Tron + * Wallets - Exodus */ - toolbox_tron_no_signer: 50801, - toolbox_tron_token_transfer_failed: 50803, - toolbox_tron_transaction_creation_failed: 50804, - toolbox_tron_trongrid_api_error: 50806, + wallet_exodus_sign_transaction_error: 21101, + wallet_exodus_not_found: 21102, + wallet_exodus_no_address: 21103, + wallet_exodus_request_canceled: 21104, + wallet_exodus_signature_canceled: 21105, + wallet_exodus_failed_to_switch_network: 21106, + wallet_exodus_chain_not_supported: 21107, + wallet_exodus_instance_missing: 21108, /** - * Toolboxes - UTXO + * Wallets - OneKey */ - toolbox_utxo_api_error: 50301, - toolbox_utxo_broadcast_failed: 50302, - toolbox_utxo_insufficient_balance: 50303, - toolbox_utxo_invalid_address: 50304, - toolbox_utxo_invalid_params: 50305, - toolbox_utxo_invalid_transaction: 50306, - toolbox_utxo_no_signer: 50307, - toolbox_utxo_not_supported: 50308, - wallet_bitkeep_failed_to_switch_network: 21002, - wallet_bitkeep_no_accounts: 21003, + wallet_onekey_not_found: 21201, + wallet_onekey_sign_transaction_error: 21202, /** - * Wallets - BitKeep/BitGet + * Wallets - OKX */ - wallet_bitkeep_not_found: 21001, - wallet_chain_not_supported: 20003, - wallet_coinbase_chain_not_supported: 21702, - wallet_coinbase_method_not_supported: 21703, - wallet_coinbase_no_accounts: 21704, + wallet_okx_not_found: 21301, + wallet_okx_chain_not_supported: 21302, + wallet_okx_failed_to_switch_network: 21303, + wallet_okx_no_accounts: 21304, /** - * Wallets - Coinbase + * Wallets - Keplr */ - wallet_coinbase_not_found: 21701, + wallet_keplr_not_found: 21401, + wallet_keplr_chain_not_supported: 21402, + wallet_keplr_signer_not_found: 21403, + wallet_keplr_no_accounts: 21404, /** - * Wallets - General + * Wallets - Cosmostation */ - wallet_connection_rejected_by_user: 20001, + wallet_cosmostation_not_found: 21501, wallet_cosmostation_chain_not_supported: 21502, wallet_cosmostation_evm_provider_not_found: 21503, wallet_cosmostation_keplr_provider_not_found: 21504, wallet_cosmostation_no_accounts: 21505, wallet_cosmostation_no_evm_accounts: 21506, wallet_cosmostation_no_evm_address: 21507, + wallet_cosmostation_signer_not_found: 21508, /** - * Wallets - Cosmostation + * Wallets - XDefi */ - wallet_cosmostation_not_found: 21501, - wallet_cosmostation_signer_not_found: 21508, - wallet_ctrl_asset_not_defined: 20304, - wallet_ctrl_contract_address_not_provided: 20303, + wallet_xdefi_not_found: 21601, + wallet_xdefi_chain_not_supported: 21602, /** - * Wallets - Ctrl + * Wallets - Coinbase */ - wallet_ctrl_not_found: 20301, - wallet_ctrl_send_transaction_no_address: 20302, + wallet_coinbase_not_found: 21701, + wallet_coinbase_chain_not_supported: 21702, + wallet_coinbase_method_not_supported: 21703, + wallet_coinbase_no_accounts: 21704, /** * Wallets - EVM Extensions */ wallet_evm_extensions_failed_to_switch_network: 21801, wallet_evm_extensions_no_provider: 21802, wallet_evm_extensions_not_found: 21803, - wallet_exodus_chain_not_supported: 21107, - wallet_exodus_failed_to_switch_network: 21106, - wallet_exodus_instance_missing: 21108, - wallet_exodus_no_address: 21103, - wallet_exodus_not_found: 21102, - wallet_exodus_request_canceled: 21104, - /** - * Wallets - Exodus - */ - wallet_exodus_sign_transaction_error: 21101, - wallet_exodus_signature_canceled: 21105, - wallet_failed_to_add_or_switch_network: 20006, - wallet_keepkey_account_not_found: 20917, - wallet_keepkey_asset_not_defined: 20902, - wallet_keepkey_chain_not_supported: 20910, - wallet_keepkey_config_not_found: 20915, - wallet_keepkey_contract_address_not_provided: 20903, - wallet_keepkey_derivation_path_error: 20905, - wallet_keepkey_invalid_params: 20914, - wallet_keepkey_invalid_response: 20909, - wallet_keepkey_method_not_supported: 20913, - wallet_keepkey_no_accounts: 20912, - wallet_keepkey_no_provider: 20916, - /** - * Wallets - KeepKey - */ - wallet_keepkey_not_found: 20901, - wallet_keepkey_send_transaction_no_address: 20904, - wallet_keepkey_signer_not_found: 20911, - wallet_keepkey_signing_error: 20906, - wallet_keepkey_transport_error: 20907, - wallet_keepkey_unsupported_chain: 20908, - wallet_keplr_chain_not_supported: 21402, - wallet_keplr_no_accounts: 21404, - /** - * Wallets - Keplr - */ - wallet_keplr_not_found: 21401, - wallet_keplr_signer_not_found: 21403, /** * Wallets - Keystore */ wallet_keystore_invalid_password: 21901, wallet_keystore_unsupported_version: 21902, - wallet_ledger_address_not_found: 20122, - wallet_ledger_app_not_open: 20110, - wallet_ledger_chain_not_supported: 20119, - wallet_ledger_connection_claimed: 20102, - /** - * Wallets - Ledger - */ - wallet_ledger_connection_error: 20101, - wallet_ledger_derivation_path_error: 20108, - wallet_ledger_device_locked: 20105, - wallet_ledger_device_not_found: 20104, - wallet_ledger_failed_to_get_address: 20123, - wallet_ledger_get_address_error: 20103, - wallet_ledger_invalid_account: 20121, - wallet_ledger_invalid_asset: 20120, - wallet_ledger_invalid_params: 20113, - wallet_ledger_invalid_response: 20111, - wallet_ledger_invalid_signature: 20114, - wallet_ledger_method_not_supported: 20112, - wallet_ledger_no_provider: 20115, - wallet_ledger_pubkey_not_found: 20116, - wallet_ledger_public_key_error: 20107, - wallet_ledger_signing_error: 20109, - wallet_ledger_transport_error: 20106, - wallet_ledger_transport_not_defined: 20117, - wallet_ledger_webusb_not_supported: 20118, - wallet_locked: 20007, - wallet_missing_api_key: 20002, - wallet_missing_params: 20004, /** * Wallets - Near Extensions */ @@ -337,80 +239,180 @@ const errorCodes = { wallet_near_extensions_no_provider: 22002, wallet_near_extensions_not_found: 22003, wallet_near_method_not_supported: 22003, - wallet_okx_chain_not_supported: 21302, - wallet_okx_failed_to_switch_network: 21303, - wallet_okx_no_accounts: 21304, + /** - * Wallets - OKX + * Wallets - Vultisig */ - wallet_okx_not_found: 21301, + wallet_vultisig_not_found: 22101, + wallet_vultisig_contract_address_not_provided: 22102, + wallet_vultisig_asset_not_defined: 22103, + wallet_vultisig_send_transaction_no_address: 22104, + /** - * Wallets - OneKey + * Wallets - Xaman */ - wallet_onekey_not_found: 21201, - wallet_onekey_sign_transaction_error: 21202, + wallet_xaman_not_configured: 23001, + wallet_xaman_not_connected: 23002, + wallet_xaman_auth_failed: 23003, + wallet_xaman_connection_failed: 23004, + wallet_xaman_transaction_failed: 23005, + wallet_xaman_monitoring_failed: 23006, + /** - * Wallets - Phantom + * Chainflip */ - wallet_phantom_not_found: 20201, + chainflip_channel_error: 30001, + chainflip_unknown_asset: 30002, /** - * Wallets - Polkadot + * Chainflip - Broker */ - wallet_polkadot_not_found: 20701, - wallet_provider_not_found: 20005, - wallet_radix_invalid_manifest: 20803, - wallet_radix_method_not_supported: 20804, - wallet_radix_no_account: 20805, + chainflip_broker_invalid_params: 30101, + chainflip_broker_recipient_error: 30102, + chainflip_broker_register: 30103, + chainflip_broker_tx_error: 30104, + chainflip_broker_withdraw: 30105, + chainflip_broker_fund_only_flip_supported: 30106, + chainflip_broker_fund_invalid_address: 30107, /** - * Wallets - Radix + * THORChain */ - wallet_radix_not_found: 20801, - wallet_radix_transaction_failed: 20802, + thorchain_chain_halted: 40001, + thorchain_trading_halted: 40002, + thorchain_asset_is_not_tcy: 40003, /** - * Wallets - Talisman + * THORChain - Swap */ - wallet_talisman_not_enabled: 20601, - wallet_talisman_not_found: 20602, - wallet_trezor_derivation_path_not_supported: 20502, - wallet_trezor_failed_to_get_address: 20503, + thorchain_swapin_router_required: 40101, + thorchain_swapin_vault_required: 40102, + thorchain_swapin_memo_required: 40103, + thorchain_swapin_token_required: 40104, + thorchain_preferred_asset_payout_required: 40105, /** - * Wallets - Trezor + * Toolboxes - Cosmos */ - wallet_trezor_failed_to_sign_transaction: 20501, - wallet_trezor_method_not_supported: 20505, - wallet_trezor_transport_error: 20504, - wallet_vultisig_asset_not_defined: 22103, - wallet_vultisig_contract_address_not_provided: 22102, - + toolbox_cosmos_account_not_found: 50101, + toolbox_cosmos_invalid_fee: 50102, + toolbox_cosmos_invalid_params: 50103, + toolbox_cosmos_no_signer: 50104, + toolbox_cosmos_not_supported: 50105, + toolbox_cosmos_signer_not_defined: 50106, + toolbox_cosmos_validate_address_prefix_not_found: 50107, + toolbox_cosmos_verify_signature_no_pubkey: 50108, /** - * Wallets - Vultisig + * Toolboxes - EVM */ - wallet_vultisig_not_found: 22101, - wallet_vultisig_send_transaction_no_address: 22104, - wallet_walletconnect_chain_not_supported: 20404, - wallet_walletconnect_connection_not_established: 20402, - wallet_walletconnect_invalid_method: 20405, - wallet_walletconnect_method_not_supported: 20406, - wallet_walletconnect_namespace_not_supported: 20403, + toolbox_evm_error_estimating_gas_limit: 50201, + toolbox_evm_error_sending_transaction: 50202, + toolbox_evm_gas_estimation_error: 50203, + toolbox_evm_invalid_gas_asset_address: 50204, + toolbox_evm_invalid_params: 50205, + toolbox_evm_invalid_transaction: 50206, + toolbox_evm_no_abi_fragment: 50207, + toolbox_evm_no_contract_address: 50208, + toolbox_evm_no_fee_data: 50209, + toolbox_evm_no_from_address: 50210, + toolbox_evm_no_gas_price: 50211, + toolbox_evm_no_signer_address: 50212, + toolbox_evm_no_signer: 50213, + toolbox_evm_no_to_address: 50214, + toolbox_evm_not_supported: 50215, + toolbox_evm_provider_not_eip1193_compatible: 50216, /** - * Wallets - WalletConnect + * Toolboxes - UTXO */ - wallet_walletconnect_project_id_not_specified: 20401, - wallet_xaman_auth_failed: 23003, - wallet_xaman_connection_failed: 23004, - wallet_xaman_monitoring_failed: 23006, - + toolbox_utxo_api_error: 50301, + toolbox_utxo_broadcast_failed: 50302, + toolbox_utxo_insufficient_balance: 50303, + toolbox_utxo_invalid_address: 50304, + toolbox_utxo_invalid_params: 50305, + toolbox_utxo_invalid_transaction: 50306, + toolbox_utxo_no_signer: 50307, + toolbox_utxo_not_supported: 50308, /** - * Wallets - Xaman + * Toolboxes - Solana */ - wallet_xaman_not_configured: 23001, - wallet_xaman_not_connected: 23002, - wallet_xaman_transaction_failed: 23005, - wallet_xdefi_chain_not_supported: 21602, + toolbox_solana_no_signer: 50401, + toolbox_solana_fee_estimation_failed: 50402, /** - * Wallets - XDefi + * Toolboxes - Substrate */ - wallet_xdefi_not_found: 21601, + toolbox_substrate_not_supported: 50501, + toolbox_substrate_transfer_error: 50502, + /** + * Toolboxes - Radix + */ + toolbox_radix_method_not_supported: 50601, + /** + * Toolboxes - Ripple + */ + toolbox_ripple_get_balance_error: 50701, + toolbox_ripple_rpc_not_configured: 50702, + toolbox_ripple_signer_not_found: 50703, + toolbox_ripple_asset_not_supported: 50704, + toolbox_ripple_broadcast_error: 50705, + /** + * Toolboxes - Tron + */ + toolbox_tron_no_signer: 50801, + toolbox_tron_invalid_token_identifier: 50802, + toolbox_tron_token_transfer_failed: 50803, + toolbox_tron_transaction_creation_failed: 50804, + toolbox_tron_fee_estimation_failed: 50805, + toolbox_tron_trongrid_api_error: 50806, + toolbox_tron_approve_failed: 50807, + toolbox_tron_invalid_token_contract: 50808, + toolbox_tron_allowance_check_failed: 50809, + /** + * Toolboxes - Near + */ + toolbox_near_no_signer: 90601, + toolbox_near_invalid_address: 90602, + toolbox_near_invalid_amount: 90603, + toolbox_near_transfer_failed: 90604, + toolbox_near_access_key_error: 90605, + toolbox_near_no_rpc_url: 90606, + toolbox_near_empty_batch: 90607, + toolbox_near_balance_failed: 90608, + toolbox_near_invalid_name: 90609, + toolbox_near_missing_contract_address: 90610, + toolbox_near_no_account: 90611, + toolbox_near_invalid_gas_params: 90612, + /** + * Toolboxes - General + */ + toolbox_not_supported: 59901, + /** + * NEAR Plugin + */ + plugin_near_invalid_name: 41001, + plugin_near_no_connection: 41002, + plugin_near_name_unavailable: 41003, + plugin_near_registration_failed: 41004, + plugin_near_transfer_failed: 41005, + /** + * SwapKit API + */ + api_v2_invalid_response: 60001, + api_v2_server_error: 60002, + api_v2_invalid_method_key_hash: 60003, + /** + * Helpers + */ + helpers_invalid_number_different_decimals: 70001, + helpers_invalid_number_of_years: 70002, + helpers_invalid_identifier: 70003, + helpers_invalid_asset_url: 70004, + helpers_invalid_asset_identifier: 70005, + helpers_invalid_memo_type: 70006, + helpers_failed_to_switch_network: 70007, + helpers_not_found_provider: 70008, + helpers_chain_not_supported: 70009, + helpers_invalid_params: 70010, + helpers_invalid_response: 70011, + /** + * Anything else + */ + not_implemented: 99999, } as const; export type ErrorKeys = keyof typeof errorCodes; diff --git a/packages/helpers/src/types/chains.ts b/packages/helpers/src/types/chains.ts index 50cb291937..3d3c95a83d 100644 --- a/packages/helpers/src/types/chains.ts +++ b/packages/helpers/src/types/chains.ts @@ -270,7 +270,7 @@ export const RPC_URLS: Record = { [Chain.Radix]: "https://radix-mainnet.rpc.grove.city/v1/326002fc/core", [Chain.Ripple]: "wss://xrpl.ws/", [Chain.Solana]: "https://solana-rpc.publicnode.com", - [Chain.THORChain]: "https://rpc.thorswap.net", + [Chain.THORChain]: "https://rpc.ninerealms.com", [Chain.Tron]: "https://tron-rpc.publicnode.com", [Chain.Zcash]: "https://api.tatum.io/v3/blockchain/node/zcash-mainnet/t-6894a2ae7fc90cccfd3ce71b-2fce88aa7f4a41a5b1e93874", diff --git a/packages/plugins/src/chainflip/plugin.ts b/packages/plugins/src/chainflip/plugin.ts index 21c48ff1ce..c1b25b3ef5 100644 --- a/packages/plugins/src/chainflip/plugin.ts +++ b/packages/plugins/src/chainflip/plugin.ts @@ -1,4 +1,4 @@ -import { AssetValue, type CryptoChain, ProviderName, SwapKitError } from "@swapkit/helpers"; +import { AssetValue, type Chain, type CryptoChain, ProviderName, SwapKitError } from "@swapkit/helpers"; import { SwapKitApi } from "@swapkit/helpers/api"; import { createPlugin } from "../utils"; import type { RequestSwapDepositAddressParams } from "./types"; @@ -27,7 +27,7 @@ export const ChainflipPlugin = createPlugin({ const sellAsset = await AssetValue.from({ asset: sellAssetString, asyncTokenLookup: true, value: sellAmount }); - const wallet = getWallet(sellAsset.chain as CryptoChain); + const wallet = getWallet(sellAsset.chain as Exclude); if (!wallet) { throw new SwapKitError("core_wallet_connection_not_found"); @@ -39,8 +39,6 @@ export const ChainflipPlugin = createPlugin({ maxBoostFeeBps: maxBoostFeeBps || chainflip.maxBoostFeeBps, }); - // @ts-expect-error TODO: right now it's inferred from toolboxes - // we need to simplify this to one object params const tx = await wallet.transfer({ assetValue: sellAsset, isProgramDerivedAddress: true, @@ -48,7 +46,7 @@ export const ChainflipPlugin = createPlugin({ sender: wallet.address, }); - return tx as string; + return tx; }, }), name: "chainflip", diff --git a/packages/toolboxes/src/solana/index.ts b/packages/toolboxes/src/solana/index.ts index 3bc08b6f36..3464027692 100644 --- a/packages/toolboxes/src/solana/index.ts +++ b/packages/toolboxes/src/solana/index.ts @@ -1,20 +1,18 @@ -import type { PublicKey, SendOptions, Transaction, VersionedTransaction } from "@solana/web3.js"; +import type { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; import type { GenericCreateTransactionParams, GenericTransferParams } from "@swapkit/helpers"; import type { getSolanaToolbox } from "./toolbox"; -type DisplayEncoding = "utf8" | "hex"; +// type DisplayEncoding = "utf8" | "hex"; -type PhantomEvent = "connect" | "disconnect" | "accountChanged"; - -type PhantomRequestMethod = - | "connect" - | "disconnect" - | "signAndSendTransaction" - | "signAndSendTransactionV0" - | "signAndSendTransactionV0WithLookupTable" - | "signTransaction" - | "signAllTransactions" - | "signMessage"; +// type PhantomRequestMethod = +// | "connect" +// | "disconnect" +// | "signAndSendTransaction" +// | "signAndSendTransactionV0" +// | "signAndSendTransactionV0WithLookupTable" +// | "signTransaction" +// | "signAllTransactions" +// | "signMessage"; interface ConnectOpts { onlyIfTrusted: boolean; @@ -27,19 +25,8 @@ export type SolanaWallet = Awaited>; export interface SolanaProvider { connect: (opts?: Partial) => Promise<{ publicKey: PublicKey }>; disconnect: () => Promise; - getAddress: () => Promise; - isConnected: boolean | null; - isPhantom: boolean; - on: (event: PhantomEvent, handler: (args: any) => void) => void; publicKey: PublicKey | null; - request: (method: PhantomRequestMethod, params: any) => Promise; - signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise; - signAndSendTransaction: ( - transaction: Transaction | VersionedTransaction, - opts?: SendOptions, - ) => Promise<{ signature: string; publicKey: PublicKey }>; signTransaction: (transaction: T) => Promise; - signAllTransactions: (transactions: T[]) => Promise; } export type SolanaCreateTransactionParams = Omit & { diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index 1edeba3e66..82efe7eb39 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -332,7 +332,7 @@ function transfer(getConnection: () => Promise, signer?: SolanaSigne sender, }); - if ("connect" in signer) { + if ("signTransaction" in signer) { const signedTransaction = await signer.signTransaction(transaction); return broadcastTransaction(getConnection)(signedTransaction); } diff --git a/packages/toolboxes/src/substrate/substrate.ts b/packages/toolboxes/src/substrate/substrate.ts index 82499aa13f..f46305f3f4 100644 --- a/packages/toolboxes/src/substrate/substrate.ts +++ b/packages/toolboxes/src/substrate/substrate.ts @@ -82,11 +82,10 @@ const transfer = async ( { recipient, assetValue, sender }: SubstrateTransferParams, ) => { const transfer = createTransaction(api, { assetValue, recipient }); + if (!transfer) throw new SwapKitError("toolbox_substrate_transfer_error"); const isKeyring = isKeyringPair(signer); - if (!transfer) return; - const address = isKeyring ? (signer as IKeyringPair).address : sender; if (!address) throw new SwapKitError("core_transaction_invalid_sender_address"); @@ -97,7 +96,7 @@ const transfer = async ( signer: isKeyring ? undefined : signer, }); - return tx?.toString(); + return tx.toString(); }; const estimateTransactionFee = async ( diff --git a/packages/wallet-hardware/src/ledger/clients/utxo.ts b/packages/wallet-hardware/src/ledger/clients/utxo.ts index 3dc1d21580..9eaa881cb0 100644 --- a/packages/wallet-hardware/src/ledger/clients/utxo.ts +++ b/packages/wallet-hardware/src/ledger/clients/utxo.ts @@ -6,36 +6,6 @@ import { type Psbt, Transaction } from "bitcoinjs-lib"; import { getLedgerTransport } from "../helpers/getLedgerTransport"; -type Params = { psbt: Psbt; inputUtxos: UTXOType[]; btcApp: any; derivationPath: string }; - -const signUTXOTransaction = ( - { psbt, inputUtxos, btcApp, derivationPath }: Params, - options?: Partial, -) => { - const inputs = inputUtxos.map((item) => { - const utxoTx = Transaction.fromHex(item.txHex || ""); - const splitTx = btcApp.splitTransaction(utxoTx.toHex(), utxoTx.hasWitnesses()); - - return [splitTx, item.index, undefined as string | null | undefined, undefined as number | null | undefined] as any; - }); - - const newTxHex = psbt.data.globalMap.unsignedTx.toBuffer().toString("hex"); - - const splitNewTx = btcApp.splitTransaction(newTxHex, true); - const outputScriptHex = btcApp.serializeTransactionOutputs(splitNewTx).toString("hex"); - - const params: CreateTransactionArg = { - additionals: ["bech32"], - associatedKeysets: inputs.map(() => derivationPath), - inputs, - outputScriptHex, - segwit: true, - useTrustedInputForSegwit: true, - }; - - return btcApp.createPaymentTransaction({ ...params, ...options }); -}; - const BaseLedgerUTXO = ({ chain, additionalSignParams, @@ -56,13 +26,6 @@ const BaseLedgerUTXO = ({ transport ||= await getLedgerTransport(); } - async function createTransportWebUSB() { - transport = await getLedgerTransport(); - const BitcoinApp = (await import("@ledgerhq/hw-app-btc")).default; - - btcApp = new BitcoinApp({ currency: chain, transport }); - } - return (derivationPathArray?: DerivationPathArray | string) => { const derivationPath = typeof derivationPathArray === "string" @@ -101,9 +64,35 @@ const BaseLedgerUTXO = ({ return btcApp.getWalletXpub({ path, xpubVersion }); }, signTransaction: async (psbt: Psbt, inputUtxos: UTXOType[]) => { - await createTransportWebUSB(); + await checkBtcAppAndCreateTransportWebUSB(false); - return signUTXOTransaction({ btcApp, derivationPath, inputUtxos, psbt }, additionalSignParams); + const inputs = inputUtxos.map((item) => { + const utxoTx = Transaction.fromHex(item.txHex || ""); + const splitTx = btcApp.splitTransaction(utxoTx.toHex(), utxoTx.hasWitnesses()); + + return [ + splitTx, + item.index, + undefined as string | null | undefined, + undefined as number | null | undefined, + ] as any; + }); + + const newTxHex = psbt.data.globalMap.unsignedTx.toBuffer().toString("hex"); + + const splitNewTx = btcApp.splitTransaction(newTxHex, true); + const outputScriptHex = btcApp.serializeTransactionOutputs(splitNewTx).toString("hex"); + + const params: CreateTransactionArg = { + additionals: ["bech32"], + associatedKeysets: inputs.map(() => derivationPath), + inputs, + outputScriptHex, + segwit: true, + useTrustedInputForSegwit: true, + }; + + return btcApp.createPaymentTransaction({ ...params, ...additionalSignParams }); }, }; }; diff --git a/packages/wallet-hardware/src/ledger/types.ts b/packages/wallet-hardware/src/ledger/types.ts index 3cb225d6c6..98077f2cc4 100644 --- a/packages/wallet-hardware/src/ledger/types.ts +++ b/packages/wallet-hardware/src/ledger/types.ts @@ -12,13 +12,14 @@ import type { } from "./clients/evm"; import type { THORChainLedger } from "./clients/thorchain"; import type { TronLedger } from "./clients/tron"; -import type { BitcoinCashLedger, BitcoinLedger, DogecoinLedger, LitecoinLedger } from "./clients/utxo"; +import type { BitcoinCashLedger, BitcoinLedger, DogecoinLedger, LitecoinLedger, ZcashLedger } from "./clients/utxo"; export type UTXOLedgerClients = | ReturnType | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; export type CosmosLedgerClients = CosmosLedger | THORChainLedger; export type EVMLedgerClients = | ReturnType diff --git a/packages/wallets/package.json b/packages/wallets/package.json index 02bfa4799b..3696652e52 100644 --- a/packages/wallets/package.json +++ b/packages/wallets/package.json @@ -6,7 +6,7 @@ "@cosmjs/proto-signing": "~0.33.0", "@keplr-wallet/types": "~0.12.238", "@passkeys/core": "^4.0.0", - "@passkeys/react": "^3.0.0", + "@passkeys/react": "^3.0.1", "@radixdlt/babylon-gateway-api-sdk": "~1.10.0", "@radixdlt/radix-dapp-toolkit": "~2.2.0", "@scure/base": "~1.2.0", @@ -58,7 +58,11 @@ "xumm": "1.8.0" }, "exports": { - ".": { "default": "./dist/src/index.js", "require": "./dist/src/index.cjs", "types": "./dist/types/index.d.ts" }, + ".": { + "default": "./dist/src/index.js", + "require": "./dist/src/index.cjs", + "types": "./dist/types/index.d.ts" + }, "./bitget": { "default": "./dist/src/bitget/index.js", "require": "./dist/src/bitget/index.cjs", @@ -170,11 +174,16 @@ "types": "./dist/types/xaman/index.d.ts" } }, - "files": ["dist/"], + "files": [ + "dist/" + ], "homepage": "https://github.com/swapkit/SwapKit", "license": "Apache-2.0", "name": "@swapkit/wallets", - "repository": { "type": "git", "url": "git+https://github.com/swapkit/SwapKit.git" }, + "repository": { + "type": "git", + "url": "git+https://github.com/swapkit/SwapKit.git" + }, "scripts": { "build": "bun run ./build.ts", "build:clean": "rm -rf dist && bun run ./build.ts", diff --git a/packages/wallets/src/evm-extensions/index.ts b/packages/wallets/src/evm-extensions/index.ts index a1d59cb1e1..309b0343ad 100644 --- a/packages/wallets/src/evm-extensions/index.ts +++ b/packages/wallets/src/evm-extensions/index.ts @@ -26,7 +26,8 @@ const getWalletForType = ( | WalletOption.OKX_MOBILE | WalletOption.METAMASK | WalletOption.TRUSTWALLET_WEB - | WalletOption.COINBASE_WEB, + | WalletOption.COINBASE_WEB + | WalletOption.EIP6963, ) => { switch (walletType) { case WalletOption.COINBASE_WEB: @@ -84,44 +85,26 @@ export const evmWallet = createWallet({ await Promise.all( filteredChains.map(async (chain) => { - if (walletType === WalletOption.EIP6963) { - if (!eip1193Provider) throw new SwapKitError("wallet_evm_extensions_no_provider"); + if (walletType === WalletOption.EIP6963 && !eip1193Provider) + throw new SwapKitError("wallet_evm_extensions_no_provider"); - await eip1193Provider.request({ method: "eth_requestAccounts" }); + const windowProvider = eip1193Provider || getWalletForType(walletType); + const browserProvider = new BrowserProvider(windowProvider, "any"); - const provider = new BrowserProvider(eip1193Provider, "any"); - await provider.send("eth_requestAccounts", []); - const signer = await provider.getSigner(); - const address = await signer.getAddress(); - - const walletMethods = await getWeb3WalletMethods({ - address, - chain, - provider, - walletProvider: eip1193Provider, - }); - - addChain({ ...walletMethods, address, chain, walletType }); - return; - } - const walletProvider = getWalletForType(walletType); - - await walletProvider.request({ method: "eth_requestAccounts" }); - - const web3provider = new BrowserProvider(walletProvider, "any"); - const signer = await web3provider.getSigner(); + await browserProvider.send("eth_requestAccounts", []); + const signer = await browserProvider.getSigner(); const address = await signer.getAddress(); const walletMethods = await getWeb3WalletMethods({ address, chain, - provider: web3provider, - walletProvider: getWalletForType(walletType), + provider: browserProvider, + walletProvider: windowProvider, }); - const disconnect = () => web3provider.send("wallet_revokePermissions", [{ eth_accounts: {} }]); - - addChain({ ...walletMethods, address, chain, disconnect, walletType }); + const disconnect = () => browserProvider.send("wallet_revokePermissions", [{ eth_accounts: {} }]); + addChain({ ...walletMethods, address, chain, walletType, disconnect }); + return; }), ); diff --git a/packages/wallets/src/exodus/index.ts b/packages/wallets/src/exodus/index.ts index af5fd8411d..779cf1184b 100644 --- a/packages/wallets/src/exodus/index.ts +++ b/packages/wallets/src/exodus/index.ts @@ -1,18 +1,16 @@ import type { Wallet } from "@passkeys/core"; import { - type AssetValue, Chain, EVMChains, filterSupportedChains, - type GenericTransferParams, prepareNetworkSwitch, SwapKitError, switchEVMWalletNetwork, WalletOption, } from "@swapkit/helpers"; +import type { SolanaProvider } from "@swapkit/toolboxes/solana"; import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core"; import { Psbt } from "bitcoinjs-lib"; -// BrowserProvider imported dynamically when needed import { AddressPurpose, BitcoinNetworkType, @@ -124,7 +122,9 @@ async function getWalletMethods({ wallet, chain }: { wallet: Wallet; chain: Chai case Chain.Solana: { const { getSolanaToolbox } = await import("@swapkit/toolboxes/solana"); - const provider = await wallet.getProvider("solana"); + const provider = (await wallet.getProvider("solana")) as any as SolanaProvider; + + provider?.publicKey; if (!provider) { throw new SwapKitError("wallet_exodus_not_found"); @@ -132,42 +132,14 @@ async function getWalletMethods({ wallet, chain }: { wallet: Wallet; chain: Chai const providerConnection = await provider.connect(); const address: string = providerConnection.publicKey.toString(); - const toolbox = await getSolanaToolbox(); - - const transfer = async ({ - recipient, - assetValue, - isProgramDerivedAddress, - }: GenericTransferParams & { assetValue: AssetValue; isProgramDerivedAddress?: boolean }) => { - // const { PublicKey } = await import("@solana/web3.js"); // TODO: Use for advanced transactions - const validateAddress = await toolbox.getAddressValidator(); - - if (!(isProgramDerivedAddress || validateAddress(recipient))) { - throw new SwapKitError("core_transaction_invalid_recipient_address"); - } - - // const fromPubkey = new PublicKey(address); // TODO: Use for advanced transactions - const connection = await toolbox.getConnection(); - const transaction = await toolbox.createTransaction({ - assetValue, - isProgramDerivedAddress, - recipient, - sender: address, - }); - - const signedTransaction = await provider.signTransaction(transaction); - const serialized = signedTransaction.serialize(); - const txHash = await connection.sendRawTransaction(serialized); - - return txHash; - }; + const toolbox = await getSolanaToolbox({ signer: provider }); const disconnect = async () => { await provider.disconnect(); }; - return { ...toolbox, address, disconnect, transfer }; + return { ...toolbox, address, disconnect }; } default: diff --git a/packages/wallets/src/walletconnect/index.ts b/packages/wallets/src/walletconnect/index.ts index 412c77e9d5..e701c9ba13 100644 --- a/packages/wallets/src/walletconnect/index.ts +++ b/packages/wallets/src/walletconnect/index.ts @@ -1,7 +1,6 @@ import type { StdSignDoc } from "@cosmjs/amino"; import { Chain, - ChainId, filterSupportedChains, type GenericTransferParams, getRPCUrl, @@ -9,7 +8,7 @@ import { SwapKitError, WalletOption, } from "@swapkit/helpers"; -import type { createThorchainToolbox, ThorchainDepositParams } from "@swapkit/toolboxes/cosmos"; +import type { ThorchainDepositParams } from "@swapkit/toolboxes/cosmos"; import type { NearSigner } from "@swapkit/toolboxes/near"; import type { TronSignedTransaction, TronSigner, TronTransaction } from "@swapkit/toolboxes/tron"; import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core"; @@ -140,27 +139,7 @@ async function getToolbox({ getDefaultChainFee, parseAminoMessageForDirectSigning, } = await import("@swapkit/toolboxes/cosmos"); - const toolbox = await getCosmosToolbox(Chain.THORChain); - - async function getAccount(accountAddress: string) { - const cosmosToolbox = toolbox; - const account = await (cosmosToolbox as Awaited>).getAccount( - accountAddress, - ); - - if (chain !== Chain.THORChain) { - return account; - } - - const [{ address, algo, pubkey }] = (await walletconnect?.client.request({ - chainId: THORCHAIN_MAINNET_ID, - request: { method: DEFAULT_COSMOS_METHODS.COSMOS_GET_ACCOUNTS, params: {} }, - // @ts-expect-error - topic: session.topic, - })) as [{ address: string; algo: string; pubkey: string }]; - - return { ...account, address, pubkey: { type: algo, value: pubkey } }; - } + const toolbox = await getCosmosToolbox(chain); const fee = getDefaultChainFee(chain); @@ -183,11 +162,16 @@ async function getToolbox({ const { accountNumber, sequence = 0 } = account; - const msgs = [buildAminoMsg({ assetValue, memo, sender: address, ...rest })]; + const msgs = [buildAminoMsg({ ...rest, assetValue, memo, sender: address })]; - const chainId = ChainId.THORChain; - - const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber?.toString(), sequence?.toString() || "0"); + const signDoc = makeSignDoc( + msgs, + fee, + assetValue.chainId, + memo, + accountNumber?.toString(), + sequence?.toString() || "0", + ); const signature: any = await signRequest(signDoc); @@ -224,7 +208,6 @@ async function getToolbox({ return { ...toolbox, deposit: (params: ThorchainDepositParams) => thorchainTransfer(params), - getAccount, transfer: (params: GenericTransferParams) => thorchainTransfer(params), }; } @@ -322,7 +305,7 @@ async function getToolbox({ async function getWalletconnect( chains: Chain[], - walletConnectProjectId?: string, + walletConnectProjectId: string, walletconnectOptions?: SignClientTypes.Options, ) { let modal: WalletConnectModal | undefined; @@ -330,9 +313,6 @@ async function getWalletconnect( let session: SessionTypes.Struct | undefined; let accounts: string[] | undefined; try { - if (!walletConnectProjectId) { - throw new SwapKitError("wallet_walletconnect_project_id_not_specified"); - } const requiredNamespaces = getRequiredNamespaces(chains.map(chainToChainId)); const { SignClient } = await import("@walletconnect/sign-client"); diff --git a/playgrounds/vite/src/App.tsx b/playgrounds/vite/src/App.tsx index a9b87713e2..befdd51117 100644 --- a/playgrounds/vite/src/App.tsx +++ b/playgrounds/vite/src/App.tsx @@ -1,7 +1,7 @@ -import { type AssetValue, Chain, type FullWallet, SKConfig } from "@swapkit/core"; -import { WalletWidget } from "@swapkit/wallets/exodus"; +import { WalletWidget } from "@passkeys/react"; +import { type AssetValue, Chain, SKConfig } from "@swapkit/core"; +import type { FullWallet } from "@swapkit/sdk"; import { useCallback, useMemo, useState } from "react"; - import Liquidity from "./Liquidity"; import Multisig from "./Multisig"; import NearNames from "./NearNames"; diff --git a/playgrounds/vite/src/Send/index.tsx b/playgrounds/vite/src/Send/index.tsx index 44caf11917..d84b50c149 100644 --- a/playgrounds/vite/src/Send/index.tsx +++ b/playgrounds/vite/src/Send/index.tsx @@ -1,4 +1,4 @@ -import type { AssetValue } from "@swapkit/core"; +import { type AssetValue, getExplorerTxUrl } from "@swapkit/core"; import { useCallback, useState } from "react"; import type { SwapKitClient } from "../swapKitClient"; @@ -22,10 +22,10 @@ export default function Send({ inputAsset, skClient }: { skClient?: SwapKitClien const handleSend = useCallback(async () => { if (!(inputAsset && inputAssetValue?.gt(0) && skClient)) return; - const from = skClient.getAddress(inputAsset.chain); - const txHash = await skClient.transfer({ assetValue: inputAssetValue, from, memo: "", recipient }); + const sender = skClient.getAddress(inputAsset.chain); + const txHash = await skClient.transfer({ assetValue: inputAssetValue, sender, memo: "", recipient }); - window.open(`${skClient.getExplorerTxUrl({ chain: inputAssetValue.chain, txHash: txHash as string })}`, "_blank"); + window.open(`${getExplorerTxUrl({ chain: inputAssetValue.chain, txHash: txHash as string })}`, "_blank"); }, [inputAsset, inputAssetValue, skClient, recipient]); return ( diff --git a/radix-toolbox-signing-methods-analysis.md b/radix-toolbox-signing-methods-analysis.md new file mode 100644 index 0000000000..afb0c9dd4c --- /dev/null +++ b/radix-toolbox-signing-methods-analysis.md @@ -0,0 +1,94 @@ +# Radix Toolbox Signing Methods Analysis + +## Current State + +The Radix toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/radix/index.ts`) currently has **very limited** signing functionality implemented: + +### Existing Methods + +**✅ signAndBroadcast** - Partially implemented but throws an error +- **Location**: Line 127-129 +- **Implementation**: + ```typescript + signAndBroadcast: (() => { + throw new SwapKitError("toolbox_radix_method_not_supported", { method: "signAndBroadcast" }); + }) as (params: any) => Promise + ``` +- **Status**: Present but non-functional (throws error) +- **Return Type**: `Promise` + +### Other Available Methods +- `getAddress()` - Returns empty string (line 124) +- `getBalance()` - Functional implementation for fetching balances (line 125) +- `validateAddress()` - Functional implementation (line 130) + +## Missing Methods + +**❌ signTransaction** - Not implemented +- **Expected signature**: `(transaction: RadixTransaction) => Promise` +- **Purpose**: Sign a transaction and return the signed transaction without broadcasting +- **Status**: Completely missing + +**❌ signMessage** - Not implemented +- **Expected signature**: `(message: string) => Promise` +- **Purpose**: Sign an arbitrary message for authentication/verification +- **Status**: Completely missing + +**❌ signAndBroadcastTransaction** - Not implemented +- **Expected signature**: `(transaction: RadixTransaction) => Promise` +- **Purpose**: Sign and broadcast a transaction, returning the transaction hash +- **Status**: Missing (only `signAndBroadcast` exists and throws error) + +## Implementation Signatures for Reference + +Based on patterns from other toolboxes (EVM, Solana, Tron), the expected signatures should be: + +```typescript +export const RadixToolbox = async ({ dappConfig }: { dappConfig?: SKConfigIntegrations["radix"] } = {}) => { + // ... existing implementation ... + + return { + // ... existing methods ... + + // Missing methods that should be implemented: + signTransaction: async (transaction: RadixTransactionManifest): Promise => { + // Implementation needed + }, + + signAndBroadcastTransaction: async (transaction: RadixTransactionManifest): Promise => { + // Implementation needed - should return tx hash + }, + + signMessage: async (message: string): Promise => { + // Implementation needed + } + }; +}; +``` + +## Comparison with Other Toolboxes + +**EVM Toolbox** (fully implemented): +- ✅ `signTransaction: (tx: TransactionRequest) => Promise` +- ✅ `signAndBroadcastTransaction: (tx: TransactionRequest) => Promise` +- ✅ `signMessage: (message: string) => Promise` + +**Solana Toolbox** (fully implemented): +- ✅ `signTransaction: (transaction: Transaction | VersionedTransaction) => Promise` +- ✅ `signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise` + +**Tron Toolbox** (fully implemented): +- ✅ `signTransaction: (transaction: TronTransaction) => Promise` + +## Summary + +The Radix toolbox is currently **incomplete** for transaction signing functionality: + +- **0 of 3** required signing methods are properly implemented +- Only a stub `signAndBroadcast` exists that throws an error +- All three core signing methods (`signTransaction`, `signAndBroadcastTransaction`, `signMessage`) need to be implemented +- The toolbox needs integration with Radix Dapp Toolkit's signing capabilities to provide full functionality + +## Next Steps + +We skip this as the chain is very unimportant right now. diff --git a/solana-toolbox-signing-methods-analysis.md b/solana-toolbox-signing-methods-analysis.md new file mode 100644 index 0000000000..f1b9b183c8 --- /dev/null +++ b/solana-toolbox-signing-methods-analysis.md @@ -0,0 +1,70 @@ +# Solana Toolbox Signing Methods Analysis + +## Current State + +The Solana toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/solana/toolbox.ts`) currently implements **2 out of 3** requested signing methods: + +### ✅ Methods that Exist + +#### 1. `signTransaction` +**Location**: Line 137 in toolbox return object, implementation at lines 353-376 +**Signature**: +```typescript +signTransaction: (transaction: Transaction | VersionedTransaction) => Promise +``` +**Implementation Details**: +- Takes a `Transaction` or `VersionedTransaction` as input +- Handles both wallet provider signers (`signer.signTransaction()`) and direct signers (`transaction.sign([signer])`) +- Automatically sets `recentBlockhash` and `feePayer` for regular transactions +- Returns the signed transaction object +- Throws `SwapKitError("toolbox_solana_no_signer")` if no signer is available + +#### 2. `signAndBroadcastTransaction` +**Location**: Line 133-136 in toolbox return object +**Signature**: +```typescript +signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise +``` +**Implementation Details**: +- Combines signing and broadcasting in a single method +- First calls `signTransaction()` to sign the transaction +- Then calls `broadcastTransaction()` to submit to the network +- Returns the transaction hash as a string +- Uses the same signing logic as `signTransaction` + +## Missing Methods + +### ❌ `signMessage` +**Status**: **Not implemented** in the toolbox return object + +**Note**: While the `SolanaProvider` interface (in `index.ts`) defines a `signMessage` method: +```typescript +signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise +``` + +This method is **not exposed** in the actual toolbox object returned by `getSolanaToolbox()`. It's only available as part of the provider interface for wallet integrations, but not as a direct toolbox method. + +## Implementation Signatures for Reference + +### Current Signatures +```typescript +// ✅ Implemented +signTransaction: (transaction: Transaction | VersionedTransaction) => Promise + +// ✅ Implemented +signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise + +// ❌ Missing from toolbox (but exists in SolanaProvider interface) +signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise +``` + +### Supporting Infrastructure +The toolbox has all necessary infrastructure for signing: +- ✅ Signer management (`SolanaSigner` type supporting both providers and direct signers) +- ✅ Connection management (`getConnection()`) +- ✅ Error handling with SwapKit errors +- ✅ Transaction broadcasting (`broadcastTransaction()`) + +## Recommendation + +To complete the signing method suite, the `signMessage` method should be added to the toolbox return object in the `getSolanaToolbox()` function. The implementation should handle both provider-based signers (using `signer.signMessage()`) and direct signers appropriately. \ No newline at end of file diff --git a/substrate-toolbox-signing-methods-analysis.md b/substrate-toolbox-signing-methods-analysis.md new file mode 100644 index 0000000000..4e8324d1d3 --- /dev/null +++ b/substrate-toolbox-signing-methods-analysis.md @@ -0,0 +1,112 @@ +# Substrate Toolbox Signing Methods Analysis + +## Current State + +The Substrate toolbox (`/packages/toolboxes/src/substrate/substrate.ts`) currently implements **2 of 3** required signing methods: + +### ✅ Existing Methods + +#### 1. `sign` - Transaction Signing Method +**Status**: ✅ **Implemented** + +```typescript +sign: (tx: SubmittableExtrinsic<"promise">) => { + if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); + if (isKeyringPair(signer)) return sign(signer, tx); + + throw new SwapKitError( + "core_wallet_not_keypair_wallet", + "Signer does not have keyring pair capabilities required for signing.", + ); +} +``` + +**Implementation Details**: +- Takes a `SubmittableExtrinsic<"promise">` (transaction object) +- Returns a signed transaction using `tx.signAsync(signer)` +- Only works with KeyringPair signers, not external wallet signers +- Throws error if no signer is available or if signer lacks keyring capabilities + +#### 2. `signAndBroadcast` - Sign and Broadcast Method +**Status**: ✅ **Implemented** + +```typescript +signAndBroadcast: ({ + tx, + callback, + address, +}: { + tx: SubmittableExtrinsic<"promise">; + callback?: Callback; + address?: string; +}) => { + if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); + if (isKeyringPair(signer)) return signAndBroadcastKeyring(signer, tx, callback); + + if (address) { + return signAndBroadcast({ address, api, callback, signer, tx }); + } + + throw new SwapKitError( + "core_wallet_not_keypair_wallet", + "Signer does not have keyring pair capabilities required for signing.", + ); +} +``` + +**Implementation Details**: +- Takes transaction object, optional callback, and optional address +- Returns transaction hash as string +- Supports both KeyringPair and external wallet signers +- Uses `tx.signAndSend()` internally +- Handles nonce calculation automatically + +### ❌ Missing Methods + +#### 1. `signMessage` - Message Signing Method +**Status**: ❌ **Not Implemented** + +**Expected Signature** (based on other toolboxes): +```typescript +signMessage: (message: string) => Promise +``` + +**What's Missing**: +- No message signing capability exists in the toolbox +- Would need to use Polkadot.js keyring or signer's message signing functionality +- Should support both raw message signing and structured message formats + +## Method Name Mapping + +The Substrate toolbox uses slightly different naming conventions than the requested standard: + +| Requested Method | Substrate Implementation | Status | +|------------------|-------------------------|---------| +| `signTransaction` | `sign` | ✅ Equivalent functionality | +| `signAndBroadcastTransaction` | `signAndBroadcast` | ✅ Equivalent functionality | +| `signMessage` | ❌ Not implemented | ❌ Missing | + +## Implementation Notes + +### Signer Support +The toolbox supports two types of signers: +- **KeyringPair**: Local keypairs created from seed phrases +- **External Signers**: Wallet-provided signers (Polkadot.js extension wallets) + +### Transaction Handling +- Uses Polkadot.js `SubmittableExtrinsic` objects for transactions +- Automatically handles nonce calculation via `api.rpc.system.accountNextIndex` +- Supports callback-based monitoring of transaction status + +### Error Handling +- Consistent error handling using `SwapKitError` +- Specific error codes for wallet/signer issues +- Clear error messages for debugging + +## Summary + +- **2 of 3** required signing methods are implemented with equivalent functionality +- Transaction signing methods are fully functional and support both local and external signers +- Message signing capability is completely missing +- The existing implementations follow Substrate/Polkadot patterns and integrate well with the ecosystem +- Method names differ slightly from standard but provide the same core functionality \ No newline at end of file diff --git a/test_ripple_methods.js b/test_ripple_methods.js new file mode 100644 index 0000000000..a5330752f9 --- /dev/null +++ b/test_ripple_methods.js @@ -0,0 +1,31 @@ +// Simple verification test for the new Ripple methods +const { getRippleToolbox } = require('./packages/toolboxes/dist/ripple'); + +async function testRippleToolbox() { + try { + // Create toolbox with a test phrase + const phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + const rippleWallet = await getRippleToolbox({ phrase }); + + console.log('✅ getRippleToolbox created successfully'); + + // Check if the new methods exist + console.log('signAndBroadcastTransaction exists:', typeof rippleWallet.signAndBroadcastTransaction === 'function'); + console.log('signMessage exists:', typeof rippleWallet.signMessage === 'function'); + + // Test getting address + const address = await rippleWallet.getAddress(); + console.log('Address:', address); + + // Test signing a simple message + const message = "Hello SwapKit!"; + const signature = await rippleWallet.signMessage(message); + console.log('Message signature:', signature); + + console.log('✅ All tests passed!'); + } catch (error) { + console.error('❌ Test failed:', error.message); + } +} + +testRippleToolbox(); \ No newline at end of file diff --git a/test_substrate_methods.js b/test_substrate_methods.js new file mode 100644 index 0000000000..a502a111f2 --- /dev/null +++ b/test_substrate_methods.js @@ -0,0 +1,38 @@ +// Test script to verify Substrate toolbox method rename and new signMessage functionality +import { Chain } from "@swapkit/helpers"; +import { getSubstrateToolbox } from "@swapkit/toolboxes/substrate"; + +async function testSubstrateToolbox() { + console.log("Testing Substrate toolbox method renames and signMessage..."); + + try { + // Create a test toolbox for Polkadot + const toolbox = await getSubstrateToolbox(Chain.Polkadot); + + // Check that the old method names don't exist + console.log("Checking old method names..."); + console.log("- sign method exists:", typeof toolbox.sign !== "undefined"); + console.log("- signAndBroadcast method exists:", typeof toolbox.signAndBroadcast !== "undefined"); + + // Check that the new method names exist + console.log("Checking new method names..."); + console.log("- signTransaction method exists:", typeof toolbox.signTransaction === "function"); + console.log( + "- signAndBroadcastTransaction method exists:", + typeof toolbox.signAndBroadcastTransaction === "function", + ); + console.log("- signMessage method exists:", typeof toolbox.signMessage === "function"); + + // Test that signMessage has the correct signature + console.log( + "signMessage is a function that returns Promise:", + typeof toolbox.signMessage === "function" && toolbox.signMessage.constructor.name === "Function", + ); + + console.log("\n✅ All method renames and new signMessage method implemented successfully!"); + } catch (error) { + console.error("❌ Error testing toolbox:", error.message); + } +} + +testSubstrateToolbox(); diff --git a/tron-signing-methods-analysis.md b/tron-signing-methods-analysis.md new file mode 100644 index 0000000000..13d4d114ed --- /dev/null +++ b/tron-signing-methods-analysis.md @@ -0,0 +1,70 @@ +# Tron Toolbox Signing Methods Analysis + +## Current State + +### Existing Methods + +The Tron toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/tron/toolbox.ts`) currently implements: + +#### 1. ✅ `signTransaction` - **EXISTS** +- **Location**: Line 509-512 in toolbox.ts +- **Return Type**: `Promise` +- **Implementation**: + ```typescript + const signTransaction = async (transaction: TronTransaction) => { + if (!signer) throw new SwapKitError("toolbox_tron_no_signer"); + return await signer.signTransaction(transaction); + }; + ``` +- **Signature**: `signTransaction: (transaction: TronTransaction) => Promise;` +- **Status**: Fully implemented and exported from the toolbox + +## Missing Methods + +### 1. ❌ `signAndBroadcastTransaction` - **MISSING** +- **Expected Signature**: `signAndBroadcastTransaction: (transaction: TronTransaction) => Promise;` +- **Expected Behavior**: Should sign a transaction and immediately broadcast it, returning the transaction hash +- **Current Workaround**: Users must call `signTransaction()` followed by `broadcastTransaction()` separately + +### 2. ❌ `signMessage` - **MISSING** +- **Expected Signature**: `signMessage: (message: string) => Promise;` +- **Expected Behavior**: Should sign an arbitrary message and return the signature +- **Notes**: This would be useful for authentication and verification purposes + +## Implementation Signatures for Reference + +### Currently Available Methods +```typescript +// ✅ Available - Signs a transaction +signTransaction: (transaction: TronTransaction) => Promise; + +// ✅ Available - Broadcasts an already signed transaction +broadcastTransaction: (signedTransaction: TronSignedTransaction) => Promise; +``` + +### Missing Methods (Expected Signatures) +```typescript +// ❌ Missing - Signs and broadcasts in one call +signAndBroadcastTransaction: (transaction: TronTransaction) => Promise; + +// ❌ Missing - Signs arbitrary messages +signMessage: (message: string) => Promise; +``` + +## Related Types + +The toolbox uses the following relevant types: +- `TronTransaction` - Transaction object from TronWeb +- `TronSignedTransaction` - Signed transaction object from TronWeb +- `TronSigner` interface with methods: + - `getAddress(): Promise` + - `signTransaction(transaction: TronTransaction): Promise` + +## Conclusion + +The Tron toolbox currently has **1 out of 3** requested signing methods implemented: +- ✅ `signTransaction` - Fully implemented +- ❌ `signAndBroadcastTransaction` - Missing (would be a convenience method) +- ❌ `signMessage` - Missing (would require TronWeb message signing support) + +The existing `signTransaction` method works in conjunction with `broadcastTransaction` to achieve the same result as the missing `signAndBroadcastTransaction` method, but requires two separate calls. \ No newline at end of file From 03f11bdb8dece700a5a1c9321987c1ccac9fad7b Mon Sep 17 00:00:00 2001 From: towan Date: Thu, 4 Sep 2025 12:43:43 +0400 Subject: [PATCH 08/15] WIP - signing tx --- packages/helpers/src/modules/swapKitError.ts | 4 +- packages/helpers/src/types/chains.ts | 2 +- packages/plugins/src/chainflip/broker.ts | 18 +++- packages/plugins/src/swapkit/plugin.ts | 2 +- packages/plugins/src/swapkit/types.ts | 17 +--- packages/toolboxes/src/near/toolbox.ts | 34 ++++++++ packages/toolboxes/src/near/types/toolbox.ts | 2 + packages/toolboxes/src/ripple/index.ts | 19 ++++- packages/toolboxes/src/substrate/substrate.ts | 82 ++++++++++--------- 9 files changed, 120 insertions(+), 60 deletions(-) diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index f9ebbd0d4f..65aed0104b 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -1,4 +1,4 @@ -/** biome-ignore-all assist/source/useSortedKeys: its sorted by type */ +// biome-ignore assist/source/useSortedKeys: keys are sorted by topic const errorCodes = { /** * Core @@ -338,6 +338,7 @@ const errorCodes = { */ toolbox_substrate_not_supported: 50501, toolbox_substrate_transfer_error: 50502, + toolbox_substrate_no_signer: 50503, /** * Toolboxes - Radix */ @@ -350,6 +351,7 @@ const errorCodes = { toolbox_ripple_signer_not_found: 50703, toolbox_ripple_asset_not_supported: 50704, toolbox_ripple_broadcast_error: 50705, + toolbox_ripple_no_signer: 50706, /** * Toolboxes - Tron */ diff --git a/packages/helpers/src/types/chains.ts b/packages/helpers/src/types/chains.ts index 3d3c95a83d..b0cbcced91 100644 --- a/packages/helpers/src/types/chains.ts +++ b/packages/helpers/src/types/chains.ts @@ -318,7 +318,7 @@ export const FALLBACK_URLS: Record = { [Chain.Ripple]: ["wss://s1.ripple.com/", "wss://s2.ripple.com/"], [Chain.THORChain]: ["https://thornode.ninerealms.com", NODE_URLS[Chain.THORChain]], [StagenetChain.THORChain]: [], - [Chain.Solana]: ["https://api.mainnet-beta.solana.com", "https://solana-mainnet.rpc.extrnode.com"], + [Chain.Solana]: [], [Chain.Tron]: ["https://api.tronstack.io", "https://api.tron.network"], [Chain.Zcash]: [], }; diff --git a/packages/plugins/src/chainflip/broker.ts b/packages/plugins/src/chainflip/broker.ts index 8df3b7e513..9b52cc0798 100644 --- a/packages/plugins/src/chainflip/broker.ts +++ b/packages/plugins/src/chainflip/broker.ts @@ -1,4 +1,6 @@ +import type { SubmittableExtrinsic } from "@polkadot/api/types"; import { decodeAddress } from "@polkadot/keyring"; +import type { Callback, ISubmittableResult } from "@polkadot/types/types"; import { isHex, u8aToHex } from "@polkadot/util"; import { AssetValue, Chain, SwapKitError, wrapWithThrow } from "@swapkit/helpers"; import type { getEvmToolbox } from "@swapkit/toolboxes/evm"; @@ -6,7 +8,17 @@ import type { getSubstrateToolbox } from "@swapkit/toolboxes/substrate"; import type { WithdrawFeeResponse } from "./types"; -type ChainflipToolbox = Awaited>>; +type ChainflipToolbox = Awaited>> & { + signAndBroadcastTransaction: ({ + tx, + callback, + address, + }: { + tx: SubmittableExtrinsic<"promise">; + callback?: Callback; + address?: string; + }) => Promise; +}; export const assetIdentifierToChainflipTicker = new Map([ ["ARB.ETH", "ArbEth"], @@ -28,7 +40,7 @@ const registerAsBroker = (toolbox: ChainflipToolbox) => () => { throw new SwapKitError("chainflip_broker_register"); } - return toolbox.signAndBroadcast({ address: toolbox.getAddress(), tx: extrinsic }); + return toolbox.signAndBroadcastTransaction({ address: toolbox.getAddress(), tx: extrinsic }); }; const withdrawFee = @@ -49,7 +61,7 @@ const withdrawFee = throw new SwapKitError("chainflip_broker_withdraw"); } - toolbox.signAndBroadcast({ + toolbox.signAndBroadcastTransaction({ callback: (result) => { if (!result.status?.isFinalized) { return; diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts index 1974b1cbef..64270f8296 100644 --- a/packages/plugins/src/swapkit/plugin.ts +++ b/packages/plugins/src/swapkit/plugin.ts @@ -1,5 +1,5 @@ import { AssetValue, Chain, type CryptoChain, type ProviderName, SwapKitError, WalletOption } from "@swapkit/helpers"; -import { type QuoteResponseRoute, type RouteLeg, SwapKitApi } from "@swapkit/helpers/api"; +import { type QuoteResponseRoute, SwapKitApi } from "@swapkit/helpers/api"; import { Psbt } from "bitcoinjs-lib"; import type { TransactionRequest } from "ethers"; import type { SwapKitPluginParams } from "../types"; diff --git a/packages/plugins/src/swapkit/types.ts b/packages/plugins/src/swapkit/types.ts index ff58b6ba51..1a10c06aed 100644 --- a/packages/plugins/src/swapkit/types.ts +++ b/packages/plugins/src/swapkit/types.ts @@ -14,11 +14,7 @@ export type SwapKitQuoteParams = { affiliateBasisPoints?: number; }; -export type SwapKitSwapParams = { - route: QuoteResponseRoute; - recipient?: string; - feeOptionKey?: FeeOption; -}; +export type SwapKitSwapParams = { route: QuoteResponseRoute; recipient?: string; feeOptionKey?: FeeOption }; export type SwapKitTransactionParams = { // Transaction data from SwapKit API @@ -38,14 +34,7 @@ export type SwapKitTransactionParams = { msg?: any; // Fallback transfer params - transferParams?: GenericTransferParams & { - assetValue: AssetValue; - memo?: string; - }; + transferParams?: GenericTransferParams & { assetValue: AssetValue; memo?: string }; }; -export type WalletCapabilities = { - supportsSignAndBroadcast: boolean; - walletType: string; - chain: Chain; -}; +export type WalletCapabilities = { supportsSignAndBroadcast: boolean; walletType: string; chain: Chain }; diff --git a/packages/toolboxes/src/near/toolbox.ts b/packages/toolboxes/src/near/toolbox.ts index f1c3002f3a..ec696b9626 100644 --- a/packages/toolboxes/src/near/toolbox.ts +++ b/packages/toolboxes/src/near/toolbox.ts @@ -225,6 +225,38 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams): Promise return result.transaction.hash; } + async function signAndBroadcastTransaction(transaction: Transaction): Promise { + if (!signer) { + throw new SwapKitError("toolbox_near_no_signer"); + } + + try { + // Sign the transaction + const signedTransaction = await signTransaction(transaction); + + // Broadcast the signed transaction + return await broadcastTransaction(signedTransaction); + } catch (error) { + throw new SwapKitError("toolbox_near_transfer_failed", { error }); + } + } + + async function signMessage(message: string): Promise { + if (!signer?.sign) { + throw new SwapKitError("toolbox_near_no_signer"); + } + + try { + // Sign the message using the key pair + const signature = await signer.sign(message); + + // Return the signature as a base64 string + return Buffer.from(signature).toString("base64"); + } catch (error) { + throw new SwapKitError("toolbox_near_transfer_failed", { error }); + } + } + async function estimateTransactionFee(params: NearTransferParams | NearGasEstimateParams) { if ("assetValue" in params) { const baseTransferCost = "115123062500"; // gas units for transfer @@ -444,6 +476,8 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams): Promise getSignerFromPrivateKey: getNearSignerFromPrivateKey, nep141, provider, + signAndBroadcastTransaction, + signMessage, signTransaction, transfer, validateAddress: await getValidateNearAddress(), diff --git a/packages/toolboxes/src/near/types/toolbox.ts b/packages/toolboxes/src/near/types/toolbox.ts index 5e496e235b..5f50e942a0 100644 --- a/packages/toolboxes/src/near/types/toolbox.ts +++ b/packages/toolboxes/src/near/types/toolbox.ts @@ -46,6 +46,8 @@ export interface NearToolbox { estimateTransactionFee: (params: NearTransferParams | NearGasEstimateParams) => Promise; broadcastTransaction: (signedTransaction: SignedTransaction) => Promise; signTransaction: (transaction: Transaction) => Promise; + signAndBroadcastTransaction: (transaction: Transaction) => Promise; + signMessage: (message: string) => Promise; getBalance: (address: string) => Promise; validateAddress: (address: string) => boolean; getSignerFromPhrase: (params: GetSignerFromPhraseParams) => Promise; diff --git a/packages/toolboxes/src/ripple/index.ts b/packages/toolboxes/src/ripple/index.ts index 4601a23cda..4c03658512 100644 --- a/packages/toolboxes/src/ripple/index.ts +++ b/packages/toolboxes/src/ripple/index.ts @@ -35,7 +35,7 @@ export function rippleValidateAddress(address: string) { type RippleToolboxParams = | { phrase?: string } - | { signer?: ChainSigner }; + | { signer?: ChainSigner & { wallet?: Wallet } }; export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { const signer = @@ -145,6 +145,22 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { throw new SwapKitError({ errorKey: "toolbox_ripple_broadcast_error", info: { chain: Chain.Ripple } }); }; + const signAndBroadcastTransaction = async (tx: Transaction): Promise => { + if (!signer) { + throw new SwapKitError({ errorKey: "toolbox_ripple_signer_not_found" }); + } + + try { + // Sign the transaction + const signedTx = await signTransaction(tx); + + // Broadcast the signed transaction + 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" }); @@ -166,6 +182,7 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { // Core methods getAddress, getBalance, + signAndBroadcastTransaction, // Signer related signer, // Expose the signer instance if created/provided signTransaction, diff --git a/packages/toolboxes/src/substrate/substrate.ts b/packages/toolboxes/src/substrate/substrate.ts index f46305f3f4..56520c3d82 100644 --- a/packages/toolboxes/src/substrate/substrate.ts +++ b/packages/toolboxes/src/substrate/substrate.ts @@ -190,37 +190,8 @@ export const BaseSubstrateToolbox = ({ gasAsset: AssetValue; signer?: IKeyringPair | Signer; chain?: SubstrateChain; -}) => ({ - api, - broadcast, - convertAddress, - createKeyring: (phrase: string) => createKeyring(phrase, network.prefix), - createTransaction: (params: GenericCreateTransactionParams) => createTransaction(api, params), - decodeAddress, - encodeAddress, - estimateTransactionFee: (params: SubstrateTransferParams) => { - if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); - return estimateTransactionFee(api, signer, gasAsset, params); - }, - gasAsset, - getAddress: (keyring?: IKeyringPair | Signer) => { - const keyringPair = keyring || signer; - if (!keyringPair) throw new SwapKitError("core_wallet_not_keypair_wallet"); - - return isKeyringPair(keyringPair) ? keyringPair.address : undefined; - }, - getBalance: createBalanceGetter(chain || Chain.Polkadot, api), - network, - sign: (tx: SubmittableExtrinsic<"promise">) => { - if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); - if (isKeyringPair(signer)) return sign(signer, tx); - - throw new SwapKitError( - "core_wallet_not_keypair_wallet", - "Signer does not have keyring pair capabilities required for signing.", - ); - }, - signAndBroadcast: ({ +}) => { + function signAndBroadcastTransaction({ tx, callback, address, @@ -228,7 +199,7 @@ export const BaseSubstrateToolbox = ({ tx: SubmittableExtrinsic<"promise">; callback?: Callback; address?: string; - }) => { + }) { if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); if (isKeyringPair(signer)) return signAndBroadcastKeyring(signer, tx, callback); @@ -240,13 +211,46 @@ export const BaseSubstrateToolbox = ({ "core_wallet_not_keypair_wallet", "Signer does not have keyring pair capabilities required for signing.", ); - }, - transfer: (params: SubstrateTransferParams) => { - if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); - return transfer(api, signer, params); - }, - validateAddress: (address: string) => validateAddress(address, network.prefix), -}); + } + + return { + api, + broadcast, + convertAddress, + createKeyring: (phrase: string) => createKeyring(phrase, network.prefix), + createTransaction: (params: GenericCreateTransactionParams) => createTransaction(api, params), + decodeAddress, + encodeAddress, + estimateTransactionFee: (params: SubstrateTransferParams) => { + if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); + return estimateTransactionFee(api, signer, gasAsset, params); + }, + gasAsset, + getAddress: (keyring?: IKeyringPair | Signer) => { + const keyringPair = keyring || signer; + if (!keyringPair) throw new SwapKitError("core_wallet_not_keypair_wallet"); + + return isKeyringPair(keyringPair) ? keyringPair.address : undefined; + }, + getBalance: createBalanceGetter(chain || Chain.Polkadot, api), + network, + sign, + /** + * @deprecated @V4 Use signAndBroadcastTransaction instead - will be removed in next major + */ + signAndBroadcast: signAndBroadcastTransaction, + signAndBroadcastTransaction, + signTransaction: (tx: SubmittableExtrinsic<"promise">) => { + if (!signer || !isKeyringPair(signer)) throw new SwapKitError("toolbox_substrate_no_signer"); + return sign(signer, tx); + }, + transfer: (params: SubstrateTransferParams) => { + if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); + return transfer(api, signer, params); + }, + validateAddress: (address: string) => validateAddress(address, network.prefix), + }; +}; export const substrateValidateAddress = ({ address, From 195253bc43b7f01e6aa60158ef42bf574b031cdd Mon Sep 17 00:00:00 2001 From: towan Date: Thu, 4 Sep 2025 18:22:16 +0400 Subject: [PATCH 09/15] WIP - signing tx --- packages/helpers/src/api/swapkitApi/types.ts | 33 +- packages/helpers/src/modules/swapKitError.ts | 4 + packages/plugins/src/swapkit/plugin.ts | 283 +++++++----------- .../src/evm/toolbox/baseEVMToolbox.ts | 15 +- packages/toolboxes/src/tron/toolbox.ts | 7 + .../toolboxes/src/utxo/toolbox/bitcoinCash.ts | 189 ++++-------- packages/toolboxes/src/utxo/toolbox/index.ts | 5 +- packages/toolboxes/src/utxo/toolbox/utxo.ts | 54 ++-- 8 files changed, 241 insertions(+), 349 deletions(-) diff --git a/packages/helpers/src/api/swapkitApi/types.ts b/packages/helpers/src/api/swapkitApi/types.ts index a7a2e3bd91..1c8fa49bca 100644 --- a/packages/helpers/src/api/swapkitApi/types.ts +++ b/packages/helpers/src/api/swapkitApi/types.ts @@ -403,6 +403,20 @@ 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 EVMTransactionDetailsParamsSchema = array( union([ string(), @@ -412,6 +426,21 @@ export const EVMTransactionDetailsParamsSchema = array( ]), ); +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 type EVMTransactionDetailsParams = z.infer; export const EVMTransactionDetailsSchema = object({ @@ -527,7 +556,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 65aed0104b..ec3ae500a8 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -391,6 +391,10 @@ const errorCodes = { plugin_near_name_unavailable: 41003, plugin_near_registration_failed: 41004, plugin_near_transfer_failed: 41005, + /** + * SwapKit Plugin + */ + plugin_swapkit_invalid_tx_data: 59801, /** * SwapKit API */ diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts index 64270f8296..21db652c3d 100644 --- a/packages/plugins/src/swapkit/plugin.ts +++ b/packages/plugins/src/swapkit/plugin.ts @@ -1,19 +1,32 @@ -import { AssetValue, Chain, type CryptoChain, type ProviderName, SwapKitError, WalletOption } from "@swapkit/helpers"; -import { type QuoteResponseRoute, SwapKitApi } from "@swapkit/helpers/api"; +import { bitgo, networks } from "@bitgo/utxo-lib"; +import type { ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; +import { Transaction } from "@near-js/transactions"; +import { VersionedTransaction } from "@solana/web3.js"; +import { + AssetValue, + Chain, + type CosmosChain, + CosmosChains, + type CryptoChain, + type EVMChain, + EVMChains, + ProviderName, + SwapKitError, +} from "@swapkit/helpers"; +import { + CosmosTransactionSchema, + EVMTransactionSchema, + NEARTransactionSchema, + type QuoteResponseRoute, + SwapKitApi, + TronTransactionSchema, +} from "@swapkit/helpers/api"; import { Psbt } from "bitcoinjs-lib"; -import type { TransactionRequest } from "ethers"; +import { match } from "ts-pattern"; import type { SwapKitPluginParams } from "../types"; import { createPlugin } from "../utils"; import type { SwapKitQuoteParams, SwapKitSwapParams } from "./types"; -// Wallets that don't support signing and need special handling -const WALLETS_WITHOUT_SIGNING = [WalletOption.CTRL, WalletOption.VULTISIG, WalletOption.KEEPKEY_BEX]; - -// Check if wallet supports signAndBroadcastTransaction -function walletSupportsSignAndBroadcast(walletType: WalletOption | string): boolean { - return !WALLETS_WITHOUT_SIGNING.includes(walletType as WalletOption); -} - export const SwapKitPlugin = createPlugin({ methods: (params: SwapKitPluginParams) => { const { getWallet } = params; @@ -54,200 +67,110 @@ export const SwapKitPlugin = createPlugin({ * Execute a swap using the SwapKit API route */ async function swap(swapParams: SwapKitSwapParams): Promise { - const { route } = swapParams; - - // Extract transaction data from the route - // TODO: Update when SwapKit API provides transaction data - const transactionData = (route as QuoteResponseRoute).tx; + const { + route: { tx, ...route }, + } = swapParams; // Determine the source chain from the sell asset const sellAsset = AssetValue.from({ asset: route.sellAsset }); - const chain = sellAsset.chain as CryptoChain; - - // Get the wallet for the source chain - const wallet = getWallet(chain); - if (!wallet) { - throw new SwapKitError("core_wallet_connection_not_found", { chain }); - } - - // Check if wallet supports signAndBroadcastTransaction - const walletType = wallet.walletType || ""; - const supportsSignAndBroadcast = walletSupportsSignAndBroadcast(walletType); + const chain = sellAsset.chain; try { - // Handle different chain types - switch (chain) { - case Chain.Bitcoin: - case Chain.BitcoinCash: - case Chain.Dogecoin: - case Chain.Litecoin: - case Chain.Zcash: { - // UTXO chains - use PSBT - const anyWallet = getWallet(chain); - - // Try signing first if wallet is expected to support it - if (supportsSignAndBroadcast && transactionData) { - try { - // Parse PSBT from hex or base64 - const psbt = Psbt.fromBase64(transactionData as string); - return await anyWallet.signMessage(psbt); - } catch (signError) { - // If signing fails, try transfer fallback - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); - } - throw signError; - } + return await match(chain as CryptoChain) + .returnType>() + .with(Chain.BitcoinCash, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - - // Direct to transfer for wallets known not to support signing - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); + return await getWallet(chain as Chain.BitcoinCash).signAndBroadcastTransaction( + bitgo.UtxoPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.bitcoincash }), + ); + }) + .with(Chain.Zcash, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - - throw new SwapKitError("core_swap_invalid_params", { error: "Unable to execute UTXO transaction" }); - } - - case Chain.Ethereum: - case Chain.Avalanche: - case Chain.BinanceSmartChain: - case Chain.Polygon: - case Chain.Arbitrum: - case Chain.Optimism: - case Chain.Base: { - // EVM chains - const anyWallet = wallet as any; - - // Try signing first if wallet is expected to support it - if (supportsSignAndBroadcast && transactionData.to) { - try { - // Create ethers TransactionRequest - const txRequest: TransactionRequest = { - data: transactionData.data, - gasLimit: transactionData.gas ? BigInt(transactionData.gas) : undefined, - gasPrice: transactionData.gasPrice ? BigInt(transactionData.gasPrice) : undefined, - to: transactionData.to, - value: transactionData.value ? BigInt(transactionData.value) : undefined, - }; - return await anyWallet.signAndBroadcastTransaction(txRequest); - } catch (signError) { - // If signing fails, try transfer fallback - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); - } - throw signError; - } + return await getWallet(chain as Chain.Zcash).signAndBroadcastTransaction( + bitgo.ZcashPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.zcash }) as ZcashPsbt, + ); + }) + .with(Chain.Bitcoin, Chain.Dogecoin, Chain.Litecoin, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - - // Direct to transfer for wallets known not to support signing - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); + const psbt = Psbt.fromBase64(tx); + return await getWallet( + chain as Chain.Bitcoin | Chain.Dogecoin | Chain.Litecoin, + ).signAndBroadcastTransaction(psbt); + }) + .with(...EVMChains, async () => { + const transaction = EVMTransactionSchema.safeParse(tx); + if (!transaction.success) { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - throw new SwapKitError("core_swap_invalid_params", { error: "Unable to execute EVM transaction" }); - } - - case Chain.Solana: { - // Solana - const anyWallet = wallet as any; - - // Try signing first if wallet is expected to support it - if (supportsSignAndBroadcast && transactionData.transaction) { - try { - // Parse Solana transaction from base64 - const { Transaction, VersionedTransaction } = await import("@solana/web3.js"); - - // Try to parse as VersionedTransaction first - try { - const buffer = Buffer.from(transactionData.transaction, "base64"); - const tx = VersionedTransaction.deserialize(buffer); - return await anyWallet.signAndBroadcastTransaction(tx); - } catch { - // Fall back to legacy Transaction - const tx = Transaction.from(Buffer.from(transactionData.transaction, "base64")); - return await anyWallet.signAndBroadcastTransaction(tx); - } - } catch (signError) { - // If signing fails, try transfer fallback - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); - } - throw signError; - } + return await getWallet(chain as EVMChain).signAndBroadcastTransaction(transaction.data); + }) + .with(Chain.Solana, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); + } + const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); + return await getWallet(chain as Chain.Solana).signAndBroadcastTransaction(transaction); + }) + .with(...CosmosChains, async () => { + const transaction = CosmosTransactionSchema.safeParse(tx); + if (!transaction.success) { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - // Direct to transfer for wallets known not to support signing - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); + return await getWallet(chain as CosmosChain).transfer(transaction.data); + }) + .with(Chain.Near, async () => { + const transaction = NEARTransactionSchema.safeParse(tx); + if (!transaction.success) { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - throw new SwapKitError("core_swap_invalid_params", { error: "Unable to execute Solana transaction" }); - } + return await getWallet(chain as Chain.Near).signAndBroadcastTransaction( + Transaction.decode(Buffer.from(transaction.data.serialized, "base64")), + ); + }) + .with(Chain.Radix, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); + } - case Chain.THORChain: - case Chain.Maya: - case Chain.Cosmos: - case Chain.Kujira: - case Chain.Noble: { - // Cosmos-based chains - typically use transfer with memo - const anyWallet = wallet as any; - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); + return await getWallet(chain as Chain.Radix).signAndBroadcast({ manifest: tx }); + }) + .with(Chain.Ripple, async () => { + if (typeof tx !== "string") { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - throw new SwapKitError("core_swap_invalid_params", { error: "Unable to execute Cosmos transaction" }); - } - case Chain.Near: { - // Near - uses transfer - const anyWallet = wallet as any; - if (transactionData.transferParams) { - return await anyWallet.transfer(transactionData.transferParams); + return await getWallet(chain as Chain.Ripple).signAndBroadcastTransaction(JSON.parse(tx)); + }) + .with(Chain.Tron, async () => { + const transaction = TronTransactionSchema.safeParse(tx); + if (!transaction.success) { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - throw new SwapKitError("core_swap_invalid_params", { error: "Unable to execute Near transaction" }); - } - default: - throw new SwapKitError("core_swap_invalid_params", { error: `Chain ${chain} is not supported for swaps` }); - } + return await getWallet(chain as Chain.Tron).signAndBroadcastTransaction(transaction.data); + }) + .otherwise(() => { + throw new SwapKitError("plugin_swapkit_invalid_tx_data", { + error: `Chain ${chain} is not supported for swaps`, + }); + }); } catch (error) { if (error instanceof SwapKitError) throw error; throw new SwapKitError("core_swap_invalid_params", { error }); } } - /** - * Override signAndBroadcastTransaction for wallets that don't support it - */ - function overrideSignAndBroadcastForWallet(wallet: any, chain: CryptoChain) { - const walletType = wallet.walletType || ""; - - if (!walletSupportsSignAndBroadcast(walletType)) { - // Override signAndBroadcastTransaction to use transfer as fallback - wallet.signAndBroadcastTransaction = async (tx: any) => { - // Extract transfer params from the transaction - // This would need to be customized based on the transaction type - console.warn(`Wallet ${walletType} doesn't support signing, falling back to transfer`); - - // The SwapKit API should provide transferParams for these wallets - if (tx.transferParams) { - return await wallet.transfer(tx.transferParams); - } - - throw new SwapKitError("core_swap_invalid_params", { - error: `Wallet ${walletType} doesn't support transaction signing for ${chain}`, - }); - }; - } - - return wallet; - } - - return { overrideSignAndBroadcastForWallet, quote, swap, walletSupportsSignAndBroadcast }; + return { quote, swap }; }, name: "swapkit", - properties: { - supportedSwapkitProviders: [ - // Add SwapKit provider names when they become available - ] as ProviderName[], - }, + properties: { supportedSwapkitProviders: Object.values(ProviderName) }, }); diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index 1d6a56734b..64af93be92 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -59,17 +59,26 @@ function getSignTransaction({ signer }: { signer?: Signer }) { } function getSignAndBroadcastTransaction({ + chain, provider, signer, }: { + chain: EVMChain; provider: Provider | BrowserProvider; signer?: Signer; }) { - return async function signAndBroadcastTransaction(tx: TransactionRequest): Promise { + return async function signAndBroadcastTransaction({ from, to, data, value }: TransactionRequest): Promise { if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); + const tx = { data, from, to, value }; + + const gasLimit = await provider.estimateGas(tx); + const gasPrices = getEstimateGasPrices({ chain, provider })(); + + const transaction = { ...tx, ...gasPrices, gasLimit }; + // Sign the transaction - const signedTx = await signer.signTransaction(tx); + const signedTx = await signer.signTransaction(transaction); // Broadcast the signed transaction const response = await provider.broadcastTransaction(signedTx); @@ -111,7 +120,7 @@ export function BaseEVMToolbox< }, isApproved: getIsApproved({ chain, provider }), sendTransaction: getSendTransaction({ chain, isEIP1559Compatible, provider, signer }), - signAndBroadcastTransaction: getSignAndBroadcastTransaction({ provider, signer }), + signAndBroadcastTransaction: getSignAndBroadcastTransaction({ chain, provider, signer }), signMessage: signer?.signMessage, signTransaction: getSignTransaction({ signer }), transfer: getTransfer({ chain, isEIP1559Compatible, provider, signer }), diff --git a/packages/toolboxes/src/tron/toolbox.ts b/packages/toolboxes/src/tron/toolbox.ts index 9a87a8689f..ac1c6a9673 100644 --- a/packages/toolboxes/src/tron/toolbox.ts +++ b/packages/toolboxes/src/tron/toolbox.ts @@ -116,6 +116,7 @@ export const createTronToolbox = async ( transfer: (params: TronTransferParams) => Promise; estimateTransactionFee: (params: TronTransferParams & { sender?: string }) => Promise; createTransaction: (params: TronCreateTransactionParams) => Promise; + signAndBroadcastTransaction: (transaction: TronTransaction) => Promise; signTransaction: (transaction: TronTransaction) => Promise; broadcastTransaction: (signedTransaction: TronSignedTransaction) => Promise; approve: (params: TronApproveParams) => Promise; @@ -516,6 +517,11 @@ export const createTronToolbox = async ( return txid; }; + const signAndBroadcastTransaction = async (transaction: TronTransaction) => { + const signedTx = await signTransaction(transaction); + return await broadcastTransaction(signedTx); + }; + /** * Check the current allowance for a spender on a token */ @@ -600,6 +606,7 @@ export const createTronToolbox = async ( getApprovedAmount, getBalance, isApproved, + signAndBroadcastTransaction, signTransaction, transfer, tronWeb, diff --git a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts index 7e3c672f58..e800abe8d0 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,40 +10,33 @@ 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 }; } @@ -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,54 +70,30 @@ export async function createBCHToolbox( return getBalance(stripPrefix(toCashAddress(address))); } - function getSignTransaction( - signer?: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType>, - ) { - return async function signTransaction({ - builder, - utxos, - }: { - builder: TransactionBuilderType; - utxos: UTXOType[]; - }): Promise { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedTx = await signer.signTransaction({ builder, utxos }); - return signedTx; - }; + async function signTransaction(psbt: Psbt) { + if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); + const signedTx = await signer.signTransaction(psbt); + return signedTx; } - function getSignAndBroadcastTransaction({ - signer, - broadcastTx, - }: { - signer: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType> | undefined; - broadcastTx: (txHash: string) => Promise; - }) { - return async function signAndBroadcastTransaction({ - builder, - utxos, - }: { - builder: TransactionBuilderType; - utxos: UTXOType[]; - }): Promise { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedTx = await signer.signTransaction({ builder, utxos }); - const txHex = signedTx.toHex(); - return broadcastTx(txHex); - }; + 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: getSignAndBroadcastTransaction({ broadcastTx, signer }), - signTransaction: getSignTransaction(signer), + signAndBroadcastTransaction, + signTransaction, stripPrefix, stripToCashAddress, transfer: transfer({ broadcastTx, getFeeRates, signer }), @@ -134,54 +101,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, @@ -189,7 +108,7 @@ function transfer({ }: { broadcastTx: (txHash: string) => Promise; getFeeRates: () => Promise>; - signer?: ChainSigner<{ builder: TransactionBuilderType; utxos: UTXOType[] }, TransactionType>; + signer?: ChainSigner; }) { return async function transfer({ recipient, @@ -205,68 +124,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 389d7de6b3..3f9d9b124e 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 7dd7e3a390..8e75762881 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,7 @@ import { applyFeeMultiplier, Chain, type ChainSigner, - DerivationPath, + // DerivationPath, type DerivationPathArray, derivationPathToString, FeeOption, @@ -147,7 +145,7 @@ async function createSignerWithKeys({ phrase: string; derivationPath: string; }) { - const keyPair = (await getCreateKeysForPath(chain as Chain.Bitcoin))({ derivationPath, phrase }); + const keyPair = (await getCreateKeysForPath(chain))({ derivationPath, phrase }); async function signTransaction(psbt: Psbt) { await psbt.signAllInputs(keyPair); @@ -332,7 +330,7 @@ function estimateTransactionFee(chain: UTXOChain) { } type CreateKeysForPathReturnType = { - [Chain.BitcoinCash]: BchECPair; + [Chain.BitcoinCash]: ECPairInterface; [Chain.Bitcoin]: ECPairInterface; [Chain.Dash]: ECPairInterface; [Chain.Dogecoin]: ECPairInterface; @@ -346,30 +344,32 @@ export async function getCreateKeysForPath CreateKeysForPathReturnType[T]; + // } - const masterHDNode = HDNode.fromSeedBuffer(Buffer.from(mnemonicToSeedSync(phrase)), network); - const keyPair = masterHDNode.derivePath(derivationPath).keyPair; - - return keyPair as BchECPair; - } as (params: { wif?: string; phrase?: string; derivationPath?: string }) => CreateKeysForPathReturnType[T]; - } case Chain.Bitcoin: + case Chain.BitcoinCash: case Chain.Dogecoin: case Chain.Litecoin: case Chain.Zcash: From d41f04881204f10961c5782e9799851e6a54b683 Mon Sep 17 00:00:00 2001 From: towan Date: Tue, 9 Sep 2025 15:34:16 +0400 Subject: [PATCH 10/15] WIP: ctrl wallet utxo missing --- bun.lock | 4 +- packages/helpers/src/api/swapkitApi/types.ts | 31 ++- packages/helpers/src/modules/swapKitError.ts | 2 + packages/plugins/src/swapkit/plugin.ts | 8 +- .../src/cosmos/thorchainUtils/registry.ts | 7 +- .../toolboxes/src/cosmos/toolbox/cosmos.ts | 61 ++++- packages/toolboxes/src/cosmos/types.ts | 6 + packages/wallet-hardware/package.json | 12 +- .../src/keepkey/chains/utxo.ts | 247 +++++++++++++----- packages/wallet-hardware/src/trezor/index.ts | 155 ++++++----- packages/wallets/package.json | 2 - packages/wallets/src/ctrl/index.ts | 36 ++- packages/wallets/src/ctrl/walletHelpers.ts | 67 ++++- 13 files changed, 496 insertions(+), 142 deletions(-) diff --git a/bun.lock b/bun.lock index e16cf3872c..f216942fc1 100644 --- a/bun.lock +++ b/bun.lock @@ -255,6 +255,7 @@ "@ledgerhq/wallet-api-client": "~1.9.0", "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", + "@trezor/connect-web": "~9.6.0", "ethers": "^6.14.0", "ts-pattern": "^5.7.0", }, @@ -271,6 +272,7 @@ "@ledgerhq/hw-transport": "6.31.6", "@ledgerhq/hw-transport-webusb": "6.29.9", "@ledgerhq/wallet-api-client": "1.9.1", + "@trezor/connect-web": "~9.6.0", "ethers": "6.15.0", "ts-pattern": "5.8.0", }, @@ -313,7 +315,6 @@ "@swapkit/wallet-core": "workspace:*", "@swapkit/wallet-hardware": "workspace:*", "@swapkit/wallet-keystore": "workspace:*", - "@trezor/connect-web": "~9.6.0", "@walletconnect/modal": "~2.7.0", "@walletconnect/sign-client": "~2.21.0", "bitcoinjs-lib": "~6.1.0", @@ -339,7 +340,6 @@ "@solana/web3.js": "1.98.4", "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", - "@trezor/connect-web": "9.6.2", "@walletconnect/logger": "2.1.2", "@walletconnect/modal": "2.7.0", "@walletconnect/sign-client": "2.21.8", diff --git a/packages/helpers/src/api/swapkitApi/types.ts b/packages/helpers/src/api/swapkitApi/types.ts index 1c8fa49bca..13ecf95a3a 100644 --- a/packages/helpers/src/api/swapkitApi/types.ts +++ b/packages/helpers/src/api/swapkitApi/types.ts @@ -454,7 +454,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() }); diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index ec3ae500a8..aabbb729f4 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -108,6 +108,8 @@ const errorCodes = { wallet_ctrl_send_transaction_no_address: 20302, wallet_ctrl_contract_address_not_provided: 20303, wallet_ctrl_asset_not_defined: 20304, + wallet_ctrl_sign_transaction_not_supported: 20305, + wallet_ctrl_transaction_missing_data: 20306, /** * Wallets - WalletConnect */ diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts index 21db652c3d..39f22ceed8 100644 --- a/packages/plugins/src/swapkit/plugin.ts +++ b/packages/plugins/src/swapkit/plugin.ts @@ -5,7 +5,7 @@ import { VersionedTransaction } from "@solana/web3.js"; import { AssetValue, Chain, - type CosmosChain, + // type CosmosChain, CosmosChains, type CryptoChain, type EVMChain, @@ -118,13 +118,13 @@ export const SwapKitPlugin = createPlugin({ const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); return await getWallet(chain as Chain.Solana).signAndBroadcastTransaction(transaction); }) - .with(...CosmosChains, async () => { + .with(...CosmosChains, () => { const transaction = CosmosTransactionSchema.safeParse(tx); if (!transaction.success) { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - - return await getWallet(chain as CosmosChain).transfer(transaction.data); + return Promise.resolve(""); + // return await getWallet(chain as CosmosChain).transfer({transaction.data}); }) .with(Chain.Near, async () => { const transaction = NEARTransactionSchema.safeParse(tx); diff --git a/packages/toolboxes/src/cosmos/thorchainUtils/registry.ts b/packages/toolboxes/src/cosmos/thorchainUtils/registry.ts index 77ff8f5f5e..2e346ca149 100644 --- a/packages/toolboxes/src/cosmos/thorchainUtils/registry.ts +++ b/packages/toolboxes/src/cosmos/thorchainUtils/registry.ts @@ -19,12 +19,13 @@ export async function createDefaultAminoTypes(chain: Chain.THORChain | Chain.May const imported = await import("@cosmjs/stargate"); const AminoTypes = imported.AminoTypes ?? imported.default?.AminoTypes; const aminoTypePrefix = chain === Chain.THORChain ? "thorchain" : "mayachain"; + const addressPrefix = chain === Chain.THORChain ? "thor" : "maya"; return new AminoTypes({ "/types.MsgDeposit": { aminoType: `${aminoTypePrefix}/MsgDeposit`, fromAmino: ({ signer, ...rest }: any) => ({ ...rest, signer: bech32ToBase64(signer) }), - toAmino: ({ signer, ...rest }: any) => ({ ...rest, signer: base64ToBech32(signer) }), + toAmino: ({ signer, ...rest }: any) => ({ ...rest, signer: base64ToBech32(signer, addressPrefix) }), }, "/types.MsgSend": { aminoType: `${aminoTypePrefix}/MsgSend`, @@ -35,8 +36,8 @@ export async function createDefaultAminoTypes(chain: Chain.THORChain | Chain.May }), toAmino: ({ fromAddress, toAddress, ...rest }: any) => ({ ...rest, - from_address: base64ToBech32(fromAddress), - to_address: base64ToBech32(toAddress), + from_address: base64ToBech32(fromAddress, addressPrefix), + to_address: base64ToBech32(toAddress, addressPrefix), }), }, }); diff --git a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts index 2d68d780f8..0b68c97b5a 100644 --- a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts +++ b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts @@ -22,9 +22,9 @@ import { SwapKitNumber, updateDerivationPath, } from "@swapkit/helpers"; -import { SwapKitApi } from "@swapkit/helpers/api"; +import { type CosmosTransaction, SwapKitApi } from "@swapkit/helpers/api"; import { match, P } from "ts-pattern"; -import type { CosmosToolboxParams } from "../types"; +import type { CosmosSignedTransaction, CosmosToolboxParams } from "../types"; import { cosmosCreateTransaction, createSigningStargateClient, @@ -136,6 +136,61 @@ 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); + + // Sign the transaction without broadcasting + const txRaw = await signingClient.sign(from, transaction.msgs, transaction.fee, transaction.memo, { + accountNumber: transaction.accountNumber, + chainId: transaction.chainId, + sequence: transaction.sequence, + }); + + // Import TxRaw for encoding + const { TxRaw } = await import("cosmjs-types/cosmos/tx/v1beta1/tx"); + const txBytes = TxRaw.encode(txRaw).finish(); + + // Calculate transaction hash + const importedCrypto = await import("@cosmjs/crypto"); + const sha256 = importedCrypto.sha256 ?? importedCrypto.default?.sha256; + const importedEncoding = await import("@cosmjs/encoding"); + const toHex = importedEncoding.toHex ?? importedEncoding.default?.toHex; + const txHash = toHex(sha256(txBytes)).toUpperCase(); + + return { txBytes, txHash }; + } + + async function signAndBroadcastTransaction(transaction: CosmosTransaction): Promise { + const from = await getAddress(); + + if (!(signer && from)) { + throw new SwapKitError("toolbox_cosmos_signer_not_defined"); + } + + const signingClient = await createSigningStargateClient(rpcUrl, signer); + + // Use signAndBroadcast for atomic operation + const result = await signingClient.signAndBroadcast( + from, + transaction.msgs, + transaction.fee, + transaction.memo, + undefined, // timeoutHeight + ); + + 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, @@ -200,6 +255,8 @@ export async function createCosmosToolbox({ chain, ...toolboxParams }: CosmosToo importedSigning.DirectSecp256k1Wallet ?? importedSigning.default?.DirectSecp256k1Wallet; return DirectSecp256k1Wallet.fromKey(privateKey, chainPrefix); }, + signAndBroadcastTransaction, + signTransaction, transfer, validateAddress: getCosmosValidateAddress(chainPrefix), verifySignature: verifySignature(getAccount), diff --git a/packages/toolboxes/src/cosmos/types.ts b/packages/toolboxes/src/cosmos/types.ts index 538faf5dca..e84fd4cbbf 100644 --- a/packages/toolboxes/src/cosmos/types.ts +++ b/packages/toolboxes/src/cosmos/types.ts @@ -31,6 +31,12 @@ export type MultisigTx = { export type CosmosSigner = DirectSecp256k1HdWallet | OfflineDirectSigner | OfflineAminoSigner; +export type CosmosSignedTransaction = { + txBytes: Uint8Array; + txHash: string; + bodyBytes?: Uint8Array; // For THORChain multisig support +}; + export type CosmosToolboxParams = { chain: T } & ( | { signer?: CosmosSigner } | { phrase?: string; derivationPath?: DerivationPathArray; index?: number } diff --git a/packages/wallet-hardware/package.json b/packages/wallet-hardware/package.json index c94ce2f1e9..51068842ef 100644 --- a/packages/wallet-hardware/package.json +++ b/packages/wallet-hardware/package.json @@ -15,6 +15,7 @@ "@ledgerhq/wallet-api-client": "~1.9.0", "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", + "@trezor/connect-web": "~9.6.0", "ethers": "^6.14.0", "ts-pattern": "^5.7.0" }, @@ -32,11 +33,16 @@ "@ledgerhq/hw-transport": "6.31.6", "@ledgerhq/hw-transport-webusb": "6.29.9", "@ledgerhq/wallet-api-client": "1.9.1", + "@trezor/connect-web": "~9.6.0", "ethers": "6.15.0", "ts-pattern": "5.8.0" }, "exports": { - ".": { "import": "./dist/index.js", "require": "./dist/index.cjs", "types": "./dist/types/index.d.ts" }, + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/types/index.d.ts" + }, "./keepkey": { "import": "./dist/keepkey/index.js", "require": "./dist/keepkey/index.cjs", @@ -53,7 +59,9 @@ "types": "./dist/types/trezor/index.d.ts" } }, - "files": ["dist/"], + "files": [ + "dist/" + ], "homepage": "https://github.com/swapkit/SwapKit", "license": "Apache-2.0", "name": "@swapkit/wallet-hardware", diff --git a/packages/wallet-hardware/src/keepkey/chains/utxo.ts b/packages/wallet-hardware/src/keepkey/chains/utxo.ts index 7f591f045e..bdb29a9b52 100644 --- a/packages/wallet-hardware/src/keepkey/chains/utxo.ts +++ b/packages/wallet-hardware/src/keepkey/chains/utxo.ts @@ -1,27 +1,18 @@ +import type { UtxoPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; import type { KeepKeySdk } from "@keepkey/keepkey-sdk"; import { Chain, DerivationPath, type DerivationPathArray, derivationPathToString, - FeeOption, - type GenericTransferParams, + SKConfig, SwapKitError, type UTXOChain, } from "@swapkit/helpers"; -import type { UTXOToolboxes } from "@swapkit/toolboxes/utxo"; -import type { Psbt } from "bitcoinjs-lib"; +import { stripToCashAddress } from "@swapkit/toolboxes/utxo"; +import { type Psbt, script, Transaction } from "bitcoinjs-lib"; import { bip32ToAddressNList, ChainToKeepKeyName } from "../coins"; -interface KeepKeyInputObject { - addressNList: number[]; - scriptType: string; - amount: string; - vout: number; - txid: string; - hex: string; -} - export const utxoWalletMethods = async ({ sdk, chain, @@ -33,7 +24,6 @@ export const utxoWalletMethods = async ({ }) => { const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo"); // This might not work for BCH - const toolbox = await getUtxoToolbox(chain); const scriptType = [Chain.Bitcoin, Chain.Litecoin].includes(chain) ? ("p2wpkh" as const) : ("p2pkh" as const); const derivationPathString = derivationPath ? derivationPathToString(derivationPath) : `${DerivationPath[chain]}/0`; @@ -46,7 +36,57 @@ export const utxoWalletMethods = async ({ const walletAddress: string = (await sdk.address.utxoGetAddress(addressInfo)).address; - const signTransaction = async (psbt: Psbt, inputs: KeepKeyInputObject[], memo = "") => { + function psbtToKeepKeyParams(psbt: Psbt | UtxoPsbt) { + // 1. Map Inputs (logic remains the same) + const inputs = psbt.data.inputs.map((input, index) => { + const txInput = psbt.txInputs[index]; + let utxoValue: number; + let utxoHex: string; + + // ---- THIS IS THE CRITICAL LOGIC ---- + if (input.witnessUtxo) { + // Use this path for SegWit inputs (BTC, LTC) + utxoValue = Number(input.witnessUtxo.value); + + // KeepKey's `hex` field requires the full transaction. If the PSBT creator + // only provided a witnessUtxo, we may need to fetch the full transaction hex here. + // A well-formed PSBT for KeepKey should probably include it anyway. + if (input.nonWitnessUtxo) { + utxoHex = input.nonWitnessUtxo.toString("hex"); + } else { + // You would need a helper here to fetch the raw transaction from a block explorer + utxoHex = ""; + } + } else if (input.nonWitnessUtxo) { + // Use this path for Non-SegWit inputs (BCH, DOGE, legacy BTC/LTC) + const prevTx = Transaction.fromBuffer(input.nonWitnessUtxo); + utxoValue = prevTx.outs[txInput?.index || 0]?.value || 0; + utxoHex = input.nonWitnessUtxo.toString("hex"); + } else { + // This is an invalid PSBT for signing; it's missing the necessary UTXO info. + throw new Error(`PSBT input ${index} is missing both witnessUtxo and nonWitnessUtxo.`); + } + // ------------------------------------ + + const bip32Derivation = input.bip32Derivation?.[0]; + if (!bip32Derivation) { + throw new Error(`PSBT input ${index} is missing derivation path required by KeepKey.`); + } + + // ... logic to determine scriptType from the UTXO's script ... + + return { + addressNList: addressInfo.address_n, + amount: utxoValue.toString(), + hex: utxoHex, + scriptType, + txid: txInput?.hash.toString("hex"), + vout: txInput?.index, + }; + }); + + // 2. MAP OUTPUTS & SEPARATE THE MEMO (OP_RETURN) + let opReturnData = ""; const outputs = psbt.txOutputs .map((output) => { const { value, address, change } = output as { @@ -56,21 +96,23 @@ export const utxoWalletMethods = async ({ change?: boolean; }; - const outputAddress = - // @ts-expect-error - stripToCashAddress is not defined in the UTXO toolbox just only on BCH - chain === Chain.BitcoinCash ? toolbox.stripToCashAddress(address) : address; + const outputAddress = chain === Chain.BitcoinCash ? stripToCashAddress(address) : address; + + if (output.script && output.script[0] === 0x6a) { + // An OP_RETURN script always starts with the byte 0x6a. + // ---- THIS IS THE CORRECTED LOGIC ---- + // KeepKey expects a clear string, so we decode the buffer as UTF-8. + opReturnData = output.script.slice(1).toString("utf8"); + // ------------------------------------ + return null; // Exclude OP_RETURN from outputs + } if (change || address === walletAddress) { - return { - addressNList: addressInfo.address_n, - addressType: "change", - amount: value, - isChange: true, - scriptType, - }; + return { address: output.address, addressType: "change", amount: value, isChange: true, scriptType }; } if (outputAddress) { + // This is a RECIPIENT output return { address: outputAddress, addressType: "spend", amount: value }; } @@ -78,6 +120,48 @@ export const utxoWalletMethods = async ({ }) .filter(Boolean); + // 3. Return the final object for KeepKey + return { + inputs, + opReturnData, // This is now a clear string, e.g., "for my friend" + outputs, + }; + } + + const signTransaction = async (psbt: T) => { + const { inputs, opReturnData: memo, outputs } = psbtToKeepKeyParams(psbt); + + // const outputs = psbt.txOutputs + // .map((output) => { + // const { value, address, change } = output as { + // address: string; + // script: Buffer; + // value: number; + // change?: boolean; + // }; + + // const outputAddress = + // // @ts-expect-error - stripToCashAddress is not defined in the UTXO toolbox just only on BCH + // chain === Chain.BitcoinCash ? toolbox.stripToCashAddress(address) : address; + + // if (change || address === walletAddress) { + // return { + // addressNList: addressInfo.address_n, + // addressType: "change", + // amount: value, + // isChange: true, + // scriptType, + // }; + // } + + // if (outputAddress) { + // return { address: outputAddress, addressType: "spend", amount: value }; + // } + + // return null; + // }) + // .filter(Boolean); + const removeNullAndEmptyObjectsFromArray = (arr: any[]) => { return arr.filter((item) => item !== null && typeof item === "object" && Object.keys(item).length > 0); }; @@ -89,42 +173,87 @@ export const utxoWalletMethods = async ({ outputs: removeNullAndEmptyObjectsFromArray(outputs), }); - return responseSign.serializedTx?.toString(); - }; + if (responseSign.serializedTx) { + // 3. PARSE the finalized transaction hex back into a transaction object + const finalTx = Transaction.fromHex(responseSign.serializedTx.toString()); - const transfer = async ({ recipient, feeOptionKey, feeRate, memo, ...rest }: GenericTransferParams) => { - if (!walletAddress) - throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "From address must be provided" }); - 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 { psbt, inputs: rawInputs } = await createTxMethod({ - ...rest, - feeRate: feeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast], - fetchTxHex: true, - memo, - recipient, - sender: walletAddress, - }); + // 4. EXTRACT the signatures and UPDATE the original PSBT + psbt.data.inputs.forEach((input, index) => { + const finalTxInput = finalTx.ins[index]; + + if (!finalTxInput || !finalTxInput.script) { + throw new SwapKitError("wallet_keepkey_signing_error", { + error: `Could not find a valid signature script in the final transaction for input ${index}`, + }); + } + + if (!input.bip32Derivation || !input.bip32Derivation[0]) { + return; // Cannot add signature without pubkey info from original PSBT + } + + // The finalTxInput.script contains the fully assembled scriptSig. + // We can pass this directly to the psbt's finalizer methods, + // or for consistency, add it to the partialSig map. + // NOTE: For a simple P2PKH, the script is [signature, pubkey]. + // The `psbt.updateInput` is the cleanest way to add this back. + // We are essentially taking the "answer" from the final tx and putting it + // into our PSBT's "worksheet". + + const pubkey = input.bip32Derivation[0].pubkey; + + // Decompile the script to extract just the signature + // Note: This can be complex. For P2PKH, the first push is the signature. + const chunks = script.decompile(finalTxInput.script); + if (!chunks || chunks.length < 2) { + throw new SwapKitError("wallet_keepkey_signing_error", { + error: `Unexpected script format in final transaction for input ${index}`, + }); + } + const signature = chunks[0] as Buffer; - const inputs = rawInputs.map(({ value, index, hash, txHex }) => ({ - //@TODO don't hardcode master, lookup on blockbook what input this is for and what path that address is! - addressNList: addressInfo.address_n, - amount: value.toString(), - hex: txHex || "", - scriptType, - txid: hash, - vout: index, - })); - - const txHex = await signTransaction(psbt, inputs, memo); - return toolbox.broadcastTx(txHex); + psbt.updateInput(index, { partialSig: [{ pubkey, signature }] }); + }); + + // 5. Return the updated PSBT, which now contains KeepKey's signature + return psbt; + } + throw new SwapKitError("wallet_keepkey_signing_error", { + error: "KeepKey did not return a serialized transaction", + }); }; - return { ...toolbox, address: walletAddress, signTransaction, transfer }; + const signer = { getAddress: async () => walletAddress, signTransaction: signTransaction }; + + const toolbox = await getUtxoToolbox>(chain, { signer }); + + // const signAndBroadcastTransaction = async (psbt: Psbt) => { + // const txHex = await signTransaction(psbt as Psbt); + // return toolbox.broadcastTx(txHex); + // }; + + // const transfer = async ({ recipient, feeOptionKey, feeRate, memo, ...rest }: GenericTransferParams) => { + // if (!walletAddress) + // throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "From address must be provided" }); + // if (!recipient) + // throw new SwapKitError("wallet_keepkey_invalid_params", { reason: "Recipient address must be provided" }); + + // const createTxMethod = + // chain === Chain.BitcoinCash + // ? (toolbox as UTXOToolboxes["BCH"]).createTransaction + // : (toolbox as UTXOToolboxes["BTC"]).createTransaction; + + // const { psbt } = await createTxMethod({ + // ...rest, + // feeRate: feeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast], + // fetchTxHex: true, + // memo, + // recipient, + // sender: walletAddress, + // }); + + // const txHex = await signTransaction(psbt as Psbt); + // return toolbox.broadcastTx(txHex); + // }; + + return { ...toolbox, address: walletAddress }; }; diff --git a/packages/wallet-hardware/src/trezor/index.ts b/packages/wallet-hardware/src/trezor/index.ts index 3854e5e16e..c519e1bb9c 100644 --- a/packages/wallet-hardware/src/trezor/index.ts +++ b/packages/wallet-hardware/src/trezor/index.ts @@ -1,4 +1,4 @@ -import type { ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; +import type { UtxoPsbt, ZcashPsbt } from "@bitgo/utxo-lib/dist/src/bitgo"; import { Chain, type DerivationPathArray, @@ -8,11 +8,12 @@ import { type GenericTransferParams, SKConfig, SwapKitError, + type UTXOChain, WalletOption, } from "@swapkit/helpers"; -import type { UTXOToolboxes, UTXOType } from "@swapkit/toolboxes/utxo"; +import { stripPrefix } from "@swapkit/toolboxes/utxo"; import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core"; -import type { Psbt } from "bitcoinjs-lib"; +import { type Psbt, Transaction } from "bitcoinjs-lib"; function getScriptType(derivationPath: DerivationPathArray) { switch (derivationPath[0]) { @@ -200,92 +201,118 @@ async function getTrezorWallet({ const address = await getAddress(); - const signTransaction = async (psbt: Psbt, inputs: UTXOType[], memo = "") => { - const TrezorConnect = (await import("@trezor/connect-web")).default; + function psbtToTrezorParams(psbt: Psbt | UtxoPsbt) { + if (!scriptType) { + throw new SwapKitError({ errorKey: "wallet_trezor_derivation_path_not_supported", info: { derivationPath } }); + } const address_n = derivationPath.map((pathElement, index) => index < 3 ? ((pathElement as number) | 0x80000000) >>> 0 : (pathElement as number), ); - const toolbox = await getUtxoToolbox(chain as Chain.BitcoinCash); + // 1. Map Inputs (logic remains the same) + const inputs = psbt.data.inputs.map((input, index) => { + const txInput = psbt.txInputs[index]; + let utxoValue: number; + + // ---- THIS IS THE CRITICAL LOGIC ---- + if (input.witnessUtxo) { + // Use this path for SegWit inputs (BTC, LTC) + utxoValue = Number(input.witnessUtxo.value); + } else if (input.nonWitnessUtxo) { + const prevTx = Transaction.fromBuffer(input.nonWitnessUtxo); + utxoValue = prevTx.outs[txInput?.index || 0]?.value || 0; + } else { + // This is an invalid PSBT for signing; it's missing the necessary UTXO info. + throw new Error(`PSBT input ${index} is missing both witnessUtxo and nonWitnessUtxo.`); + } + // ------------------------------------ - const result = await TrezorConnect.signTransaction({ - coin, - inputs: inputs.map(({ hash, index, value }) => ({ - // Hardens the first 3 elements of the derivation path - required by trezor - address_n, - // object needs amount but does not use it for signing - amount: value, - prev_hash: hash, - prev_index: index, - script_type: scriptType.input, - })), - outputs: psbt.txOutputs.map((output) => { + const bip32Derivation = input.bip32Derivation?.[0]; + if (!bip32Derivation) { + throw new Error(`PSBT input ${index} is missing derivation path required by KeepKey.`); + } + + // ... logic to determine scriptType from the UTXO's script ... + const txid = Buffer.from(psbt.txInputs[index]?.hash || "") + .reverse() + .toString("hex"); + + return { address_n, amount: utxoValue, prev_hash: txid, prev_index: index, script_type: scriptType.input }; + }); + + // 2. MAP OUTPUTS & SEPARATE THE MEMO (OP_RETURN) + const memo = psbt.txOutputs + .find((output) => output.script && output.script[0] === 0x6a) + ?.script.slice(1) + .toString("utf8"); + + const outputs = psbt.txOutputs + .map((output) => { // OP_RETURN - if (!output.address) { - return { amount: "0", op_return_data: Buffer.from(memo).toString("hex"), script_type: "PAYTOOPRETURN" }; + if (!output.address && memo) { + return { + amount: "0", + op_return_data: Buffer.from(memo).toString("hex"), + script_type: "PAYTOOPRETURN" as const, + }; } + if (!output.address) return null; + const outputAddress = - chain === Chain.BitcoinCash ? toolbox.stripPrefix(toCashAddress(output.address)) : output.address; + chain === Chain.BitcoinCash ? stripPrefix(toCashAddress(output.address)) : output.address; const isChangeAddress = outputAddress === address; return isChangeAddress ? { address_n, amount: output.value, script_type: scriptType.output } - : { address: outputAddress, amount: output.value, script_type: "PAYTOADDRESS" }; - }), - }); + : { address: outputAddress, amount: output.value, script_type: "PAYTOADDRESS" as const }; + }) + .filter((output) => output !== null); - if (result.success) { - return result.payload.serializedTx; - } + // 3. Return the final object for KeepKey + return { inputs, outputs }; + } - throw new SwapKitError({ - errorKey: "wallet_trezor_failed_to_sign_transaction", - info: { chain, error: (result.payload as { error: string; code?: string }).error }, - }); - }; + const signTransaction = async (psbt: T) => { + const TrezorConnect = (await import("@trezor/connect-web")).default; - const transfer = async ({ - recipient, - feeOptionKey, - feeRate: paramFeeRate, - memo, - ...rest - }: GenericTransferParams) => { - if (!(address && recipient)) { - throw new SwapKitError({ - errorKey: "wallet_missing_params", - info: { address, memo, recipient, wallet: WalletOption.TREZOR }, - }); - } + const { inputs, outputs } = psbtToTrezorParams(psbt); - const toolbox = await getUtxoToolbox(chain); + // @ts-expect-error + const result = await TrezorConnect.signTransaction({ coin, inputs, outputs }); - const feeRate = paramFeeRate || (await toolbox.getFeeRates())[feeOptionKey || FeeOption.Fast]; + if (result.success) { + const signatures = result.payload.signatures; - const createTxMethod = - chain === Chain.BitcoinCash - ? (toolbox as UTXOToolboxes["BCH"]).buildTx - : (toolbox as UTXOToolboxes["BTC"]).createTransaction; + psbt.data.inputs.forEach((input, index) => { + if (!input.bip32Derivation || !input.bip32Derivation[0]) { + return; + } - const { psbt, inputs } = await createTxMethod({ - ...rest, - feeRate, - fetchTxHex: true, - memo, - recipient, - sender: address, - }); + const signatureHex = signatures[index]; + if (!signatureHex) { + throw new Error(`Trezor did not return a signature for input ${index}`); + } - const txHex = await signTransaction(psbt, inputs, memo); - const tx = await toolbox.broadcastTx(txHex); + const pubkey = input.bip32Derivation[0].pubkey; - return tx; + psbt.updateInput(index, { partialSig: [{ pubkey, signature: Buffer.from(signatureHex, "hex") }] }); + }); + + return psbt; + } + + // Handle the error case + throw new SwapKitError({ + errorKey: "wallet_trezor_failed_to_sign_transaction", + info: { chain, error: (result.payload as { error: string; code?: string }).error }, + }); }; - const toolbox = await getUtxoToolbox(chain); + const signer = { getAddress: async () => address, signTransaction: signTransaction }; + const toolbox = await getUtxoToolbox>(chain, { signer }); - return { ...toolbox, address, signTransaction, transfer }; + return { ...toolbox, address }; } default: diff --git a/packages/wallets/package.json b/packages/wallets/package.json index 3696652e52..0a90a2210a 100644 --- a/packages/wallets/package.json +++ b/packages/wallets/package.json @@ -17,7 +17,6 @@ "@swapkit/wallet-core": "workspace:*", "@swapkit/wallet-hardware": "workspace:*", "@swapkit/wallet-keystore": "workspace:*", - "@trezor/connect-web": "~9.6.0", "@walletconnect/modal": "~2.7.0", "@walletconnect/sign-client": "~2.21.0", "bitcoinjs-lib": "~6.1.0", @@ -44,7 +43,6 @@ "@solana/web3.js": "1.98.4", "@swapkit/helpers": "workspace:*", "@swapkit/toolboxes": "workspace:*", - "@trezor/connect-web": "9.6.2", "@walletconnect/logger": "2.1.2", "@walletconnect/modal": "2.7.0", "@walletconnect/sign-client": "2.21.8", diff --git a/packages/wallets/src/ctrl/index.ts b/packages/wallets/src/ctrl/index.ts index 28d8446e8c..ecbbc08ea9 100644 --- a/packages/wallets/src/ctrl/index.ts +++ b/packages/wallets/src/ctrl/index.ts @@ -6,10 +6,10 @@ import { SwapKitError, WalletOption, } from "@swapkit/helpers"; +import type { CosmosTransaction } from "@swapkit/helpers/api"; import type { NearCreateTransactionParams } from "@swapkit/toolboxes/near"; import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core"; - -import { getCtrlAddress, getCtrlProvider, walletTransfer } from "./walletHelpers"; +import { getCtrlAddress, getCtrlProvider, thorchainTransactionToCtrlParams, walletTransfer } from "./walletHelpers"; export const ctrlWallet = createWallet({ connect: ({ addChain, walletType, supportedChains }) => @@ -78,9 +78,41 @@ async function getWalletMethods(chain: (typeof CTRL_SUPPORTED_CHAINS)[number]) { const gasLimit = chain === Chain.Maya ? MAYA_GAS_VALUE : THORCHAIN_GAS_VALUE; const toolbox = await getCosmosToolbox(chain); + /** + * Convert CosmosTransaction to CTRL's proprietary format and broadcast + */ + const signAndBroadcastTransaction = async (transaction: CosmosTransaction): Promise => { + const provider = await getCtrlProvider(chain); + + if (!provider || !("request" in provider)) { + throw new SwapKitError("wallet_ctrl_not_found"); + } + + // Convert CosmosTransaction to CTRL's format + const { type, ...tx } = thorchainTransactionToCtrlParams({ chain, transaction }); + + return walletTransfer(tx, type); + }; + + /** + * Sign transaction without broadcasting + * Note: CTRL doesn't expose a signing-only API for THORChain/Maya + * This would require the transaction to be broadcast + */ + const signTransaction = (_transaction: any) => { + throw new SwapKitError("wallet_ctrl_sign_transaction_not_supported", { + chain, + info: "CTRL wallet does not support signing without broadcasting for THORChain/Maya", + method: "signTransaction", + wallet: WalletOption.CTRL, + }); + }; + return { ...toolbox, deposit: (tx: GenericTransferParams) => walletTransfer({ ...tx, recipient: "" }, "deposit"), + signAndBroadcastTransaction, + signTransaction, transfer: (tx: GenericTransferParams) => walletTransfer({ ...tx, gasLimit }, "transfer"), }; } diff --git a/packages/wallets/src/ctrl/walletHelpers.ts b/packages/wallets/src/ctrl/walletHelpers.ts index 1cbf415c83..ab8a0ab21f 100644 --- a/packages/wallets/src/ctrl/walletHelpers.ts +++ b/packages/wallets/src/ctrl/walletHelpers.ts @@ -1,6 +1,6 @@ import type { Keplr } from "@keplr-wallet/types"; import { - type AssetValue, + AssetValue, Chain, ChainToChainId, type EVMChain, @@ -10,6 +10,13 @@ import { SwapKitError, WalletOption, } from "@swapkit/helpers"; +import type { + APICosmosEncodedObject, + CosmosSendMsg, + CosmosTransaction, + ThorchainDepositMsg, +} from "@swapkit/helpers/api"; +import { base64ToBech32, MAYA_GAS_VALUE, THORCHAIN_GAS_VALUE } from "@swapkit/toolboxes/cosmos"; import type { SolanaProvider } from "@swapkit/toolboxes/solana"; import type { Eip1193Provider } from "ethers"; @@ -188,3 +195,61 @@ export async function walletTransfer( return transaction({ chain: assetValue.chain, method, params }); } + +/** + * Convert a CosmosTransaction to CTRL's format for THORChain/Maya + */ +export function thorchainTransactionToCtrlParams({ + transaction, + chain, +}: { + transaction: CosmosTransaction; + chain: Chain.THORChain | Chain.Maya; +}) { + // Extract the first message (CTRL only handles single transfers) + const msg = transaction.msgs[0] as APICosmosEncodedObject; + + if (!msg) { + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "msgs" }); + } + + if (msg.typeUrl === "/types.MsgSend") { + const typedMessage = msg as any as CosmosSendMsg; + if (!typedMessage.value.toAddress) { + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "toAddress" }); + } + + const recipient = base64ToBech32(typedMessage.value.toAddress, chain.toLowerCase()); + const amount = typedMessage.value.amount[0]; + if (!amount) { + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "amount" }); + } + const denom = amount.denom; + const identifier = `${chain}.${denom.toUpperCase()}`; + const assetValue = AssetValue.from({ amount: BigInt(amount.amount), asset: identifier }); + const gasLimit = chain === Chain.Maya ? MAYA_GAS_VALUE : THORCHAIN_GAS_VALUE; + + return { + assetValue, + gasLimit, + ...(transaction.memo && { memo: transaction.memo }), + recipient, + type: "transfer" as const, + }; + } + + const typedMessage = msg as any as ThorchainDepositMsg; + + const coin = typedMessage.value.coins[0]; + if (!coin) { + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "coins" }); + } + + const asset = coin.asset; + + const identifier = `${asset.chain}.${asset.symbol.toUpperCase()}`; + + const assetValue = AssetValue.from({ amount: BigInt(coin.amount), asset: identifier }); + + return { assetValue, ...(transaction.memo && { memo: transaction.memo }), recipient: "", type: "deposit" as const }; +} From fb6cf9d296d68c7271cecbb14534e38e1912ad0c Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 10 Sep 2025 13:15:18 +0400 Subject: [PATCH 11/15] WIP: remove md files --- CTRL_WALLET_IMPLEMENTATION_PLAN.md | 393 ----------------------------- SIGNING_IMPLEMENTATION_PLAN.md | 305 ---------------------- SOLUTION_SUMMARY.md | 136 ---------- TOOLBOX_SIGNING_METHODS_SUMMARY.md | 104 -------- WALLET_SIGNER_ANALYSIS.md | 168 ------------ 5 files changed, 1106 deletions(-) delete mode 100644 CTRL_WALLET_IMPLEMENTATION_PLAN.md delete mode 100644 SIGNING_IMPLEMENTATION_PLAN.md delete mode 100644 SOLUTION_SUMMARY.md delete mode 100644 TOOLBOX_SIGNING_METHODS_SUMMARY.md delete mode 100644 WALLET_SIGNER_ANALYSIS.md diff --git a/CTRL_WALLET_IMPLEMENTATION_PLAN.md b/CTRL_WALLET_IMPLEMENTATION_PLAN.md deleted file mode 100644 index ed5141f192..0000000000 --- a/CTRL_WALLET_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,393 +0,0 @@ -# CTRL Wallet Signing Implementation Plan - -## Executive Summary - -The CTRL wallet presents unique challenges for implementing standardized `sign` and `signAndBroadcast` methods. Unlike traditional wallets that expose signing capabilities, CTRL uses a high-level transaction API that abstracts signing details. This plan outlines strategies to work within CTRL's constraints while providing a consistent interface. - -## Current CTRL Implementation Analysis - -### Chain Support Overview - -| Chain Type | Current Implementation | Signing Method | -|------------|----------------------|----------------| -| **UTXO** (BTC, LTC, DOGE, BCH) | `walletTransfer` API | No direct signing exposed | -| **EVM** (ETH, BSC, AVAX, etc.) | Standard signer via provider | `signer.signTransaction` available | -| **Solana** | Standard toolbox with provider | `signTransaction` available | -| **Cosmos** (ATOM, KUJI, NOBLE) | Offline signer | Standard signing available | -| **THORChain/Maya** | Custom `walletTransfer` | No direct signing | -| **Near** | Custom transaction API | `signAndSendTransaction` only | - -### Key Findings - -1. **UTXO Chains Limitation**: CTRL does not expose PSBT signing for UTXO chains. It only provides: - - `walletTransfer`: High-level transfer method - - `deposit`: Special method for THORChain/Maya deposits - - No access to raw signing capabilities - -2. **EVM Chains**: Full signing support through ethers.js signer - - Can implement standard `signTransaction` - - Can implement `signAndBroadcastTransaction` - -3. **Solana**: Full signing support through provider - - Already has `signTransaction` - - Can add unified `signAndBroadcastTransaction` - -4. **Cosmos Chains**: Standard signing through offline signer - - Can implement standard signing methods - -## Implementation Strategy - -### Approach 1: Hybrid Implementation (Recommended) - -Create a wrapper that provides the best available functionality for each chain: - -```typescript -interface CtrlSigningCapabilities { - // Unified interface - same method names across all chains - - // UTXO - Will throw error directing to use transfer method - sign?: (transaction: Psbt) => Promise; - signAndBroadcast?: (transaction: Psbt) => Promise; - - // EVM - Full signing support - sign?: (tx: EVMTxParams) => Promise; - signAndBroadcast?: (tx: EVMTxParams) => Promise; - - // Solana - Full signing support - sign?: (tx: Transaction | VersionedTransaction) => Promise; - signAndBroadcast?: (tx: Transaction | VersionedTransaction) => Promise; - - // CTRL-specific transfer fallback for UTXO - transfer?: (params: UTXOTransferParams) => Promise; -} -``` - -### Approach 2: Request CTRL API Enhancement - -Work with CTRL team to expose: -1. PSBT signing endpoint for UTXO chains -2. Raw transaction signing for Near -3. Separate sign and broadcast methods - -### Approach 3: Transaction Builder Pattern - -For chains without signing access, implement a transaction builder that: -1. Builds unsigned transactions -2. Returns transaction objects for CTRL to process -3. Uses CTRL's transfer API as the execution mechanism - -## Detailed Implementation Plan - -### Phase 1: Document Current Capabilities - -Create clear documentation of what's possible with CTRL: - -```typescript -// packages/wallets/src/ctrl/capabilities.ts -export const CTRL_CAPABILITIES = { - bitcoin: { - sign: false, - signAndBroadcast: false, - transfer: true, - buildTransaction: true, - }, - ethereum: { - sign: true, - signAndBroadcast: true, - transfer: true, - buildTransaction: true, - }, - solana: { - sign: true, - signAndBroadcast: true, - transfer: true, - buildTransaction: true, - }, - // ... other chains -}; -``` - -### Phase 2: Implement Available Signing Methods - -#### 2.1 EVM Implementation -```typescript -// packages/wallets/src/ctrl/evm-signing.ts -export async function getEVMSigningMethods(provider: BrowserProvider, chain: EVMChain) { - const signer = await provider.getSigner(); - - return { - sign: async (tx: EVMTxParams): Promise => { - // Remove send, just sign - const signedTx = await signer.signTransaction(tx); - return signedTx; - }, - - signAndBroadcast: async (tx: EVMTxParams): Promise => { - const response = await signer.sendTransaction(tx); - return response.hash; - }, - }; -} -``` - -#### 2.2 Solana Implementation -```typescript -// packages/wallets/src/ctrl/solana-signing.ts -export function getSolanaSigningMethods(provider: SolanaProvider) { - return { - sign: async (transaction: Transaction | VersionedTransaction) => { - return provider.signTransaction(transaction); - }, - - signAndBroadcast: async (transaction: Transaction | VersionedTransaction) => { - const signed = await provider.signTransaction(transaction); - const connection = await getConnection(); - const signature = await connection.sendRawTransaction(signed.serialize()); - return signature; - }, - }; -} -``` - -#### 2.3 UTXO Workaround Implementation -```typescript -// packages/wallets/src/ctrl/utxo-builder.ts -export function getUTXOBuilderMethods(chain: UTXOChain) { - return { - // Can build but not sign PSBTs - buildPSBT: async (params: UTXOBuildTxParams): Promise => { - const { createTransaction } = await getUtxoToolbox(chain); - const { psbt } = await createTransaction(params); - return psbt; - }, - - // Use CTRL's transfer API directly - transfer: async (params: UTXOTransferParams): Promise => { - return walletTransfer(params); - }, - - // Throw informative error for signing attempts - sign: async (): Promise => { - throw new SwapKitError("wallet_ctrl_psbt_signing_not_supported", { - solution: "Use transfer method instead or contact CTRL support for PSBT signing", - chain, - }); - }, - - // Throw informative error for sign and broadcast attempts - signAndBroadcast: async (): Promise => { - throw new SwapKitError("wallet_ctrl_psbt_signing_not_supported", { - solution: "Use transfer method instead or contact CTRL support for PSBT signing", - chain, - }); - }, - }; -} -``` - -### Phase 3: Update Wallet Integration - -Modify `/packages/wallets/src/ctrl/index.ts`: - -```typescript -async function getWalletMethods(chain: Chain) { - switch (chain) { - case Chain.Bitcoin: - case Chain.BitcoinCash: - case Chain.Dogecoin: - case Chain.Litecoin: { - const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo"); - const { getUTXOBuilderMethods } = await import("./utxo-builder"); - - const toolbox = await getUtxoToolbox(chain); - const builderMethods = getUTXOBuilderMethods(chain); - - return { - ...toolbox, - ...builderMethods, - transfer: walletTransfer, - // Override signing to show clear error - sign: builderMethods.sign, - signAndBroadcast: builderMethods.signAndBroadcast, - }; - } - - // EVM chains - full signing support - case Chain.Ethereum: - case Chain.BinanceSmartChain: - // ... other EVM chains - { - const provider = new BrowserProvider(ethereumWindowProvider, "any"); - const signer = await provider.getSigner(); - const toolbox = await getEvmToolbox(chain, { provider, signer }); - const signingMethods = await getEVMSigningMethods(provider, chain); - - return { - ...toolbox, - ...ctrlMethods, - ...signingMethods, - }; - } - - // Solana - full signing support - case Chain.Solana: { - const solanaProvider = window.xfi?.solana; - const toolbox = getSolanaToolbox({ signer: solanaProvider }); - const signingMethods = getSolanaSigningMethods(solanaProvider); - - return { - ...toolbox, - ...signingMethods, - }; - } - } -} -``` - -### Phase 4: Error Handling & User Guidance - -Create clear error messages when unsupported operations are attempted: - -```typescript -export class CTRLWalletError extends SwapKitError { - constructor(operation: string, chain: Chain, alternative?: string) { - super({ - errorKey: "wallet_ctrl_operation_not_supported", - info: { - operation, - chain, - message: `CTRL wallet does not support ${operation} for ${chain}`, - alternative: alternative || "Use the transfer method instead", - documentation: "https://docs.swapkit.dev/wallets/ctrl#limitations", - }, - }); - } -} -``` - -### Phase 5: Documentation - -Create comprehensive documentation: - -1. **Capabilities Matrix** - ```markdown - ## CTRL Wallet Capabilities - - | Chain | Sign | Sign & Broadcast | Transfer | Build Tx | - |-------|------|-----------------|----------|----------| - | Bitcoin | ❌ | ❌ | ✅ | ✅ | - | Ethereum | ✅ | ✅ | ✅ | ✅ | - | Solana | ✅ | ✅ | ✅ | ✅ | - ``` - -2. **Migration Guide** - ```typescript - // Before (attempting PSBT signing) - try { - const signed = await wallet.signPSBT(psbt); - } catch (error) { - // Handle error - } - - // After (using CTRL transfer) - if (wallet.type === WalletOption.CTRL && isUTXOChain(chain)) { - // Use transfer API directly - const txHash = await wallet.transfer(params); - } else { - // Standard signing flow - const signed = await wallet.signPSBT(psbt); - } - ``` - -3. **Best Practices** - - Always check wallet capabilities before attempting operations - - Provide fallback mechanisms for unsupported operations - - Guide users to alternative methods when signing isn't available - -## Testing Strategy - -### Unit Tests -```typescript -describe("CTRL Wallet", () => { - describe("UTXO Chains", () => { - it("should throw informative error for PSBT signing", async () => { - const wallet = await connectCtrl([Chain.Bitcoin]); - await expect(wallet.signPSBT(psbt)).rejects.toThrow( - "CTRL wallet does not support PSBT signing" - ); - }); - - it("should successfully transfer using CTRL API", async () => { - const wallet = await connectCtrl([Chain.Bitcoin]); - const txHash = await wallet.transfer(params); - expect(txHash).toBeDefined(); - }); - }); - - describe("EVM Chains", () => { - it("should sign transactions", async () => { - const wallet = await connectCtrl([Chain.Ethereum]); - const signed = await wallet.signTransaction(tx); - expect(signed).toBeDefined(); - }); - }); -}); -``` - -### Integration Tests -- Test with CTRL wallet browser extension -- Verify transfer operations work correctly -- Ensure error messages are helpful -- Test fallback mechanisms - -## Timeline - -### Week 1: Foundation -- Day 1-2: Implement capability detection system -- Day 3-4: Create EVM and Solana signing methods -- Day 5: Build UTXO transaction builder - -### Week 2: Integration -- Day 1-2: Update CTRL wallet integration -- Day 3: Implement error handling -- Day 4-5: Write documentation - -### Week 3: Testing & Refinement -- Day 1-2: Unit tests -- Day 3: Integration tests -- Day 4-5: User testing and feedback - -## Future Enhancements - -### Short-term (1-2 months) -1. Work with CTRL team to request PSBT signing API -2. Implement transaction status monitoring -3. Add transaction fee estimation for CTRL transfers - -### Long-term (3-6 months) -1. Full PSBT signing support (pending CTRL API) -2. Batch transaction support -3. Advanced transaction building with custom scripts - -## Risk Mitigation - -### Technical Risks -- **Missing PSBT signing**: Users expect signing capability - - *Mitigation*: Clear documentation, helpful errors, transfer fallback - -- **API changes**: CTRL might change their API - - *Mitigation*: Version detection, compatibility layer - -### User Experience Risks -- **Confusion about capabilities**: Users don't understand limitations - - *Mitigation*: Clear UI indicators, capability checks, guided flows - -## Conclusion - -While CTRL wallet doesn't expose direct signing for UTXO chains, we can provide a functional implementation that: -1. Uses CTRL's transfer API for UTXO operations -2. Provides full signing for EVM and Solana -3. Clearly communicates limitations -4. Guides users to appropriate alternatives - -This approach maintains SwapKit's consistent interface while working within CTRL's constraints. Future CTRL API enhancements could enable full signing support. \ No newline at end of file diff --git a/SIGNING_IMPLEMENTATION_PLAN.md b/SIGNING_IMPLEMENTATION_PLAN.md deleted file mode 100644 index 2fce2a03bb..0000000000 --- a/SIGNING_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,305 +0,0 @@ -# SwapKit Universal Signing Implementation Plan - -## Executive Summary -Implement standardized `sign` and `signAndBroadcast` methods across all toolboxes and wallets to support: -- **PSBT signing** for UTXO chains -- **EVM transaction signing** for EVM chains -- **Base64 encoded versioned transaction signing** for Solana - -## Current State Analysis - -### UTXO Chains (Bitcoin, Litecoin, Dogecoin, BitcoinCash, Zcash) -- ✅ Have `createTransaction` that builds PSBTs -- ✅ Have internal `signer.signTransaction(psbt)` capability -- ❌ Missing exposed `sign` method for PSBT-only signing -- ❌ Missing exposed `signAndBroadcast` method -- ✅ Have `transfer` that combines sign + broadcast - -### EVM Chains (Ethereum, BSC, Avalanche, Polygon, Arbitrum, Optimism, Base) -- ✅ Have `sendTransaction` that handles sign + broadcast -- ✅ Have internal `signer.signTransaction` capability -- ❌ Missing exposed `sign` method for transaction objects -- ❌ Missing exposed `signAndBroadcast` method -- ✅ Have `transfer` that combines sign + broadcast - -### Solana -- ✅ Have `signTransaction` method exposed -- ✅ Have `broadcastTransaction` method exposed -- ❌ Missing unified `signAndBroadcast` method -- ✅ Supports versioned transactions - -## Implementation Tasks - -### Phase 1: Core Toolbox Updates - -#### 1.1 UTXO Toolbox Enhancement -**Files to modify:** -- `packages/toolboxes/src/utxo/toolbox/utxo.ts` -- `packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts` -- `packages/toolboxes/src/utxo/toolbox/zcash.ts` -- `packages/toolboxes/src/utxo/types.ts` - -**Tasks:** -1. Add `sign` method to toolbox interface - ```typescript - sign: (psbt: Psbt) => Promise - ``` -2. Add `signAndBroadcast` method - ```typescript - signAndBroadcast: (psbt: Psbt) => Promise - ``` -3. Update type definitions in `utxo/types.ts` -4. Implement methods in each UTXO toolbox variant - -#### 1.2 EVM Toolbox Enhancement -**Files to modify:** -- `packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` -- `packages/toolboxes/src/evm/toolbox/evm.ts` -- `packages/toolboxes/src/evm/toolbox/op.ts` -- `packages/toolboxes/src/evm/types.ts` - -**Tasks:** -1. Add `sign` method to toolbox interface - ```typescript - sign: (tx: EVMTxParams) => Promise - ``` -2. Add `signAndBroadcast` method - ```typescript - signAndBroadcast: (tx: EVMTxParams) => Promise - ``` -3. Refactor existing `sendTransaction` to use new methods -4. Update type definitions - -#### 1.3 Solana Toolbox Enhancement -**Files to modify:** -- `packages/toolboxes/src/solana/toolbox.ts` -- `packages/toolboxes/src/solana/index.ts` - -**Tasks:** -1. Keep existing `signTransaction` as `sign` for consistency - ```typescript - sign: ( - transaction: Transaction | VersionedTransaction - ) => Promise - ``` -2. Add unified `signAndBroadcast` method - ```typescript - signAndBroadcast: ( - transaction: Transaction | VersionedTransaction - ) => Promise - ``` -3. Ensure versioned transaction support is documented -4. Add base64 encoding/decoding utilities - -### Phase 2: Wallet Integration Updates - -#### 2.1 Hardware Wallets -**Wallets:** Ledger, Trezor, KeepKey - -**Tasks per wallet:** -1. Implement PSBT signing for UTXO chains -2. Implement EVM transaction signing -3. Add Solana support where applicable -4. Update wallet interfaces - -#### 2.2 Browser Extension Wallets -**Wallets:** MetaMask, Coinbase, OKX, Phantom, Talisman, Exodus - -**Tasks per wallet:** -1. Expose native signing methods through standardized interface -2. Handle chain-specific signing requirements -3. Add proper error handling and user cancellation - -#### 2.3 Mobile Wallets -**Wallets:** WalletConnect, Xaman, Coinbase Mobile, OKX Mobile - -**Tasks per wallet:** -1. Implement QR code or deep-link based signing -2. Handle async signing flows -3. Add timeout and retry logic - -#### 2.4 Keystore Wallet -**Files:** `packages/wallets/src/keystore/` - -**Tasks:** -1. Implement all signing methods directly -2. Add secure key management -3. Support all chain types - -### Phase 3: Type System Updates - -#### 3.1 Core Types -**Files to modify:** -- `packages/helpers/src/types/wallet.ts` -- `packages/toolboxes/src/types.ts` - -**New interfaces:** -```typescript -interface SigningCapabilities { - // Unified signing interface - same method names across all chains - sign?: ( - transaction: Psbt | EVMTxParams | Transaction | VersionedTransaction - ) => Promise; - - signAndBroadcast?: ( - transaction: Psbt | EVMTxParams | Transaction | VersionedTransaction - ) => Promise; -} -``` - -### Phase 4: Testing & Documentation - -#### 4.1 Unit Tests -**Files to create/modify:** -- `packages/toolboxes/src/utxo/__tests__/signing.test.ts` -- `packages/toolboxes/src/evm/__tests__/signing.test.ts` -- `packages/toolboxes/src/solana/__tests__/signing.test.ts` - -#### 4.2 Integration Tests -- Test each wallet with each supported chain -- Test error scenarios (user rejection, timeout, etc.) -- Test with mainnet forks - -#### 4.3 Documentation -- Update API documentation -- Create migration guide for existing integrations -- Add code examples for each signing method - -## Implementation Order - -### Week 1: Core Toolbox Updates -1. **Day 1-2:** UTXO toolbox signing methods -2. **Day 3-4:** EVM toolbox signing methods -3. **Day 5:** Solana toolbox refinements - -### Week 2: Wallet Integrations (Priority 1) -1. **Day 1-2:** Ledger wallet -2. **Day 3:** MetaMask & Coinbase wallets -3. **Day 4:** Phantom wallet -4. **Day 5:** WalletConnect - -### Week 3: Wallet Integrations (Priority 2) -1. **Day 1:** OKX wallet -2. **Day 2:** Trezor & KeepKey -3. **Day 3:** Keystore wallet -4. **Day 4-5:** Remaining wallets - -### Week 4: Testing & Documentation -1. **Day 1-2:** Unit test implementation -2. **Day 3:** Integration test implementation -3. **Day 4-5:** Documentation and examples - -## Success Criteria - -1. **Consistency:** All toolboxes expose the same signing interface -2. **Compatibility:** All wallets work with all supported chains -3. **Type Safety:** Full TypeScript coverage with no `any` types -4. **Testing:** >90% code coverage for new methods -5. **Documentation:** Complete API docs and migration guide - -## Risk Mitigation - -### Technical Risks -- **Hardware wallet limitations:** Some hardware wallets may not support all chains - - *Mitigation:* Document supported chains per wallet - -- **Breaking changes:** Existing integrations might break - - *Mitigation:* Keep existing methods, deprecate gradually - -- **Async signing flows:** Mobile wallets have complex async patterns - - *Mitigation:* Implement robust timeout and retry logic - -### Timeline Risks -- **Wallet vendor changes:** External wallet APIs might change - - *Mitigation:* Pin wallet SDK versions, monitor changelogs - -- **Testing complexity:** Testing all combinations is time-consuming - - *Mitigation:* Prioritize critical paths, use test matrices - -## Monitoring & Rollout - -### Rollout Strategy -1. **Beta release:** Release as `@next` tag for early adopters -2. **Gradual migration:** Provide compatibility layer -3. **Full release:** After 2 weeks of beta testing - -### Monitoring -- Track signing success rates per wallet/chain -- Monitor error rates and types -- Collect user feedback through GitHub issues - -## Appendix: Detailed File Changes - -### UTXO Toolbox Changes - -#### `/packages/toolboxes/src/utxo/toolbox/utxo.ts` -```typescript -// Add to toolbox return object: -sign: async (psbt: Psbt): Promise => { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedPsbt = await signer.signTransaction(psbt); - return signedPsbt; -}, - -signAndBroadcast: async (psbt: Psbt): Promise => { - if (!signer) throw new SwapKitError("toolbox_utxo_no_signer"); - const signedPsbt = await signer.signTransaction(psbt); - signedPsbt.finalizeAllInputs(); - const txHex = signedPsbt.extractTransaction().toHex(); - return broadcastTx(txHex); -}, -``` - -### EVM Toolbox Changes - -#### `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` -```typescript -// Add to BaseEVMToolbox return object: -sign: async (tx: EVMTxParams): Promise => { - if (!signer) throw new SwapKitError("toolbox_evm_no_signer"); - const signedTx = await signer.signTransaction(tx); - return signedTx; -}, - -signAndBroadcast: async (tx: EVMTxParams): Promise => { - const signedTx = await sign(tx); - const response = await provider.broadcastTransaction(signedTx); - return response.hash; -}, -``` - -### Solana Toolbox Changes - -#### `/packages/toolboxes/src/solana/toolbox.ts` -```typescript -// Note: signTransaction already exists, rename it to sign for consistency -// Add to toolbox return object: -sign: signTransaction(getConnection, signer), // Alias existing signTransaction - -signAndBroadcast: async ( - transaction: Transaction | VersionedTransaction -): Promise => { - const signedTx = await signTransaction(getConnection, signer)(transaction); - return broadcastTransaction(getConnection)(signedTx); -}, - -// Add base64 utilities: -serializeTransaction: (tx: Transaction | VersionedTransaction): string => { - return Buffer.from(tx.serialize()).toString('base64'); -}, - -deserializeTransaction: ( - base64: string -): Transaction | VersionedTransaction => { - const buffer = Buffer.from(base64, 'base64'); - // Detect and deserialize based on version - return VersionedTransaction.deserialize(buffer); -}, -``` - -## Notes - -- All signing methods should be optional on interfaces to maintain backward compatibility -- Error messages should be chain-specific for better debugging -- Consider adding signing method capability detection -- Hardware wallet support matrix should be documented clearly diff --git a/SOLUTION_SUMMARY.md b/SOLUTION_SUMMARY.md deleted file mode 100644 index 6185bb6678..0000000000 --- a/SOLUTION_SUMMARY.md +++ /dev/null @@ -1,136 +0,0 @@ -# SwapKit API Integration Solution Summary - -## Overview - -We've implemented a comprehensive solution for integrating the SwapKit API with wallet signing capabilities across all supported wallets. The solution intelligently handles wallets with and without transaction signing support. - -## What Was Accomplished - -### 1. Unified Transaction Signing Interface - -- Added `signTransaction` and `signAndBroadcastTransaction` methods to all toolboxes -- Extracted signing logic into separate functions for cleaner code architecture -- Maintained backward compatibility with existing implementations - -### 2. Wallet Analysis - -Identified wallets that need special handling: - -**Wallets WITHOUT signing support:** -- CTRL Wallet -- Vultisig Wallet -- KeepKey-BEX Wallet - -**Wallets WITH signing support:** -- Exodus, Phantom, OKX, OneKey, TronLink -- WalletConnect, Coinbase, Bitget -- Hardware wallets (Ledger, Trezor) -- All standard EVM wallets (MetaMask, etc.) -- Keplr, Cosmostation, and other chain-specific wallets - -### 3. SwapKit Plugin Implementation - -Created a new SwapKit plugin (`packages/plugins/src/swapkit/`) with: - -- **Quote method**: Fetches quotes from SwapKit API -- **Swap method**: Executes swaps with intelligent routing: - - Primary: Uses `signAndBroadcastTransaction` for supported wallets - - Fallback: Uses `transfer` method for non-supporting wallets -- **Automatic detection** of wallet capabilities -- **Multi-chain support** for all major blockchain types - -## How It Works - -### Flow Diagram - -``` -User Request Swap - ↓ -SwapKit API Quote - ↓ -Get Transaction Data - ↓ -Check Wallet Type - ↓ - ┌───┴───┐ - │ │ -Supports Doesn't -Signing Support - │ │ - ↓ ↓ -signAnd transfer() -Broadcast with API -Transaction params - │ │ - └───┬───┘ - ↓ -Return TX Hash -``` - -### Implementation Details - -1. **For Supporting Wallets**: - - SwapKit API provides built transaction (PSBT, EVM tx, Solana tx) - - Plugin calls `wallet.signAndBroadcastTransaction(tx)` - - Transaction is signed and broadcast - -2. **For Non-Supporting Wallets**: - - SwapKit API provides transfer parameters - - Plugin calls `wallet.transfer(params)` - - Wallet executes transfer with memo/data as needed - -## Key Files Modified - -### Toolboxes -- `/packages/toolboxes/src/utxo/toolbox/utxo.ts` - UTXO signing methods -- `/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts` - BCH signing methods -- `/packages/toolboxes/src/utxo/toolbox/zcash.ts` - Zcash signing methods -- `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` - EVM signing methods -- `/packages/toolboxes/src/solana/toolbox.ts` - Solana signing methods - -### Plugin Files (New) -- `/packages/plugins/src/swapkit/plugin.ts` - Main plugin implementation -- `/packages/plugins/src/swapkit/types.ts` - TypeScript types -- `/packages/plugins/src/swapkit/index.ts` - Module exports -- `/packages/plugins/src/swapkit/USAGE.md` - Usage documentation - -### Documentation -- `/packages/plugins/swapkit/wallet-signing-analysis.md` - Wallet capabilities analysis - -## Benefits - -1. **Unified Interface**: All wallets now have consistent `signTransaction` and `signAndBroadcastTransaction` methods -2. **Automatic Fallback**: Wallets without signing automatically use transfer method -3. **No Breaking Changes**: Existing code continues to work -4. **Future-Proof**: Easy to add new wallets or modify behavior -5. **Developer-Friendly**: Simple API for executing swaps regardless of wallet type - -## Usage Example - -```typescript -// Initialize with plugin -const swapKit = createSwapKit({ - plugins: { ...SwapKitPlugin } -}); - -// Get quote -const quote = await swapKit.swapkit.quote({ - sellAsset: 'ETH.ETH', - buyAsset: 'BTC.BTC', - sellAmount: '1000000000000000000' -}); - -// Execute swap (works with ANY wallet) -const txHash = await swapKit.swapkit.swap({ route: quote }); -``` - -## Next Steps - -1. **Testing**: Test with each wallet type to ensure proper behavior -2. **SwapKit API Integration**: Ensure API returns proper transaction data and transfer params -3. **Error Handling**: Add specific error messages for each failure case -4. **Monitoring**: Add telemetry to track which wallets use which method - -## Conclusion - -This solution provides a robust, maintainable way to integrate the SwapKit API with all supported wallets, automatically handling the differences in wallet capabilities while maintaining a clean, unified interface for developers. \ No newline at end of file diff --git a/TOOLBOX_SIGNING_METHODS_SUMMARY.md b/TOOLBOX_SIGNING_METHODS_SUMMARY.md deleted file mode 100644 index 96a01b1eb9..0000000000 --- a/TOOLBOX_SIGNING_METHODS_SUMMARY.md +++ /dev/null @@ -1,104 +0,0 @@ -# SwapKit Toolbox Signing Methods Summary - -## Overview -This document summarizes the analysis of signing methods across all SwapKit toolboxes. The goal is to standardize the following three methods across all chains: - -1. **signTransaction** - Signs a transaction and returns the signed transaction -2. **signAndBroadcastTransaction** - Signs and broadcasts a transaction, returns the tx hash -3. **signMessage** - Signs a message for authentication/verification - -## Reference Implementation (EVM & UTXO) - -### EVM Toolbox ✅ Complete -```typescript -signTransaction: (tx: TransactionRequest) => Promise -signAndBroadcastTransaction: (tx: TransactionRequest) => Promise -signMessage: (message: string) => Promise -``` - -### UTXO Toolbox ✅ Complete -```typescript -signTransaction: (psbt: Psbt) => Promise -signAndBroadcastTransaction: (psbt: Psbt) => Promise -// Note: BCH uses different signatures with TransactionBuilderType -``` - -## Toolbox Status Summary - -| Toolbox | signTransaction | signAndBroadcastTransaction | signMessage | Notes | -|---------|----------------|----------------------------|-------------|-------| -| **EVM** | ✅ Implemented | ✅ Implemented | ✅ Implemented | Complete implementation | -| **UTXO** | ✅ Implemented | ✅ Implemented | ❌ Missing | No signMessage method | -| **Solana** | ✅ Implemented | ✅ Implemented | ❌ Missing | signMessage in provider but not toolbox | -| **Cosmos** | ❌ Missing | ✅ Implemented (THORChain only) | ❌ Missing | Explicitly omitted from types | -| **Tron** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | -| **Near** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | -| **Ripple** | ✅ Implemented | ❌ Missing | ❌ Missing | Has separate sign + broadcast | -| **Substrate** | ✅ `sign` | ✅ `signAndBroadcast` | ❌ Missing | Different naming convention | -| **Radix** | ❌ Missing | ❌ Missing* | ❌ Missing | *Has error-throwing placeholder | - -## Implementation Priority - -### High Priority (Core Functionality Missing) -1. **Radix** - Needs all three methods implemented -2. **Cosmos** - Needs signTransaction and signMessage - -### Medium Priority (Convenience Methods) -1. **Tron** - Add signAndBroadcastTransaction, signMessage -2. **Near** - Add signAndBroadcastTransaction, signMessage -3. **Ripple** - Add signAndBroadcastTransaction, signMessage - -### Low Priority (Message Signing) -1. **UTXO** - Add signMessage -2. **Solana** - Expose signMessage from provider to toolbox -3. **Substrate** - Add signMessage (consider renaming to match standard) - -## Recommended Method Signatures - -### signTransaction -```typescript -// Generic signature -signTransaction: (transaction: ChainSpecificTransaction) => Promise - -// Examples: -// Tron: (transaction: TronTransaction) => Promise -// Near: (transaction: Transaction) => Promise -// Ripple: (tx: Transaction) => Promise<{ tx_blob: string; hash: string }> -``` - -### signAndBroadcastTransaction -```typescript -// Generic signature -signAndBroadcastTransaction: (transaction: ChainSpecificTransaction) => Promise - -// Should combine existing signTransaction + broadcastTransaction -``` - -### signMessage -```typescript -// Generic signature -signMessage: (message: string) => Promise - -// Returns signature as hex/base64 string -``` - -## Implementation Notes - -1. **Error Handling**: All methods should throw `SwapKitError` with appropriate error keys when signer is not available -2. **Signer Validation**: Check for signer existence before attempting any signing operation -3. **Return Values**: signAndBroadcastTransaction should always return transaction hash as string -4. **Async/Await**: All signing methods should be async functions -5. **Type Safety**: Maintain chain-specific transaction types - -## Files Analyzed - -- `/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts` -- `/packages/toolboxes/src/utxo/toolbox/utxo.ts` -- `/packages/toolboxes/src/solana/toolbox.ts` -- `/packages/toolboxes/src/cosmos/toolbox/cosmos.ts` -- `/packages/toolboxes/src/cosmos/toolbox/thorchain.ts` -- `/packages/toolboxes/src/tron/toolbox.ts` -- `/packages/toolboxes/src/near/toolbox.ts` -- `/packages/toolboxes/src/ripple/index.ts` -- `/packages/toolboxes/src/substrate/toolbox.ts` -- `/packages/toolboxes/src/radix/toolbox.ts` \ No newline at end of file diff --git a/WALLET_SIGNER_ANALYSIS.md b/WALLET_SIGNER_ANALYSIS.md deleted file mode 100644 index bd1651611c..0000000000 --- a/WALLET_SIGNER_ANALYSIS.md +++ /dev/null @@ -1,168 +0,0 @@ -# SwapKit Wallet Signer Implementation Analysis - -## Executive Summary - -Analysis of 19 wallet packages to identify custom signer implementations and toolbox method overrides. - -## Classification by Implementation Pattern - -### 🔴 Full Custom Signer Implementations - -These wallets implement comprehensive custom signer methods: - -| Wallet | Custom Signers | Key Methods | Notes | -|--------|---------------|-------------|-------| -| **Bitget** | BTC, Cosmos, Solana, Tron | `signTransaction`, `getAddress` | Most comprehensive multi-chain custom signers | -| **Coinbase** | EVM (AbstractSigner) | `signTransaction`, `signMessage`, `getAddress` | Custom EVM signer class | -| **WalletConnect** | EVM, NEAR, Tron | `signTransaction`, `signMessage`, `getAddress` | Protocol-based signers | -| **OneKey** | BTC (sats-connect), EVM | `signTransaction`, `signMessage`, `getAddress` | Custom EVM AbstractSigner | -| **OKX** | BTC, Tron, Near | `signTransaction`, `getAddress` | Multi-chain custom implementations | -| **TronLink** | Tron | `signTransaction`, `getAddress` | Specialized Tron signer | - -### 🟡 Partial Custom Implementations - -These wallets have selective custom implementations: - -| Wallet | Custom Elements | Standard Elements | Notes | -|--------|----------------|-------------------|-------| -| **CTRL** | Near signer wrapper | EVM, Cosmos use standard | Extensive method overrides | -| **Exodus** | BTC (sats-connect) | EVM, Solana standard | Mixed approach | -| **KeepKey BEX** | BTC/UTXO signing | Uses provider signers | Custom wallet transfer methods | -| **Vultisig** | Custom transfer methods | Provider signers | Heavy toolbox overrides | - -### 🟢 Standard Implementations - -These wallets use standard toolbox signers: - -| Wallet | Signer Type | Toolbox Overrides | Notes | -|--------|------------|-------------------|-------| -| **Cosmostation** | Keplr + window.ethereum | None | Standard patterns | -| **Keplr** | OfflineSignerOnlyAmino | None | Cosmos-only, minimal customization | -| **Phantom** | Standard providers | Solana transfer override | Minimal customization | -| **PolkadotJS** | Extension signer | None | Single-chain standard | -| **Talisman** | Standard toolbox signers | None | Multi-chain standard | - -### 🔵 Special Cases - -| Wallet | Pattern | Explanation | -|--------|---------|-------------| -| **Radix** | No standard toolbox | Custom implementation with `signAndBroadcast` | -| **Xaman** | App-specific flow | Uses QR/deep link signing, no standard signer | - -## Toolbox Method Override Analysis - -### Heavy Override Pattern -Wallets that extensively modify toolbox behavior: - -1. **CTRL** - - Overrides: `transfer`, `deposit` (THORChain/Maya/BTC/UTXO/Near) - - Custom: `walletTransfer`, `createTransaction` methods - -2. **Vultisig** - - Overrides: `transfer` (all chains), `deposit` (THORChain/Maya) - - Custom: `approve`, `call`, `sendTransaction` for EVM - -3. **KeepKey BEX** - - Overrides: `transfer`, `deposit` (Cosmos/THORChain) - - Custom: `getBalance` for BTC - -### Selective Override Pattern -Wallets with specific customizations: - -1. **Exodus**: Solana `transfer` override -2. **Phantom**: Solana `transfer` with custom validation -3. **OKX**: Cosmos `transfer` override -4. **WalletConnect**: THORChain `transfer` and `deposit` overrides - -### No Override Pattern -Wallets using standard toolboxes: -- Cosmostation, Keplr, OneKey, PolkadotJS, Talisman, TronLink - -## Chain Support Matrix - -### Multi-Chain Leaders (15+ chains) -1. **CTRL**: 21 chains -2. **KeepKey BEX**: 18 chains -3. **Vultisig**: 17 chains -4. **OKX**: 15 chains -5. **WalletConnect**: 15 chains - -### Moderate Support (5-14 chains) -1. **Bitget**: 14 chains -2. **Cosmostation**: 11 chains -3. **OneKey**: 11 chains -4. **Exodus**: ~11 chains -5. **Talisman**: 9 chains -6. **Coinbase**: 7 chains - -### Specialized/Limited (1-4 chains) -1. **Keplr**: 4 chains (Cosmos ecosystem) -2. **Phantom**: 3 chains -3. **PolkadotJS**: 1 chain (Polkadot) -4. **Radix**: 1 chain (Radix) -5. **TronLink**: 1 chain (Tron) -6. **Xaman**: 1 chain (Ripple) - -## Key Findings - -### Signer Implementation Patterns - -1. **Custom Signer Pattern** (30% of wallets) - - Implement full `ChainSigner` interface - - Methods: `getAddress()`, `signTransaction()`, `signMessage()` - - Examples: Bitget, Coinbase, WalletConnect - -2. **Provider Wrapper Pattern** (40% of wallets) - - Wrap wallet-specific providers into SwapKit interfaces - - Minimal custom implementation - - Examples: Keplr, Talisman, PolkadotJS - -3. **Hybrid Pattern** (20% of wallets) - - Mix custom signers for some chains, standard for others - - Examples: CTRL, Exodus, OKX - -4. **Special Pattern** (10% of wallets) - - Non-standard implementations - - Examples: Radix (custom API), Xaman (app-specific) - -### Toolbox Override Patterns - -1. **Heavy Override** (~15% of wallets): CTRL, Vultisig, KeepKey BEX -2. **Selective Override** (~25% of wallets): Exodus, Phantom, OKX, WalletConnect -3. **No Override** (~60% of wallets): Most wallets use standard toolboxes - -### Common Override Methods - -Most frequently overridden methods: -1. `transfer` - Custom transaction flows -2. `deposit` - THORChain/Maya specific -3. `getBalance` - Custom balance fetching -4. `signTransaction` - Chain-specific signing - -## Recommendations - -### For Standardization - -1. **High Priority Wallets** (extensive custom implementations): - - CTRL: Consider standardizing transfer methods - - Vultisig: Evaluate if custom methods can use standard patterns - - KeepKey BEX: Review necessity of custom balance methods - -2. **Medium Priority** (partial customizations): - - Standardize Solana transfer overrides (Exodus, Phantom) - - Unify THORChain/Maya deposit patterns - -3. **Low Priority** (working as intended): - - Standard implementation wallets - - Single-chain specialized wallets - -### Architecture Patterns to Preserve - -1. **Good Patterns**: - - Custom signers for chains requiring special handling (BTC, Tron) - - Provider wrapper pattern for standard chains - - Clear separation of concerns - -2. **Patterns to Review**: - - Extensive method overrides that duplicate toolbox functionality - - Inconsistent error handling across custom implementations \ No newline at end of file From f4c8227d955e5a37a3fb5bfac5f0295a4b5529cd Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 10 Sep 2025 13:16:03 +0400 Subject: [PATCH 12/15] WIP: remove md files --- packages/plugins/src/swapkit/USAGE.md | 186 -------------------------- 1 file changed, 186 deletions(-) delete mode 100644 packages/plugins/src/swapkit/USAGE.md diff --git a/packages/plugins/src/swapkit/USAGE.md b/packages/plugins/src/swapkit/USAGE.md deleted file mode 100644 index b424089c1f..0000000000 --- a/packages/plugins/src/swapkit/USAGE.md +++ /dev/null @@ -1,186 +0,0 @@ -# SwapKit Plugin Usage Guide - -The SwapKit plugin enables swapping through the SwapKit API with automatic handling of transaction signing and broadcasting. - -## Features - -- **Quote fetching** from SwapKit API -- **Automatic transaction signing** for supported wallets -- **Fallback to transfer method** for wallets without signing support (CTRL, Vultisig, KeepKey-BEX) -- **Multi-chain support** (Bitcoin, Ethereum, Solana, Cosmos, etc.) - -## Basic Usage - -```typescript -import { createSwapKit } from '@swapkit/core'; -import { SwapKitPlugin } from '@swapkit/plugins/swapkit'; - -// Initialize SwapKit with the plugin -const swapKit = createSwapKit({ - plugins: { - ...SwapKitPlugin, - }, - config: { - // Your config - } -}); - -// Connect wallet -await swapKit.connectWallet(WalletOption.METAMASK, [Chain.Ethereum]); - -// Get a quote -const quote = await swapKit.swapkit.quote({ - sellAsset: 'ETH.ETH', - buyAsset: 'BTC.BTC', - sellAmount: '1000000000000000000', // 1 ETH in wei - sourceAddress: '0x...', // Your ETH address - destinationAddress: 'bc1q...', // Your BTC address - slippage: 3, // 3% slippage - affiliate: 'YOUR_AFFILIATE', - affiliateBasisPoints: 100, // 1% -}); - -// Execute the swap -const txHash = await swapKit.swapkit.swap({ - route: quote, - recipient: 'bc1q...', // Optional, uses route's destination by default -}); - -console.log('Swap transaction:', txHash); -``` - -## Wallet-Specific Behavior - -### Wallets with Full Signing Support - -For wallets like MetaMask, Ledger, Trezor, Exodus, Phantom, OKX, OneKey, etc.: - -```typescript -// The plugin will automatically use signAndBroadcastTransaction -const txHash = await swapKit.swapkit.swap({ route }); -// Transaction is signed and broadcast directly -``` - -### Wallets without Signing Support - -For wallets like CTRL, Vultisig, and KeepKey-BEX: - -```typescript -// The plugin automatically falls back to the transfer method -const txHash = await swapKit.swapkit.swap({ route }); -// Behind the scenes, it uses wallet.transfer() with params from SwapKit API -``` - -## Advanced Usage - -### Checking Wallet Capabilities - -```typescript -// Check if a wallet supports signing -const supportsSigniing = swapKit.swapkit.walletSupportsSignAndBroadcast(WalletOption.CTRL); -console.log('CTRL supports signing:', supportsSigniing); // false -``` - -### Override SignAndBroadcast for Non-Supporting Wallets - -```typescript -// Get wallet instance -const wallet = swapKit.getWallet(Chain.Bitcoin); - -// Override signAndBroadcastTransaction if needed -const enhancedWallet = swapKit.swapkit.overrideSignAndBroadcastForWallet( - wallet, - Chain.Bitcoin -); -``` - -## Transaction Types by Chain - -### UTXO Chains (Bitcoin, Litecoin, Dogecoin, etc.) - -```typescript -// SwapKit API provides PSBT (Partially Signed Bitcoin Transaction) -// Plugin handles: -const psbt = Psbt.fromHex(transactionData.psbt); -await wallet.signAndBroadcastTransaction(psbt); -``` - -### EVM Chains (Ethereum, Avalanche, BSC, etc.) - -```typescript -// SwapKit API provides transaction parameters -// Plugin creates ethers.js TransactionRequest: -const txRequest = { - to: transactionData.to, - data: transactionData.data, - value: BigInt(transactionData.value), - gasLimit: BigInt(transactionData.gas), - gasPrice: BigInt(transactionData.gasPrice), -}; -await wallet.signAndBroadcastTransaction(txRequest); -``` - -### Solana - -```typescript -// SwapKit API provides base64 encoded transaction -// Plugin handles versioned and legacy transactions: -const tx = VersionedTransaction.deserialize(buffer); -await wallet.signAndBroadcastTransaction(tx); -``` - -### Cosmos Chains (THORChain, Maya, Cosmos, etc.) - -```typescript -// Typically uses transfer with memo -await wallet.transfer({ - recipient: route.destination, - assetValue: sellAsset, - memo: route.memo, -}); -``` - -## Error Handling - -```typescript -try { - const quote = await swapKit.swapkit.quote({ - sellAsset: 'ETH.ETH', - buyAsset: 'BTC.BTC', - sellAmount: '1000000000000000000', - }); - - const txHash = await swapKit.swapkit.swap({ route: quote }); - console.log('Success:', txHash); -} catch (error) { - if (error.errorKey === 'core_quote_failed') { - console.error('Failed to get quote:', error); - } else if (error.errorKey === 'core_swap_failed') { - console.error('Failed to execute swap:', error); - } else if (error.errorKey === 'core_wallet_feature_not_supported') { - console.error('Wallet doesn\'t support this operation:', error); - } -} -``` - -## Testing with Different Wallets - -```typescript -// Test with a wallet that supports signing -await swapKit.connectWallet(WalletOption.METAMASK, [Chain.Ethereum]); -const txHash1 = await swapKit.swapkit.swap({ route: ethRoute }); - -// Test with a wallet that doesn't support signing -await swapKit.connectWallet(WalletOption.CTRL, [Chain.Bitcoin]); -const txHash2 = await swapKit.swapkit.swap({ route: btcRoute }); -// Automatically uses transfer fallback -``` - -## Configuration - -The plugin automatically detects wallet capabilities and uses the appropriate method: - -1. **Primary method**: `signAndBroadcastTransaction` for wallets with full signing support -2. **Fallback method**: `transfer` for wallets without signing support - -No additional configuration is needed - the plugin handles everything automatically based on the connected wallet type. \ No newline at end of file From e7e0b4d91115cfb6231e0b638bae787d12b28d7c Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 10 Sep 2025 13:16:49 +0400 Subject: [PATCH 13/15] WIP: remove md files --- .../src/swapkit/wallet-signing-analysis.md | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 packages/plugins/src/swapkit/wallet-signing-analysis.md diff --git a/packages/plugins/src/swapkit/wallet-signing-analysis.md b/packages/plugins/src/swapkit/wallet-signing-analysis.md deleted file mode 100644 index c33f0a3e95..0000000000 --- a/packages/plugins/src/swapkit/wallet-signing-analysis.md +++ /dev/null @@ -1,113 +0,0 @@ -# Wallet Signing Capabilities Analysis - -## Wallets that DON'T support signing (need special handling) - -### 1. **CTRL Wallet** -- **Chains affected**: Bitcoin, BitcoinCash, Dogecoin, Litecoin, THORChain, Maya, Near -- **Implementation**: Uses `walletTransfer` for UTXO chains, custom transfer methods for others -- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params - -### 2. **Vultisig Wallet** -- **Chains affected**: All supported chains -- **Implementation**: Uses `walletTransfer` throughout -- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params - -### 3. **KeepKey-BEX Wallet** -- **Chains affected**: All supported chains -- **Implementation**: Uses `walletTransfer` -- **Special handling needed**: Yes - will need to use transfer method with SwapKit API params - -## Wallets that DO support signing - -### 1. **Exodus** -- **Bitcoin**: ✅ Has `signTransaction` via sats-connect -- **EVM chains**: ✅ Standard ethers.js signer -- **Special handling needed**: No - -### 2. **Phantom** -- **Bitcoin**: ✅ Uses toolbox directly (has signing) -- **Ethereum**: ✅ Standard ethers.js signer -- **Solana**: ✅ Uses provider.signTransaction -- **Special handling needed**: No - -### 3. **OKX** -- **Bitcoin**: ✅ Has `signTransaction` via wallet.signPsbt -- **EVM chains**: ✅ Standard ethers.js signer -- **Cosmos**: ⚠️ Uses custom cosmosTransfer -- **Near**: ✅ Has signer -- **Solana**: ✅ Has signing -- **Special handling needed**: Only for Cosmos - -### 4. **OneKey** -- **All chains**: ✅ Has signing capabilities -- **Special handling needed**: No - -### 5. **TronLink** -- **Tron**: ✅ Has signing capabilities -- **Special handling needed**: No - -### 6. **WalletConnect** -- **EVM chains**: ✅ Has EVM signer -- **Other chains**: ✅ Standard signing -- **Special handling needed**: No - -### 7. **Coinbase** -- **All supported chains**: ✅ Has signer implementation -- **Special handling needed**: No - -### 8. **Bitget** -- **All supported chains**: ✅ Has signing helpers -- **Special handling needed**: No - -### 9. **Ledger** (Hardware) -- **All chains**: ✅ Full signing support via hardware -- **Special handling needed**: No - -### 10. **Trezor** (Hardware) -- **All chains**: ✅ Full signing support via hardware -- **Special handling needed**: No - -### 11. **Keplr** -- **Cosmos chains**: ✅ Has signing -- **Special handling needed**: No - -### 12. **Cosmostation** -- **Cosmos chains**: ✅ Has signing -- **Special handling needed**: No - -### 13. **Xaman** -- **XRP/Ripple**: ✅ Has signing -- **Special handling needed**: No - -### 14. **Polkadot.js** -- **Polkadot/Substrate**: ✅ Has signing -- **Special handling needed**: No - -### 15. **Talisman** -- **Polkadot/Substrate**: ✅ Has signing -- **Special handling needed**: No - -### 16. **Radix** -- **Radix**: ✅ Has signing -- **Special handling needed**: No - -### 17. **Keystore** -- **All chains**: ✅ Has signing (software wallet) -- **Special handling needed**: No - -### 18. **EVM Extensions** (MetaMask, etc.) -- **EVM chains**: ✅ Standard ethers.js signer -- **Special handling needed**: No - -## Summary - -**Wallets needing special handling:** -- CTRL -- Vultisig -- KeepKey-BEX -- OKX (Cosmos only) - -For these wallets, we'll need to: -1. Detect if the wallet doesn't support `signAndBroadcastTransaction` -2. Fall back to using the `transfer` method with parameters from SwapKit API -3. Override the `signAndBroadcastTransaction` method to use the fallback \ No newline at end of file From 0e69d0dc63ca4b865d3a0e756fe214e6b7f790da Mon Sep 17 00:00:00 2001 From: towan Date: Wed, 10 Sep 2025 13:17:45 +0400 Subject: [PATCH 14/15] WIP: remove md files --- radix-toolbox-signing-methods-analysis.md | 94 --------------- solana-toolbox-signing-methods-analysis.md | 70 ----------- substrate-toolbox-signing-methods-analysis.md | 112 ------------------ test_ripple_methods.js | 31 ----- test_substrate_methods.js | 38 ------ tron-signing-methods-analysis.md | 70 ----------- 6 files changed, 415 deletions(-) delete mode 100644 radix-toolbox-signing-methods-analysis.md delete mode 100644 solana-toolbox-signing-methods-analysis.md delete mode 100644 substrate-toolbox-signing-methods-analysis.md delete mode 100644 test_ripple_methods.js delete mode 100644 test_substrate_methods.js delete mode 100644 tron-signing-methods-analysis.md diff --git a/radix-toolbox-signing-methods-analysis.md b/radix-toolbox-signing-methods-analysis.md deleted file mode 100644 index afb0c9dd4c..0000000000 --- a/radix-toolbox-signing-methods-analysis.md +++ /dev/null @@ -1,94 +0,0 @@ -# Radix Toolbox Signing Methods Analysis - -## Current State - -The Radix toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/radix/index.ts`) currently has **very limited** signing functionality implemented: - -### Existing Methods - -**✅ signAndBroadcast** - Partially implemented but throws an error -- **Location**: Line 127-129 -- **Implementation**: - ```typescript - signAndBroadcast: (() => { - throw new SwapKitError("toolbox_radix_method_not_supported", { method: "signAndBroadcast" }); - }) as (params: any) => Promise - ``` -- **Status**: Present but non-functional (throws error) -- **Return Type**: `Promise` - -### Other Available Methods -- `getAddress()` - Returns empty string (line 124) -- `getBalance()` - Functional implementation for fetching balances (line 125) -- `validateAddress()` - Functional implementation (line 130) - -## Missing Methods - -**❌ signTransaction** - Not implemented -- **Expected signature**: `(transaction: RadixTransaction) => Promise` -- **Purpose**: Sign a transaction and return the signed transaction without broadcasting -- **Status**: Completely missing - -**❌ signMessage** - Not implemented -- **Expected signature**: `(message: string) => Promise` -- **Purpose**: Sign an arbitrary message for authentication/verification -- **Status**: Completely missing - -**❌ signAndBroadcastTransaction** - Not implemented -- **Expected signature**: `(transaction: RadixTransaction) => Promise` -- **Purpose**: Sign and broadcast a transaction, returning the transaction hash -- **Status**: Missing (only `signAndBroadcast` exists and throws error) - -## Implementation Signatures for Reference - -Based on patterns from other toolboxes (EVM, Solana, Tron), the expected signatures should be: - -```typescript -export const RadixToolbox = async ({ dappConfig }: { dappConfig?: SKConfigIntegrations["radix"] } = {}) => { - // ... existing implementation ... - - return { - // ... existing methods ... - - // Missing methods that should be implemented: - signTransaction: async (transaction: RadixTransactionManifest): Promise => { - // Implementation needed - }, - - signAndBroadcastTransaction: async (transaction: RadixTransactionManifest): Promise => { - // Implementation needed - should return tx hash - }, - - signMessage: async (message: string): Promise => { - // Implementation needed - } - }; -}; -``` - -## Comparison with Other Toolboxes - -**EVM Toolbox** (fully implemented): -- ✅ `signTransaction: (tx: TransactionRequest) => Promise` -- ✅ `signAndBroadcastTransaction: (tx: TransactionRequest) => Promise` -- ✅ `signMessage: (message: string) => Promise` - -**Solana Toolbox** (fully implemented): -- ✅ `signTransaction: (transaction: Transaction | VersionedTransaction) => Promise` -- ✅ `signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise` - -**Tron Toolbox** (fully implemented): -- ✅ `signTransaction: (transaction: TronTransaction) => Promise` - -## Summary - -The Radix toolbox is currently **incomplete** for transaction signing functionality: - -- **0 of 3** required signing methods are properly implemented -- Only a stub `signAndBroadcast` exists that throws an error -- All three core signing methods (`signTransaction`, `signAndBroadcastTransaction`, `signMessage`) need to be implemented -- The toolbox needs integration with Radix Dapp Toolkit's signing capabilities to provide full functionality - -## Next Steps - -We skip this as the chain is very unimportant right now. diff --git a/solana-toolbox-signing-methods-analysis.md b/solana-toolbox-signing-methods-analysis.md deleted file mode 100644 index f1b9b183c8..0000000000 --- a/solana-toolbox-signing-methods-analysis.md +++ /dev/null @@ -1,70 +0,0 @@ -# Solana Toolbox Signing Methods Analysis - -## Current State - -The Solana toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/solana/toolbox.ts`) currently implements **2 out of 3** requested signing methods: - -### ✅ Methods that Exist - -#### 1. `signTransaction` -**Location**: Line 137 in toolbox return object, implementation at lines 353-376 -**Signature**: -```typescript -signTransaction: (transaction: Transaction | VersionedTransaction) => Promise -``` -**Implementation Details**: -- Takes a `Transaction` or `VersionedTransaction` as input -- Handles both wallet provider signers (`signer.signTransaction()`) and direct signers (`transaction.sign([signer])`) -- Automatically sets `recentBlockhash` and `feePayer` for regular transactions -- Returns the signed transaction object -- Throws `SwapKitError("toolbox_solana_no_signer")` if no signer is available - -#### 2. `signAndBroadcastTransaction` -**Location**: Line 133-136 in toolbox return object -**Signature**: -```typescript -signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise -``` -**Implementation Details**: -- Combines signing and broadcasting in a single method -- First calls `signTransaction()` to sign the transaction -- Then calls `broadcastTransaction()` to submit to the network -- Returns the transaction hash as a string -- Uses the same signing logic as `signTransaction` - -## Missing Methods - -### ❌ `signMessage` -**Status**: **Not implemented** in the toolbox return object - -**Note**: While the `SolanaProvider` interface (in `index.ts`) defines a `signMessage` method: -```typescript -signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise -``` - -This method is **not exposed** in the actual toolbox object returned by `getSolanaToolbox()`. It's only available as part of the provider interface for wallet integrations, but not as a direct toolbox method. - -## Implementation Signatures for Reference - -### Current Signatures -```typescript -// ✅ Implemented -signTransaction: (transaction: Transaction | VersionedTransaction) => Promise - -// ✅ Implemented -signAndBroadcastTransaction: (transaction: Transaction | VersionedTransaction) => Promise - -// ❌ Missing from toolbox (but exists in SolanaProvider interface) -signMessage: (message: Uint8Array | string, display?: DisplayEncoding) => Promise -``` - -### Supporting Infrastructure -The toolbox has all necessary infrastructure for signing: -- ✅ Signer management (`SolanaSigner` type supporting both providers and direct signers) -- ✅ Connection management (`getConnection()`) -- ✅ Error handling with SwapKit errors -- ✅ Transaction broadcasting (`broadcastTransaction()`) - -## Recommendation - -To complete the signing method suite, the `signMessage` method should be added to the toolbox return object in the `getSolanaToolbox()` function. The implementation should handle both provider-based signers (using `signer.signMessage()`) and direct signers appropriately. \ No newline at end of file diff --git a/substrate-toolbox-signing-methods-analysis.md b/substrate-toolbox-signing-methods-analysis.md deleted file mode 100644 index 4e8324d1d3..0000000000 --- a/substrate-toolbox-signing-methods-analysis.md +++ /dev/null @@ -1,112 +0,0 @@ -# Substrate Toolbox Signing Methods Analysis - -## Current State - -The Substrate toolbox (`/packages/toolboxes/src/substrate/substrate.ts`) currently implements **2 of 3** required signing methods: - -### ✅ Existing Methods - -#### 1. `sign` - Transaction Signing Method -**Status**: ✅ **Implemented** - -```typescript -sign: (tx: SubmittableExtrinsic<"promise">) => { - if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); - if (isKeyringPair(signer)) return sign(signer, tx); - - throw new SwapKitError( - "core_wallet_not_keypair_wallet", - "Signer does not have keyring pair capabilities required for signing.", - ); -} -``` - -**Implementation Details**: -- Takes a `SubmittableExtrinsic<"promise">` (transaction object) -- Returns a signed transaction using `tx.signAsync(signer)` -- Only works with KeyringPair signers, not external wallet signers -- Throws error if no signer is available or if signer lacks keyring capabilities - -#### 2. `signAndBroadcast` - Sign and Broadcast Method -**Status**: ✅ **Implemented** - -```typescript -signAndBroadcast: ({ - tx, - callback, - address, -}: { - tx: SubmittableExtrinsic<"promise">; - callback?: Callback; - address?: string; -}) => { - if (!signer) throw new SwapKitError("core_wallet_not_keypair_wallet"); - if (isKeyringPair(signer)) return signAndBroadcastKeyring(signer, tx, callback); - - if (address) { - return signAndBroadcast({ address, api, callback, signer, tx }); - } - - throw new SwapKitError( - "core_wallet_not_keypair_wallet", - "Signer does not have keyring pair capabilities required for signing.", - ); -} -``` - -**Implementation Details**: -- Takes transaction object, optional callback, and optional address -- Returns transaction hash as string -- Supports both KeyringPair and external wallet signers -- Uses `tx.signAndSend()` internally -- Handles nonce calculation automatically - -### ❌ Missing Methods - -#### 1. `signMessage` - Message Signing Method -**Status**: ❌ **Not Implemented** - -**Expected Signature** (based on other toolboxes): -```typescript -signMessage: (message: string) => Promise -``` - -**What's Missing**: -- No message signing capability exists in the toolbox -- Would need to use Polkadot.js keyring or signer's message signing functionality -- Should support both raw message signing and structured message formats - -## Method Name Mapping - -The Substrate toolbox uses slightly different naming conventions than the requested standard: - -| Requested Method | Substrate Implementation | Status | -|------------------|-------------------------|---------| -| `signTransaction` | `sign` | ✅ Equivalent functionality | -| `signAndBroadcastTransaction` | `signAndBroadcast` | ✅ Equivalent functionality | -| `signMessage` | ❌ Not implemented | ❌ Missing | - -## Implementation Notes - -### Signer Support -The toolbox supports two types of signers: -- **KeyringPair**: Local keypairs created from seed phrases -- **External Signers**: Wallet-provided signers (Polkadot.js extension wallets) - -### Transaction Handling -- Uses Polkadot.js `SubmittableExtrinsic` objects for transactions -- Automatically handles nonce calculation via `api.rpc.system.accountNextIndex` -- Supports callback-based monitoring of transaction status - -### Error Handling -- Consistent error handling using `SwapKitError` -- Specific error codes for wallet/signer issues -- Clear error messages for debugging - -## Summary - -- **2 of 3** required signing methods are implemented with equivalent functionality -- Transaction signing methods are fully functional and support both local and external signers -- Message signing capability is completely missing -- The existing implementations follow Substrate/Polkadot patterns and integrate well with the ecosystem -- Method names differ slightly from standard but provide the same core functionality \ No newline at end of file diff --git a/test_ripple_methods.js b/test_ripple_methods.js deleted file mode 100644 index a5330752f9..0000000000 --- a/test_ripple_methods.js +++ /dev/null @@ -1,31 +0,0 @@ -// Simple verification test for the new Ripple methods -const { getRippleToolbox } = require('./packages/toolboxes/dist/ripple'); - -async function testRippleToolbox() { - try { - // Create toolbox with a test phrase - const phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; - const rippleWallet = await getRippleToolbox({ phrase }); - - console.log('✅ getRippleToolbox created successfully'); - - // Check if the new methods exist - console.log('signAndBroadcastTransaction exists:', typeof rippleWallet.signAndBroadcastTransaction === 'function'); - console.log('signMessage exists:', typeof rippleWallet.signMessage === 'function'); - - // Test getting address - const address = await rippleWallet.getAddress(); - console.log('Address:', address); - - // Test signing a simple message - const message = "Hello SwapKit!"; - const signature = await rippleWallet.signMessage(message); - console.log('Message signature:', signature); - - console.log('✅ All tests passed!'); - } catch (error) { - console.error('❌ Test failed:', error.message); - } -} - -testRippleToolbox(); \ No newline at end of file diff --git a/test_substrate_methods.js b/test_substrate_methods.js deleted file mode 100644 index a502a111f2..0000000000 --- a/test_substrate_methods.js +++ /dev/null @@ -1,38 +0,0 @@ -// Test script to verify Substrate toolbox method rename and new signMessage functionality -import { Chain } from "@swapkit/helpers"; -import { getSubstrateToolbox } from "@swapkit/toolboxes/substrate"; - -async function testSubstrateToolbox() { - console.log("Testing Substrate toolbox method renames and signMessage..."); - - try { - // Create a test toolbox for Polkadot - const toolbox = await getSubstrateToolbox(Chain.Polkadot); - - // Check that the old method names don't exist - console.log("Checking old method names..."); - console.log("- sign method exists:", typeof toolbox.sign !== "undefined"); - console.log("- signAndBroadcast method exists:", typeof toolbox.signAndBroadcast !== "undefined"); - - // Check that the new method names exist - console.log("Checking new method names..."); - console.log("- signTransaction method exists:", typeof toolbox.signTransaction === "function"); - console.log( - "- signAndBroadcastTransaction method exists:", - typeof toolbox.signAndBroadcastTransaction === "function", - ); - console.log("- signMessage method exists:", typeof toolbox.signMessage === "function"); - - // Test that signMessage has the correct signature - console.log( - "signMessage is a function that returns Promise:", - typeof toolbox.signMessage === "function" && toolbox.signMessage.constructor.name === "Function", - ); - - console.log("\n✅ All method renames and new signMessage method implemented successfully!"); - } catch (error) { - console.error("❌ Error testing toolbox:", error.message); - } -} - -testSubstrateToolbox(); diff --git a/tron-signing-methods-analysis.md b/tron-signing-methods-analysis.md deleted file mode 100644 index 13d4d114ed..0000000000 --- a/tron-signing-methods-analysis.md +++ /dev/null @@ -1,70 +0,0 @@ -# Tron Toolbox Signing Methods Analysis - -## Current State - -### Existing Methods - -The Tron toolbox (`/Users/damian/Workspace/Crypto/Thorswap/SwapKit/packages/toolboxes/src/tron/toolbox.ts`) currently implements: - -#### 1. ✅ `signTransaction` - **EXISTS** -- **Location**: Line 509-512 in toolbox.ts -- **Return Type**: `Promise` -- **Implementation**: - ```typescript - const signTransaction = async (transaction: TronTransaction) => { - if (!signer) throw new SwapKitError("toolbox_tron_no_signer"); - return await signer.signTransaction(transaction); - }; - ``` -- **Signature**: `signTransaction: (transaction: TronTransaction) => Promise;` -- **Status**: Fully implemented and exported from the toolbox - -## Missing Methods - -### 1. ❌ `signAndBroadcastTransaction` - **MISSING** -- **Expected Signature**: `signAndBroadcastTransaction: (transaction: TronTransaction) => Promise;` -- **Expected Behavior**: Should sign a transaction and immediately broadcast it, returning the transaction hash -- **Current Workaround**: Users must call `signTransaction()` followed by `broadcastTransaction()` separately - -### 2. ❌ `signMessage` - **MISSING** -- **Expected Signature**: `signMessage: (message: string) => Promise;` -- **Expected Behavior**: Should sign an arbitrary message and return the signature -- **Notes**: This would be useful for authentication and verification purposes - -## Implementation Signatures for Reference - -### Currently Available Methods -```typescript -// ✅ Available - Signs a transaction -signTransaction: (transaction: TronTransaction) => Promise; - -// ✅ Available - Broadcasts an already signed transaction -broadcastTransaction: (signedTransaction: TronSignedTransaction) => Promise; -``` - -### Missing Methods (Expected Signatures) -```typescript -// ❌ Missing - Signs and broadcasts in one call -signAndBroadcastTransaction: (transaction: TronTransaction) => Promise; - -// ❌ Missing - Signs arbitrary messages -signMessage: (message: string) => Promise; -``` - -## Related Types - -The toolbox uses the following relevant types: -- `TronTransaction` - Transaction object from TronWeb -- `TronSignedTransaction` - Signed transaction object from TronWeb -- `TronSigner` interface with methods: - - `getAddress(): Promise` - - `signTransaction(transaction: TronTransaction): Promise` - -## Conclusion - -The Tron toolbox currently has **1 out of 3** requested signing methods implemented: -- ✅ `signTransaction` - Fully implemented -- ❌ `signAndBroadcastTransaction` - Missing (would be a convenience method) -- ❌ `signMessage` - Missing (would require TronWeb message signing support) - -The existing `signTransaction` method works in conjunction with `broadcastTransaction` to achieve the same result as the missing `signAndBroadcastTransaction` method, but requires two separate calls. \ No newline at end of file From 249c051fdeb7692d5c3aa656911fe1cb38ceb5d9 Mon Sep 17 00:00:00 2001 From: towan Date: Mon, 15 Sep 2025 21:31:53 +0400 Subject: [PATCH 15/15] WIP: new signing flow with fallback --- packages/helpers/src/modules/swapKitError.ts | 2 + packages/plugins/src/chainflip/plugin.ts | 1 - packages/plugins/src/near/plugin.ts | 10 +- packages/plugins/src/swapkit/plugin.ts | 144 +++++++++++++++--- .../toolboxes/src/cosmos/toolbox/cosmos.ts | 1 + .../toolboxes/src/cosmos/toolbox/thorchain.ts | 1 + .../src/evm/toolbox/baseEVMToolbox.ts | 1 + packages/toolboxes/src/near/toolbox.ts | 1 + packages/toolboxes/src/near/types/toolbox.ts | 1 + packages/toolboxes/src/radix/index.ts | 4 + packages/toolboxes/src/ripple/index.ts | 13 +- packages/toolboxes/src/solana/toolbox.ts | 1 + packages/toolboxes/src/substrate/substrate.ts | 1 + packages/toolboxes/src/tron/toolbox.ts | 2 + .../toolboxes/src/utxo/toolbox/bitcoinCash.ts | 1 + packages/toolboxes/src/utxo/toolbox/utxo.ts | 1 + packages/toolboxes/src/utxo/toolbox/zcash.ts | 3 +- packages/wallet-hardware/package.json | 10 +- .../src/keepkey/chains/utxo.ts | 1 - packages/wallets/package.json | 15 +- packages/wallets/src/ctrl/walletHelpers.ts | 8 +- packages/wallets/src/evm-extensions/index.ts | 2 +- 22 files changed, 154 insertions(+), 70 deletions(-) diff --git a/packages/helpers/src/modules/swapKitError.ts b/packages/helpers/src/modules/swapKitError.ts index aabbb729f4..d949f8960f 100644 --- a/packages/helpers/src/modules/swapKitError.ts +++ b/packages/helpers/src/modules/swapKitError.ts @@ -41,6 +41,8 @@ const errorCodes = { core_swap_contract_not_supported: 10206, core_swap_transaction_error: 10207, core_swap_quote_mode_not_supported: 10208, + core_swap_transfer_failed: 10209, + core_wallet_incompatible: 10210, /** * Core - Transaction */ diff --git a/packages/plugins/src/chainflip/plugin.ts b/packages/plugins/src/chainflip/plugin.ts index c1b25b3ef5..28a75ea8e1 100644 --- a/packages/plugins/src/chainflip/plugin.ts +++ b/packages/plugins/src/chainflip/plugin.ts @@ -43,7 +43,6 @@ export const ChainflipPlugin = createPlugin({ assetValue: sellAsset, isProgramDerivedAddress: true, recipient: depositAddress, - sender: wallet.address, }); return tx; diff --git a/packages/plugins/src/near/plugin.ts b/packages/plugins/src/near/plugin.ts index 4eaf3ed324..8bcd59081e 100644 --- a/packages/plugins/src/near/plugin.ts +++ b/packages/plugins/src/near/plugin.ts @@ -152,16 +152,10 @@ export const NearPlugin = createPlugin({ }, async swap(swapParams: SwapParams<"near", QuoteResponseRoute>) { const { - route: { - buyAsset: buyAssetString, - sellAsset: sellAssetString, - inboundAddress, - sellAmount, - meta: { near }, - }, + route: { buyAsset: buyAssetString, sellAsset: sellAssetString, inboundAddress, sellAmount }, } = swapParams; - if (!(sellAssetString && buyAssetString && near?.sellAsset)) { + if (!(sellAssetString && buyAssetString)) { throw new SwapKitError("core_swap_asset_not_recognized"); } diff --git a/packages/plugins/src/swapkit/plugin.ts b/packages/plugins/src/swapkit/plugin.ts index 39f22ceed8..f2ff429e9e 100644 --- a/packages/plugins/src/swapkit/plugin.ts +++ b/packages/plugins/src/swapkit/plugin.ts @@ -5,6 +5,7 @@ import { VersionedTransaction } from "@solana/web3.js"; import { AssetValue, Chain, + type CosmosChain, // type CosmosChain, CosmosChains, type CryptoChain, @@ -15,18 +16,24 @@ import { } from "@swapkit/helpers"; import { CosmosTransactionSchema, + type EVMTransaction, EVMTransactionSchema, NEARTransactionSchema, type QuoteResponseRoute, SwapKitApi, TronTransactionSchema, } from "@swapkit/helpers/api"; +import type { FullWallet } from "@swapkit/toolboxes"; import { Psbt } from "bitcoinjs-lib"; import { match } from "ts-pattern"; import type { SwapKitPluginParams } from "../types"; import { createPlugin } from "../utils"; import type { SwapKitQuoteParams, SwapKitSwapParams } from "./types"; +export function walletHasWorkingSigner(wallet: FullWallet[CryptoChain]): boolean { + return !!wallet?.signer; +} + export const SwapKitPlugin = createPlugin({ methods: (params: SwapKitPluginParams) => { const { getWallet } = params; @@ -72,91 +79,182 @@ export const SwapKitPlugin = createPlugin({ } = swapParams; // Determine the source chain from the sell asset - const sellAsset = AssetValue.from({ asset: route.sellAsset }); + const sellAsset = await AssetValue.from({ asset: route.sellAsset, asyncTokenLookup: true }); const chain = sellAsset.chain; try { + if (!walletHasWorkingSigner(getWallet(chain as CryptoChain))) { + match(chain as CryptoChain) + .returnType>() + .with(...EVMChains, async () => { + const wallet = await getWallet(chain as EVMChain); + if (!wallet) { + throw new SwapKitError("core_wallet_connection_not_found", { chain }); + } + const { from, to, data, value } = tx as EVMTransaction; + return await wallet.sendTransaction({ data, from, to, value: BigInt(value || "0") }); + }) + .with(Chain.Radix, async () => { + const wallet = await getWallet(chain as Chain.Radix); + if (!wallet) { + throw new SwapKitError("core_wallet_connection_not_found", { chain }); + } + return wallet.signAndBroadcast({ manifest: tx as string }); + }) + .otherwise(async () => { + const { targetAddress, sellAmount, memo } = route; + + if (!targetAddress) { + throw new SwapKitError("core_swap_invalid_params", { missing: ["targetAddress"] }); + } + + const assetValue = await AssetValue.from({ + asset: route.sellAsset, + asyncTokenLookup: true, + value: sellAmount, + }); + + const wallet = await getWallet(chain as CryptoChain); + + if (!wallet) { + throw new SwapKitError("core_wallet_connection_not_found", { chain }); + } + + const txHash = await wallet.transfer({ + assetValue, + isProgramDerivedAddress: true, + memo, + recipient: targetAddress, + }); + + return txHash as string; + }); + } + + // Try to use signer-based transaction execution first return await match(chain as CryptoChain) .returnType>() .with(Chain.BitcoinCash, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - return await getWallet(chain as Chain.BitcoinCash).signAndBroadcastTransaction( - bitgo.UtxoPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.bitcoincash }), - ); + const wallet = await getWallet(chain as Chain.BitcoinCash); + + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction( + bitgo.UtxoPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.bitcoincash }), + ); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Zcash, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - return await getWallet(chain as Chain.Zcash).signAndBroadcastTransaction( - bitgo.ZcashPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.zcash }) as ZcashPsbt, - ); + const wallet = await getWallet(chain as Chain.Zcash); + + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction( + bitgo.ZcashPsbt.fromBuffer(Buffer.from(tx, "base64"), { network: networks.zcash }) as ZcashPsbt, + ); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Bitcoin, Chain.Dogecoin, Chain.Litecoin, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - const psbt = Psbt.fromBase64(tx); - return await getWallet( - chain as Chain.Bitcoin | Chain.Dogecoin | Chain.Litecoin, - ).signAndBroadcastTransaction(psbt); + const wallet = await getWallet(chain as Chain.Bitcoin | Chain.Dogecoin | Chain.Litecoin | Chain.Dash); + + if (walletHasWorkingSigner(wallet)) { + const psbt = Psbt.fromBase64(tx); + return wallet.signAndBroadcastTransaction(psbt); + } + throw new Error("No signer available in wallet"); }) .with(...EVMChains, async () => { const transaction = EVMTransactionSchema.safeParse(tx); if (!transaction.success) { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } + const wallet = await getWallet(chain as EVMChain); - return await getWallet(chain as EVMChain).signAndBroadcastTransaction(transaction.data); + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction(transaction.data); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Solana, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); - return await getWallet(chain as Chain.Solana).signAndBroadcastTransaction(transaction); + const wallet = await getWallet(chain as Chain.Solana); + + if (walletHasWorkingSigner(wallet)) { + const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); + return wallet.signAndBroadcastTransaction(transaction); + } + throw new Error("No signer available in wallet"); }) - .with(...CosmosChains, () => { + .with(...CosmosChains, async () => { const transaction = CosmosTransactionSchema.safeParse(tx); if (!transaction.success) { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } - return Promise.resolve(""); - // return await getWallet(chain as CosmosChain).transfer({transaction.data}); + const wallet = await getWallet(chain as CosmosChain); + + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction(transaction.data); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Near, async () => { const transaction = NEARTransactionSchema.safeParse(tx); if (!transaction.success) { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } + const wallet = await getWallet(chain as Chain.Near); - return await getWallet(chain as Chain.Near).signAndBroadcastTransaction( - Transaction.decode(Buffer.from(transaction.data.serialized, "base64")), - ); + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction( + Transaction.decode(Buffer.from(transaction.data.serialized, "base64")), + ); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Radix, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } + const wallet = await getWallet(chain as Chain.Radix); - return await getWallet(chain as Chain.Radix).signAndBroadcast({ manifest: tx }); + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcast({ manifest: tx }); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Ripple, async () => { if (typeof tx !== "string") { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } + const wallet = await getWallet(chain as Chain.Ripple); - return await getWallet(chain as Chain.Ripple).signAndBroadcastTransaction(JSON.parse(tx)); + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction(JSON.parse(tx)); + } + throw new Error("No signer available in wallet"); }) .with(Chain.Tron, async () => { const transaction = TronTransactionSchema.safeParse(tx); if (!transaction.success) { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { chain, tx }); } + const wallet = await getWallet(chain as Chain.Tron); - return await getWallet(chain as Chain.Tron).signAndBroadcastTransaction(transaction.data); + if (walletHasWorkingSigner(wallet)) { + return wallet.signAndBroadcastTransaction(transaction.data); + } + throw new Error("No signer available in wallet"); }) .otherwise(() => { throw new SwapKitError("plugin_swapkit_invalid_tx_data", { diff --git a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts index 0b68c97b5a..646b3799ce 100644 --- a/packages/toolboxes/src/cosmos/toolbox/cosmos.ts +++ b/packages/toolboxes/src/cosmos/toolbox/cosmos.ts @@ -256,6 +256,7 @@ export async function createCosmosToolbox({ chain, ...toolboxParams }: CosmosToo return DirectSecp256k1Wallet.fromKey(privateKey, chainPrefix); }, signAndBroadcastTransaction, + signer, signTransaction, transfer, validateAddress: getCosmosValidateAddress(chainPrefix), diff --git a/packages/toolboxes/src/cosmos/toolbox/thorchain.ts b/packages/toolboxes/src/cosmos/toolbox/thorchain.ts index b6d922f8d2..4941f5cc3e 100644 --- a/packages/toolboxes/src/cosmos/toolbox/thorchain.ts +++ b/packages/toolboxes/src/cosmos/toolbox/thorchain.ts @@ -244,6 +244,7 @@ export async function createThorchainToolbox({ derivationPath: derivationPathToString(derivationPath), prefix: chainPrefix, }), + signer, signMultisigTx: signMultisigTx(chain), signWithPrivateKey, transfer, diff --git a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts index 64af93be92..b2d6947af1 100644 --- a/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts +++ b/packages/toolboxes/src/evm/toolbox/baseEVMToolbox.ts @@ -121,6 +121,7 @@ export function BaseEVMToolbox< isApproved: getIsApproved({ chain, provider }), sendTransaction: getSendTransaction({ chain, isEIP1559Compatible, provider, signer }), signAndBroadcastTransaction: getSignAndBroadcastTransaction({ chain, provider, signer }), + signer, signMessage: signer?.signMessage, signTransaction: getSignTransaction({ signer }), transfer: getTransfer({ chain, isEIP1559Compatible, provider, signer }), diff --git a/packages/toolboxes/src/near/toolbox.ts b/packages/toolboxes/src/near/toolbox.ts index ec696b9626..7dce972915 100644 --- a/packages/toolboxes/src/near/toolbox.ts +++ b/packages/toolboxes/src/near/toolbox.ts @@ -477,6 +477,7 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams): Promise nep141, provider, signAndBroadcastTransaction, + signer, signMessage, signTransaction, transfer, diff --git a/packages/toolboxes/src/near/types/toolbox.ts b/packages/toolboxes/src/near/types/toolbox.ts index 5f50e942a0..9a39a66e78 100644 --- a/packages/toolboxes/src/near/types/toolbox.ts +++ b/packages/toolboxes/src/near/types/toolbox.ts @@ -47,6 +47,7 @@ export interface NearToolbox { broadcastTransaction: (signedTransaction: SignedTransaction) => Promise; signTransaction: (transaction: Transaction) => Promise; signAndBroadcastTransaction: (transaction: Transaction) => Promise; + signer: NearSigner | undefined; signMessage: (message: string) => Promise; getBalance: (address: string) => Promise; validateAddress: (address: string) => boolean; diff --git a/packages/toolboxes/src/radix/index.ts b/packages/toolboxes/src/radix/index.ts index 78ac0ff13a..f294317713 100644 --- a/packages/toolboxes/src/radix/index.ts +++ b/packages/toolboxes/src/radix/index.ts @@ -127,6 +127,10 @@ export const RadixToolbox = async ({ dappConfig }: { dappConfig?: SKConfigIntegr signAndBroadcast: (() => { throw new SwapKitError("toolbox_radix_method_not_supported", { method: "signAndBroadcast" }); }) as (params: any) => Promise, + signer: undefined, + transfer: (() => { + throw new SwapKitError("toolbox_radix_method_not_supported", { method: "transfer" }); + }) as (params: any) => Promise, validateAddress: radixValidateAddress, }; }; diff --git a/packages/toolboxes/src/ripple/index.ts b/packages/toolboxes/src/ripple/index.ts index 4c03658512..2761394efd 100644 --- a/packages/toolboxes/src/ripple/index.ts +++ b/packages/toolboxes/src/ripple/index.ts @@ -21,11 +21,8 @@ const RIPPLE_ERROR_CODES = { ACCOUNT_NOT_FOUND: 19 } as const; function createSigner(phrase: string): ChainSigner { const wallet = Wallet.fromMnemonic(phrase); return { - // publicKey: wallet.publicKey, - // Address is sync, but interface requires async getAddress: () => Promise.resolve(wallet.address), - // Signing is sync, but interface requires async - signTransaction: (tx: Transaction) => Promise.resolve(wallet.sign(tx as Transaction)), // Cast needed as Wallet.sign expects Transaction + signTransaction: (tx: Transaction) => Promise.resolve(wallet.sign(tx as Transaction)), }; } @@ -151,10 +148,8 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { } try { - // Sign the transaction const signedTx = await signTransaction(tx); - // Broadcast the signed transaction return await broadcastTransaction(signedTx.tx_blob); } catch (error) { throw new SwapKitError({ errorKey: "toolbox_ripple_broadcast_error", info: { chain: Chain.Ripple, error } }); @@ -175,16 +170,14 @@ export const getRippleToolbox = async (params: RippleToolboxParams = {}) => { return { broadcastTransaction, - createSigner, // Expose the helper + createSigner, createTransaction, disconnect, estimateTransactionFee, - // Core methods getAddress, getBalance, signAndBroadcastTransaction, - // Signer related - signer, // Expose the signer instance if created/provided + signer, signTransaction, transfer, validateAddress: rippleValidateAddress, diff --git a/packages/toolboxes/src/solana/toolbox.ts b/packages/toolboxes/src/solana/toolbox.ts index 82efe7eb39..3445f68998 100644 --- a/packages/toolboxes/src/solana/toolbox.ts +++ b/packages/toolboxes/src/solana/toolbox.ts @@ -134,6 +134,7 @@ export async function getSolanaToolbox( const signedTx = await signTransaction(getConnection, signer)(transaction); return broadcastTransaction(getConnection)(signedTx); }, + signer, signTransaction: signTransaction(getConnection, signer), transfer: transfer(getConnection, signer), }; diff --git a/packages/toolboxes/src/substrate/substrate.ts b/packages/toolboxes/src/substrate/substrate.ts index 56520c3d82..59b00a31aa 100644 --- a/packages/toolboxes/src/substrate/substrate.ts +++ b/packages/toolboxes/src/substrate/substrate.ts @@ -240,6 +240,7 @@ export const BaseSubstrateToolbox = ({ */ signAndBroadcast: signAndBroadcastTransaction, signAndBroadcastTransaction, + signer, signTransaction: (tx: SubmittableExtrinsic<"promise">) => { if (!signer || !isKeyringPair(signer)) throw new SwapKitError("toolbox_substrate_no_signer"); return sign(signer, tx); diff --git a/packages/toolboxes/src/tron/toolbox.ts b/packages/toolboxes/src/tron/toolbox.ts index ac1c6a9673..ff9316b6f0 100644 --- a/packages/toolboxes/src/tron/toolbox.ts +++ b/packages/toolboxes/src/tron/toolbox.ts @@ -117,6 +117,7 @@ export const createTronToolbox = async ( 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; @@ -607,6 +608,7 @@ export const createTronToolbox = async ( 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 e800abe8d0..3735484656 100644 --- a/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts +++ b/packages/toolboxes/src/utxo/toolbox/bitcoinCash.ts @@ -93,6 +93,7 @@ export async function createBCHToolbox( getBalance: handleGetBalance, getFeeRates, signAndBroadcastTransaction, + signer, signTransaction, stripPrefix, stripToCashAddress, diff --git a/packages/toolboxes/src/utxo/toolbox/utxo.ts b/packages/toolboxes/src/utxo/toolbox/utxo.ts index 8e75762881..95ae24e576 100644 --- a/packages/toolboxes/src/utxo/toolbox/utxo.ts +++ b/packages/toolboxes/src/utxo/toolbox/utxo.ts @@ -242,6 +242,7 @@ export async function createUTXOToolbox({ 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 }), diff --git a/packages/toolboxes/src/utxo/toolbox/zcash.ts b/packages/toolboxes/src/utxo/toolbox/zcash.ts index 1b983b68e3..d9ed634686 100644 --- a/packages/toolboxes/src/utxo/toolbox/zcash.ts +++ b/packages/toolboxes/src/utxo/toolbox/zcash.ts @@ -258,8 +258,7 @@ export async function createZcashToolbox( createTransaction, getPrivateKeyFromMnemonic, signAndBroadcastTransaction: getSignAndBroadcastTransaction(signer), - - // New unified signing methods for Zcash + signer, signTransaction: getSignTransaction(signer), transfer, validateAddress: validateZcashAddress, diff --git a/packages/wallet-hardware/package.json b/packages/wallet-hardware/package.json index 51068842ef..e0d33ac436 100644 --- a/packages/wallet-hardware/package.json +++ b/packages/wallet-hardware/package.json @@ -38,11 +38,7 @@ "ts-pattern": "5.8.0" }, "exports": { - ".": { - "import": "./dist/index.js", - "require": "./dist/index.cjs", - "types": "./dist/types/index.d.ts" - }, + ".": { "import": "./dist/index.js", "require": "./dist/index.cjs", "types": "./dist/types/index.d.ts" }, "./keepkey": { "import": "./dist/keepkey/index.js", "require": "./dist/keepkey/index.cjs", @@ -59,9 +55,7 @@ "types": "./dist/types/trezor/index.d.ts" } }, - "files": [ - "dist/" - ], + "files": ["dist/"], "homepage": "https://github.com/swapkit/SwapKit", "license": "Apache-2.0", "name": "@swapkit/wallet-hardware", diff --git a/packages/wallet-hardware/src/keepkey/chains/utxo.ts b/packages/wallet-hardware/src/keepkey/chains/utxo.ts index bdb29a9b52..aaf470b258 100644 --- a/packages/wallet-hardware/src/keepkey/chains/utxo.ts +++ b/packages/wallet-hardware/src/keepkey/chains/utxo.ts @@ -5,7 +5,6 @@ import { DerivationPath, type DerivationPathArray, derivationPathToString, - SKConfig, SwapKitError, type UTXOChain, } from "@swapkit/helpers"; diff --git a/packages/wallets/package.json b/packages/wallets/package.json index 0a90a2210a..f9dc852733 100644 --- a/packages/wallets/package.json +++ b/packages/wallets/package.json @@ -56,11 +56,7 @@ "xumm": "1.8.0" }, "exports": { - ".": { - "default": "./dist/src/index.js", - "require": "./dist/src/index.cjs", - "types": "./dist/types/index.d.ts" - }, + ".": { "default": "./dist/src/index.js", "require": "./dist/src/index.cjs", "types": "./dist/types/index.d.ts" }, "./bitget": { "default": "./dist/src/bitget/index.js", "require": "./dist/src/bitget/index.cjs", @@ -172,16 +168,11 @@ "types": "./dist/types/xaman/index.d.ts" } }, - "files": [ - "dist/" - ], + "files": ["dist/"], "homepage": "https://github.com/swapkit/SwapKit", "license": "Apache-2.0", "name": "@swapkit/wallets", - "repository": { - "type": "git", - "url": "git+https://github.com/swapkit/SwapKit.git" - }, + "repository": { "type": "git", "url": "git+https://github.com/swapkit/SwapKit.git" }, "scripts": { "build": "bun run ./build.ts", "build:clean": "rm -rf dist && bun run ./build.ts", diff --git a/packages/wallets/src/ctrl/walletHelpers.ts b/packages/wallets/src/ctrl/walletHelpers.ts index ab8a0ab21f..c86a98aa7a 100644 --- a/packages/wallets/src/ctrl/walletHelpers.ts +++ b/packages/wallets/src/ctrl/walletHelpers.ts @@ -210,19 +210,19 @@ export function thorchainTransactionToCtrlParams({ const msg = transaction.msgs[0] as APICosmosEncodedObject; if (!msg) { - throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "msgs" }); + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { key: "msgs", transaction }); } if (msg.typeUrl === "/types.MsgSend") { const typedMessage = msg as any as CosmosSendMsg; if (!typedMessage.value.toAddress) { - throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "toAddress" }); + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { key: "toAddress", transaction }); } const recipient = base64ToBech32(typedMessage.value.toAddress, chain.toLowerCase()); const amount = typedMessage.value.amount[0]; if (!amount) { - throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "amount" }); + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { key: "amount", transaction }); } const denom = amount.denom; const identifier = `${chain}.${denom.toUpperCase()}`; @@ -242,7 +242,7 @@ export function thorchainTransactionToCtrlParams({ const coin = typedMessage.value.coins[0]; if (!coin) { - throw new SwapKitError("wallet_ctrl_transaction_missing_data", { transaction, key: "coins" }); + throw new SwapKitError("wallet_ctrl_transaction_missing_data", { key: "coins", transaction }); } const asset = coin.asset; diff --git a/packages/wallets/src/evm-extensions/index.ts b/packages/wallets/src/evm-extensions/index.ts index 309b0343ad..238edbe9d6 100644 --- a/packages/wallets/src/evm-extensions/index.ts +++ b/packages/wallets/src/evm-extensions/index.ts @@ -103,7 +103,7 @@ export const evmWallet = createWallet({ }); const disconnect = () => browserProvider.send("wallet_revokePermissions", [{ eth_accounts: {} }]); - addChain({ ...walletMethods, address, chain, walletType, disconnect }); + addChain({ ...walletMethods, address, chain, disconnect, walletType }); return; }), );