diff --git a/.changeset/swapkit-sdk-15fcrq2iqxhgc.md b/.changeset/swapkit-sdk-15fcrq2iqxhgc.md new file mode 100644 index 0000000..997a2aa --- /dev/null +++ b/.changeset/swapkit-sdk-15fcrq2iqxhgc.md @@ -0,0 +1,11 @@ +--- +"@swapkit/sdk": patch +"@swapkit/wallet-extensions": patch +"@swapkit/wallet-hardware": patch +"@swapkit/wallet-mobile": patch +"@swapkit/wallets": patch +--- + +Update SwapKit SDK dependencies: + +- @swapkit/helpers: 4.20.1 → 0.0.0-rc-20260728115044 diff --git a/.changeset/ton-connect-wallet.md b/.changeset/ton-connect-wallet.md new file mode 100644 index 0000000..1a3277e --- /dev/null +++ b/.changeset/ton-connect-wallet.md @@ -0,0 +1,13 @@ +--- +"@swapkit/wallets": minor +--- + +Add TON Connect as a wallet option (`@swapkit/wallets/tonconnect`). + +TON Connect is the standard wallet connection protocol for the TON blockchain and supports only `Chain.Ton`, including its native token and Jettons. The integration wraps `@tonconnect/ui` for the connect modal, universal links, and bridge sessions, and delegates message building, balances, and fee estimation to the signerless `@swapkit/toolboxes/ton` toolbox. Signing and broadcasting are routed through the connected wallet's `sendTransaction`. + +Notes: + +- Requires a publicly hosted `tonconnect-manifest.json` (`manifestUrl`), or an existing `TonConnectUI` instance can be injected. +- Sweep transfers (`CARRY_ALL_REMAINING_BALANCE`) are rejected: the TON Connect protocol does not carry a send mode, the wallet chooses it. +- Detached signing (`sign`) is not supported by the protocol; use `signAndBroadcastTransaction` / `transfer`. diff --git a/bun.lock b/bun.lock index de3fe6b..147b356 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,7 @@ "@cosmjs/stargate": "0.37.0", "@scure/bip32": "2.2.0", "@swapkit/core": "^4.6.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/plugins": "^4.7.1", "@swapkit/server": "^4.4.2", "@swapkit/toolboxes": "^4.28.0", @@ -32,7 +32,7 @@ "dependencies": { "@stricahq/typhonjs": "~3.0.1", "@swapkit/core": "^4.6.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/plugins": "^4.7.1", "@swapkit/server": "^4.4.2", "@swapkit/toolboxes": "^4.28.0", @@ -56,7 +56,7 @@ "@near-js/transactions": "~2.5.0", "@scure/base": "~2.2.0", "@solana/web3.js": "~1.98.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", @@ -109,7 +109,7 @@ "@near-js/transactions": "~2.5.0", "@scure/base": "2.2.0", "@scure/bip32": "2.2.0", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", @@ -156,7 +156,7 @@ "name": "@swapkit/wallet-mobile", "version": "4.3.21", "dependencies": { - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/wallet-core": "^4.3.19", }, @@ -181,12 +181,14 @@ "@scure/base": "~2.2.0", "@scure/bip39": "~2.2.0", "@solana/web3.js": "~1.98.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", "@swapkit/wallet-extensions": "workspace:*", "@swapkit/wallet-hardware": "workspace:*", + "@ton/core": "~0.63.1", + "@tonconnect/ui": "~3.0.0", "@walletconnect/modal": "~2.7.0", "@walletconnect/sign-client": "~2.23.9", "cosmjs-types": "~0.10.1", @@ -210,6 +212,8 @@ "@radixdlt/radix-dapp-toolkit": "2.3.0", "@scure/base": "2.2.0", "@solana/web3.js": "1.98.4", + "@ton/core": "0.63.1", + "@tonconnect/ui": "3.0.0", "@walletconnect/logger": "3.0.2", "@walletconnect/modal": "2.7.0", "@walletconnect/sign-client": "2.23.9", @@ -265,6 +269,7 @@ }, }, "overrides": { + "@swapkit/helpers": "0.0.0-rc-20260728115044", "nuqs": "2.8.9", }, "packages": { @@ -1124,11 +1129,11 @@ "@substrate/ss58-registry": ["@substrate/ss58-registry@1.51.0", "", {}, "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ=="], - "@swapkit/contracts": ["@swapkit/contracts@4.1.5", "", {}, "sha512-0OZ+A4d68XIoqXWxZL10ahF+MKuT2J215oeeJZB1bAk5mu9oBfl1qeBl6IWbp1RnKMcpecm5K2hTGlCsKpR+TQ=="], + "@swapkit/contracts": ["@swapkit/contracts@0.0.0-rc-20260728115044", "", {}, "sha512-eIYzks9P5s/JDPJe8x4UVNthi3JA8wLLMkzA1s4gwVgXgkOw0/Ku7jg6P2LlIs8yhkECp4tbE1W/UJIjXiMu3Q=="], "@swapkit/core": ["@swapkit/core@4.6.4", "", { "dependencies": { "@swapkit/helpers": "4.20.1", "@swapkit/plugins": "4.7.4", "@swapkit/toolboxes": "4.28.0", "@swapkit/wallet-core": "4.3.21" }, "peerDependencies": { "@stricahq/typhonjs": "3.1.0", "@ton/core": "0.63.1", "cosmjs-types": "0.11.0", "ts-pattern": "5.9.0" } }, "sha512-0nwvzI7dOy9uieMXcJzTGe6DuQRurmj6DKV4+A4nYT2Me0OOFnfgxSJ+O2Z3qa4Z/KyO47mfOuHKmSXLKEwlLg=="], - "@swapkit/helpers": ["@swapkit/helpers@4.20.1", "", { "dependencies": { "@swapkit/contracts": "4.1.5", "@swapkit/tokens": "4.4.1", "@swapkit/types": "0.12.0" }, "peerDependencies": { "@near-js/providers": "2.5.1", "ethers": "6.16.0", "ts-pattern": "5.9.0", "zod": "4.4.3", "zustand": "5.0.13" } }, "sha512-7AujBntn3HfCOR/wW8q2qDaYQnrdzoHS4HeGbmE9+WZ7nXAWhQ24cUWAouBLtR2Cjh4Uw8NLiYtPTSV347wcNw=="], + "@swapkit/helpers": ["@swapkit/helpers@0.0.0-rc-20260728115044", "", { "dependencies": { "@noble/hashes": "2.2.0", "@swapkit/contracts": "0.0.0-rc-20260728115044", "@swapkit/tokens": "0.0.0-rc-20260728115044", "@swapkit/types": "0.0.0-rc-20260728115044" }, "peerDependencies": { "ts-pattern": "5.9.0", "zod": "4.4.3", "zustand": "5.0.13" } }, "sha512-UzFyBqBUAUB22Z4tWVEOQxdHhnYT/qgOiEGHeiQPswrCEfkuYiWT4WFMvklUsXYM0cZjZVaD2JuomiI86LmMqg=="], "@swapkit/plugins": ["@swapkit/plugins@4.7.4", "", { "dependencies": { "@swapkit/helpers": "4.20.1", "@swapkit/toolboxes": "4.28.0", "@swapkit/utxo-signer": "2.3.0" }, "peerDependencies": { "@mysten/sui": "2.17.0", "@near-js/transactions": "2.5.1", "@near-js/utils": "2.5.1", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0", "@solana/web3.js": "1.98.4", "@stellar/stellar-sdk": "16.1.0", "ethers": "6.16.0", "ts-pattern": "5.9.0" } }, "sha512-dEBM8nrwLb99teLr2XNh8QNgQHGYjEzalHFV5Ldc5XWHU97rn5E9dKfRCEPLAv2RaosbDR6fGnsXbhmgn4t/Gw=="], @@ -1136,11 +1141,11 @@ "@swapkit/server": ["@swapkit/server@4.4.5", "", { "dependencies": { "@swapkit/helpers": "4.20.1", "@swapkit/tokens": "4.4.1", "@swapkit/toolboxes": "4.28.0", "@swapkit/wallet-keystore": "4.5.5" }, "peerDependencies": { "ts-pattern": "5.9.0" } }, "sha512-9BRvLYMv8h3Z447HYuJAzV2/fyrMgBhl3KrNzuwWMFwKf88YJP+NuvDv3qoCyFDOf08S8jfS4ea34Ku+xkssFQ=="], - "@swapkit/tokens": ["@swapkit/tokens@4.4.1", "", { "peerDependencies": { "ts-pattern": "5.9.0" } }, "sha512-PVHduHv3RTc4aY9pHDvge9t0rrChJA5xGkoYd34E/PO9t/3gWRb4ABHVmakLxtLJnh5hb/McSha5y1HGzvzVEQ=="], + "@swapkit/tokens": ["@swapkit/tokens@0.0.0-rc-20260728115044", "", { "peerDependencies": { "ts-pattern": "5.9.0" } }, "sha512-tGfoV8OU+h0MSxrvvVjVrREGkitDiYqC76Zv6AAiIsNjU+hbL9Lf4rEcPMNMNgHHFlXamXy0TqvOUMbn/nbbhg=="], "@swapkit/toolboxes": ["@swapkit/toolboxes@4.28.0", "", { "dependencies": { "@swapkit/helpers": "4.20.1", "@swapkit/utxo-signer": "2.3.0" }, "peerDependencies": { "@aptos-labs/ts-sdk": "7.0.1", "@cosmjs/amino": "0.39.0", "@cosmjs/crypto": "0.39.0", "@cosmjs/proto-signing": "0.39.0", "@cosmjs/stargate": "0.39.0", "@mysten/sui": "2.17.0", "@near-js/accounts": "2.5.1", "@near-js/crypto": "2.5.1", "@near-js/providers": "2.5.1", "@near-js/signers": "2.5.1", "@near-js/transactions": "2.5.1", "@near-js/utils": "2.5.1", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@orbs-network/ton-access": "2.3.3", "@polkadot/api": "16.5.6", "@polkadot/keyring": "14.0.3", "@polkadot/rpc-provider": "16.5.6", "@polkadot/util": "14.0.3", "@polkadot/util-crypto": "14.0.3", "@provablehq/sdk": "0.11.3", "@radixdlt/babylon-gateway-api-sdk": "1.10.1", "@radixdlt/radix-dapp-toolkit": "2.3.0", "@scure/base": "2.2.0", "@scure/bip32": "2.2.0", "@scure/bip39": "2.2.0", "@solana/spl-memo": "0.2.5", "@solana/spl-token": "0.4.14", "@solana/web3.js": "1.98.4", "@stellar/stellar-sdk": "16.1.0", "@stricahq/bip32ed25519": "1.1.2", "@stricahq/cbors": "1.0.4", "@stricahq/typhonjs": "3.1.0", "@ton/core": "0.63.1", "@ton/crypto": "3.3.0", "@ton/ton": "16.2.4", "bignumber.js": "9.3.1", "cosmjs-types": "0.11.0", "ethers": "6.16.0", "micro-key-producer": "0.8.6", "near-seed-phrase": "0.2.1", "protobufjs": "8.0.3", "ripple-address-codec": "5.0.1", "starknet": "10.0.2", "ts-pattern": "5.9.0", "xrpl": "4.6.0" } }, "sha512-ximfqTV82dmcc+knKmnB1hLqVe3JHvwrcqhyNh7ysrLMkfRxnA8TpFr6yR/+fjD7zZWVekG+1uKuebDsRbd5Ww=="], - "@swapkit/types": ["@swapkit/types@0.12.0", "", {}, "sha512-x3doLExMh372PQYKngJFC1lzr7FSW99g5TfUBSDsZf43pOnkhNaFR0kmXDU/BdcD/z6UMJzm9i71G4NcVJ1P+g=="], + "@swapkit/types": ["@swapkit/types@0.0.0-rc-20260728115044", "", {}, "sha512-yfH45QlF1btfyKFQx3Rlf3DjXODM4exqXEStMiE/Lsqe13iu8P2mgaxqZJHKU51Nwn5R0eM5MuCgjYNDLo24Uw=="], "@swapkit/ui": ["@swapkit/ui@0.21.1", "", { "dependencies": { "@aptos-labs/ts-sdk": "1.34.0", "@cosmjs/amino": "0.39.0", "@cosmjs/crypto": "0.39.0", "@cosmjs/proto-signing": "0.39.0", "@cosmjs/stargate": "0.39.0", "@hookform/resolvers": "5.2.2", "@mysten/sui": "1.44.0", "@near-js/accounts": "2.5.1", "@near-js/crypto": "2.5.1", "@near-js/providers": "2.5.1", "@near-js/signers": "2.5.1", "@near-js/transactions": "2.5.1", "@near-js/utils": "2.5.1", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@orbs-network/ton-access": "2.3.3", "@polkadot/api": "15.10.2", "@polkadot/keyring": "13.5.7", "@polkadot/rpc-provider": "15.10.2", "@polkadot/util": "13.5.7", "@polkadot/util-crypto": "13.5.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.8", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.8", "@radix-ui/react-slot": "1.2.4", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-tooltip": "1.2.8", "@radixdlt/babylon-gateway-api-sdk": "1.10.1", "@radixdlt/radix-dapp-toolkit": "2.3.0", "@scure/base": "2.2.0", "@scure/bip32": "2.2.0", "@scure/bip39": "2.2.0", "@sentry/react": "9.27.0", "@solana/spl-memo": "0.2.5", "@solana/spl-token": "0.4.14", "@solana/web3.js": "1.98.4", "@stellar/stellar-sdk": "15.1.0", "@stricahq/bip32ed25519": "1.1.2", "@stricahq/cbors": "1.0.4", "@stricahq/typhonjs": "3.1.0", "@swapkit/core": "^4.4.38", "@swapkit/helpers": "^4.15.0", "@swapkit/plugins": "^4.6.52", "@swapkit/toolboxes": "^4.17.6", "@swapkit/wallet-keystore": "^4.4.6", "@swapkit/wallets": "^4.8.25", "@ton/core": "0.63.1", "@ton/crypto": "3.3.0", "@ton/ton": "16.2.4", "bignumber.js": "9.3.1", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cosmjs-types": "0.11.0", "crypto-browserify": "3.12.1", "ethers": "6.16.0", "highlight.js": "^11.11.1", "lucide-react": "0.552.0", "micro-key-producer": "0.8.6", "micro-packed": "0.9.0", "near-seed-phrase": "0.2.1", "nuqs": "2.8.9", "protobufjs": "8.0.3", "react": "19.1.1", "react-dom": "19.1.1", "react-hook-form": "7.65.0", "sonner": "2.0.7", "starknet": "10.0.2", "tailwind-merge": "2.6.0", "tailwindcss": "3.4.18", "tailwindcss-animate": "1.0.7", "ts-pattern": "5.9.0", "xrpl": "4.6.0", "zod": "4.4.3", "zustand": "5.0.13" } }, "sha512-vwwdbcU14PluIMF/v9QCjeMBPgLe1gFnSOuHjB8NOIUW1rqLJ5BnO2LzHX8TOWSmUMaBMHZ7I638rTn+3d0dVQ=="], @@ -1170,6 +1175,16 @@ "@ton/ton": ["@ton/ton@16.2.4", "", { "dependencies": { "axios": "1.15.0", "dataloader": "^2.0.0", "zod": "^3.21.4" }, "peerDependencies": { "@ton/core": ">=0.63.0 <1.0.0", "@ton/crypto": ">=3.2.0" } }, "sha512-GWCNKizQm7MM99XNQGr+zWWdSfbFu/WLeQYm7tMIQDNKpAYEpLDKJUKuK/yrz763DBbnTDAFeq1NWAEy8rAOaQ=="], + "@tonconnect/isomorphic-eventsource": ["@tonconnect/isomorphic-eventsource@0.0.2", "", { "dependencies": { "eventsource": "^2.0.2" } }, "sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ=="], + + "@tonconnect/isomorphic-fetch": ["@tonconnect/isomorphic-fetch@0.0.3", "", { "dependencies": { "node-fetch": "^2.6.9" } }, "sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w=="], + + "@tonconnect/protocol": ["@tonconnect/protocol@3.0.0", "", { "dependencies": { "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1" } }, "sha512-YqwllY12+rXsAZgKD5mlRBb6wXEbe9jKXMCFOV387yCKQDMnwXK1fbgKWrroPp9fPDtQHXhilZQNnuAKS/iR/w=="], + + "@tonconnect/sdk": ["@tonconnect/sdk@4.0.0", "", { "dependencies": { "@tonconnect/isomorphic-eventsource": "0.0.2", "@tonconnect/isomorphic-fetch": "0.0.3", "@tonconnect/protocol": "3.0.0" } }, "sha512-2/vgrBFhIfBV2f8s11bo+VV+VwjElTi2dM2ZDP41VTXyjOmNgWxDw3vSqrS/puniPbm+4tqK6EXLfwvpBxlq5w=="], + + "@tonconnect/ui": ["@tonconnect/ui@3.0.0", "", { "dependencies": { "@tonconnect/sdk": "4.0.0", "classnames": "^2.5.1", "csstype": "^3.1.3", "deepmerge": "^4.3.1", "ua-parser-js": "^1.0.35" } }, "sha512-vt/HAyEr7bMJgff38lDw9xV7ptS8bzdjkdnwJaFjSZth2vT/qbp3J/RDxxNMaX4q0VFuHafGh8SZe1JHhHFOgQ=="], + "@trezor/analytics": ["@trezor/analytics@1.5.0", "", { "dependencies": { "@trezor/env-utils": "1.5.0", "@trezor/utils": "9.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "sha512-evILW5XJEmfPlf0TY1duOLtGJ47pdGeSKVE3P75ODEUsRNxtPVqlkOUBPmYpCxPnzS8XDmkatT8lf9/DF0G6nA=="], "@trezor/blockchain-link": ["@trezor/blockchain-link@2.6.2", "", { "dependencies": { "@solana-program/compute-budget": "^0.8.0", "@solana-program/stake": "^0.2.1", "@solana-program/token": "^0.5.1", "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", "@solana/rpc-types": "^2.3.0", "@stellar/stellar-sdk": "14.2.0", "@trezor/blockchain-link-types": "1.5.1", "@trezor/blockchain-link-utils": "1.5.2", "@trezor/env-utils": "1.5.0", "@trezor/utils": "9.5.0", "@trezor/utxo-lib": "2.5.0", "@trezor/websocket-client": "1.3.0", "@types/web": "^0.0.197", "crypto-browserify": "3.12.0", "socks-proxy-agent": "8.0.5", "stream-browserify": "^3.0.0", "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, "sha512-HMz+Dm6nMflqRkaebMqpv3uNnVkRrb6lD2HKTHNBGhjVbqf0wRzJ8JoQ08wn/sF00ljJZz8pGnpcMYHJNxE+wQ=="], @@ -1488,6 +1503,8 @@ "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], @@ -1560,6 +1577,8 @@ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], @@ -2400,6 +2419,8 @@ "tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], + "tweetnacl-util": ["tweetnacl-util@0.15.1", "", {}, "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="], + "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], @@ -2412,7 +2433,7 @@ "ua-is-frozen": ["ua-is-frozen@0.1.2", "", {}, "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw=="], - "ua-parser-js": ["ua-parser-js@2.0.9", "", { "dependencies": { "detect-europe-js": "^0.1.2", "is-standalone-pwa": "^0.1.1", "ua-is-frozen": "^0.1.2" }, "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w=="], + "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], @@ -2876,6 +2897,8 @@ "@swapkit/core/cosmjs-types": ["cosmjs-types@0.11.0", "", {}, "sha512-kDSkgHpRTrg1413jCNehT3P21+EBxZWFMBr9JEzVfmPiNdtuwAoLAkCYo7c7i/pTakAwyHsXbxOg8kkD+AN33w=="], + "@swapkit/server/@swapkit/tokens": ["@swapkit/tokens@4.4.1", "", { "peerDependencies": { "ts-pattern": "5.9.0" } }, "sha512-PVHduHv3RTc4aY9pHDvge9t0rrChJA5xGkoYd34E/PO9t/3gWRb4ABHVmakLxtLJnh5hb/McSha5y1HGzvzVEQ=="], + "@swapkit/toolboxes/@cosmjs/crypto": ["@cosmjs/crypto@0.39.0", "", { "dependencies": { "@cosmjs/encoding": "^0.39.0", "@cosmjs/math": "^0.39.0", "@cosmjs/utils": "^0.39.0", "@noble/ciphers": "^2.1.1", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@scure/bip39": "^2.0.1" } }, "sha512-ATRhSXN8w3fvUkj9xzLHwvzylDvvn4f3cC1CQwhQc2OxyzpEEFACS3wHS6iwdJJS99acV8dq+oVOYflUqI0brQ=="], "@swapkit/toolboxes/@stricahq/typhonjs": ["@stricahq/typhonjs@3.1.0", "", { "dependencies": { "@stricahq/cbors": "1.0.2", "bech32": "^2.0.0", "bignumber.js": "^9.0.1", "blakejs": "^1.2.1", "bs58": "^5.0.0", "buffer": "^6.0.3", "lodash": "^4.17.21" } }, "sha512-Eaw0qqNIz+6sA9zD/9pFCdQ+gNbCv8PKFMhXu4Vq6KSR1XsH40jNPFkrqgKaq8q+gbO569U1+tu41dHk40JNbw=="], @@ -2900,8 +2923,6 @@ "@swapkit/ui/@swapkit/core": ["@swapkit/core@4.4.38", "", { "dependencies": { "@swapkit/helpers": "4.15.0", "@swapkit/plugins": "4.6.52", "@swapkit/toolboxes": "4.17.6", "@swapkit/wallet-core": "4.3.7" }, "peerDependencies": { "@stricahq/typhonjs": "3.1.0", "cosmjs-types": "0.11.0", "ts-pattern": "5.9.0" } }, "sha512-E/+YVJ1nR7SXPlRIvocgsh7CbhlQcDeTY/0BRkaFZpevylnh4ZCyD9wHdxXduDPOIgcyjCq5PHq39iXIbs+R+w=="], - "@swapkit/ui/@swapkit/helpers": ["@swapkit/helpers@4.15.0", "", { "dependencies": { "@swapkit/contracts": "4.1.5", "@swapkit/tokens": "4.3.1", "@swapkit/types": "0.8.1" }, "peerDependencies": { "@near-js/providers": "2.5.1", "ethers": "6.16.0", "ts-pattern": "5.9.0", "zod": "4.4.3", "zustand": "5.0.13" } }, "sha512-m6FgXfGfTt8zoH0OixrTiPxW+r7Tej0eRslomCsMPwl18PgMcZVBgWIxnH3cRH2fjwk4Ormavg1fpOQ0x0mtUw=="], - "@swapkit/ui/@swapkit/plugins": ["@swapkit/plugins@4.6.52", "", { "dependencies": { "@swapkit/helpers": "4.15.0", "@swapkit/toolboxes": "4.17.6", "@swapkit/utxo-signer": "2.2.2" }, "peerDependencies": { "@mysten/sui": "1.44.0", "@near-js/transactions": "2.5.1", "@near-js/utils": "2.5.1", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0", "@solana/web3.js": "1.98.4", "ethers": "6.16.0", "ts-pattern": "5.9.0" } }, "sha512-l1A6gKSRL6Cbaqa0fdfYD37D89XBn8MP+N6FDwv/9Vhp8QM7tKBFo9EDcOXtJqbQmTOhI1J6vWUmQxjGqlrNJQ=="], "@swapkit/ui/@swapkit/toolboxes": ["@swapkit/toolboxes@4.17.6", "", { "dependencies": { "@swapkit/helpers": "4.15.0", "@swapkit/utxo-signer": "2.2.2" }, "peerDependencies": { "@aptos-labs/ts-sdk": "1.34.0", "@cosmjs/amino": "0.39.0", "@cosmjs/crypto": "0.39.0", "@cosmjs/proto-signing": "0.39.0", "@cosmjs/stargate": "0.39.0", "@mysten/sui": "1.44.0", "@near-js/accounts": "2.5.1", "@near-js/crypto": "2.5.1", "@near-js/providers": "2.5.1", "@near-js/signers": "2.5.1", "@near-js/transactions": "2.5.1", "@near-js/utils": "2.5.1", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@orbs-network/ton-access": "2.3.3", "@polkadot/api": "15.10.2", "@polkadot/keyring": "13.5.7", "@polkadot/rpc-provider": "15.10.2", "@polkadot/util": "13.5.7", "@polkadot/util-crypto": "13.5.7", "@radixdlt/babylon-gateway-api-sdk": "1.10.1", "@radixdlt/radix-dapp-toolkit": "2.3.0", "@scure/base": "2.2.0", "@scure/bip32": "2.2.0", "@scure/bip39": "2.2.0", "@solana/spl-memo": "0.2.5", "@solana/spl-token": "0.4.14", "@solana/web3.js": "1.98.4", "@stellar/stellar-sdk": "15.1.0", "@stricahq/bip32ed25519": "1.1.2", "@stricahq/cbors": "1.0.4", "@stricahq/typhonjs": "3.1.0", "@ton/core": "0.63.1", "@ton/crypto": "3.3.0", "@ton/ton": "16.2.4", "bignumber.js": "9.3.1", "cosmjs-types": "0.11.0", "ethers": "6.16.0", "micro-key-producer": "0.8.6", "near-seed-phrase": "0.2.1", "protobufjs": "8.0.3", "starknet": "10.0.2", "ts-pattern": "5.9.0", "xrpl": "4.6.0" } }, "sha512-dSXT9bYuHvmYpPeFPaJBJt7O+XrIw1A2dxomyf/loMngwLrwStQprnt1g6M7kI6zCSiXTL6dAh9wHwOxROFCiw=="], @@ -2958,6 +2979,8 @@ "@trezor/device-authenticity/@trezor/protobuf": ["@trezor/protobuf@1.5.2", "", { "dependencies": { "@trezor/schema-utils": "1.4.0", "long": "5.2.5", "protobufjs": "7.4.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw=="], + "@trezor/env-utils/ua-parser-js": ["ua-parser-js@2.0.9", "", { "dependencies": { "detect-europe-js": "^0.1.2", "is-standalone-pwa": "^0.1.1", "ua-is-frozen": "^0.1.2" }, "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w=="], + "@trezor/protobuf/long": ["long@5.2.5", "", {}, "sha512-e0r9YBBgNCq1D1o5Dp8FMH0N5hsFtXDBiVa0qoJPHpakvZkmDKPRoGffZJII/XsHvj9An9blm+cRJ01yQqU+Dw=="], "@trezor/protobuf/protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], @@ -3436,10 +3459,6 @@ "@swapkit/ui/@swapkit/core/@swapkit/wallet-core": ["@swapkit/wallet-core@4.3.7", "", { "dependencies": { "@swapkit/helpers": "4.15.0" } }, "sha512-S5gTczdChHoaYQKGmJIKSusdI8dtXtYkZ/b6KYSEC8tmgI1P4hsv1yk0vOswzASJC8iNG0XJNq1uQ10l7ID4TQ=="], - "@swapkit/ui/@swapkit/helpers/@swapkit/tokens": ["@swapkit/tokens@4.3.1", "", { "peerDependencies": { "ts-pattern": "5.9.0" } }, "sha512-q8eBkQxypgG0GNwIBTimbqEg943IO9bo2CAoYk2i4CL68L1wX30yLtT1iHsa+sBsfBfTc5LyKsrJb8e452zqQQ=="], - - "@swapkit/ui/@swapkit/helpers/@swapkit/types": ["@swapkit/types@0.8.1", "", {}, "sha512-fBZ9rhDOs31iq/Ks6BHvIbFILwqujpJUjf1N2wqAUnX6mAwa3Z/9TtpTJYXcevjzUAPtxa8YGQyjqihrfStCcw=="], - "@swapkit/ui/@swapkit/plugins/@swapkit/utxo-signer": ["@swapkit/utxo-signer@2.2.2", "", { "peerDependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0", "micro-packed": "0.9.0" } }, "sha512-Mes19KKZr0emYXnkJbN3WPLgLYkxnju5CJTUl1TcZgmxFCom6M6ijU4gkOweX0iIcGzqc51RIYGB/vUAFio8bQ=="], "@swapkit/ui/@swapkit/toolboxes/@swapkit/utxo-signer": ["@swapkit/utxo-signer@2.2.2", "", { "peerDependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0", "micro-packed": "0.9.0" } }, "sha512-Mes19KKZr0emYXnkJbN3WPLgLYkxnju5CJTUl1TcZgmxFCom6M6ijU4gkOweX0iIcGzqc51RIYGB/vUAFio8bQ=="], diff --git a/package.json b/package.json index 3943f11..2430b16 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@cosmjs/stargate": "0.37.0", "@scure/bip32": "2.2.0", "@swapkit/core": "^4.6.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/plugins": "^4.7.1", "@swapkit/server": "^4.4.2", "@swapkit/toolboxes": "^4.28.0", @@ -21,7 +21,10 @@ "zod": "4.4.3" }, "name": "swapkit-wallets", - "overrides": { "nuqs": "2.8.9" }, + "overrides": { + "nuqs": "2.8.9", + "@swapkit/helpers": "0.0.0-rc-20260728115044" + }, "packageManager": "bun@1.3.13", "private": true, "scripts": { @@ -45,5 +48,9 @@ "type-check:ci": "bun run --filter './packages/*' type-check", "version-bump": "bunx changeset version && bun run enrich:changelogs && bun install" }, - "workspaces": ["packages/*", "playgrounds/*", "tools/*"] + "workspaces": [ + "packages/*", + "playgrounds/*", + "tools/*" + ] } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 896be16..94707f2 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -3,7 +3,7 @@ "dependencies": { "@stricahq/typhonjs": "~3.0.1", "@swapkit/core": "^4.6.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/plugins": "^4.7.1", "@swapkit/server": "^4.4.2", "@swapkit/toolboxes": "^4.28.0", diff --git a/packages/wallet-extensions/package.json b/packages/wallet-extensions/package.json index 2329732..58448e2 100644 --- a/packages/wallet-extensions/package.json +++ b/packages/wallet-extensions/package.json @@ -11,7 +11,7 @@ "@near-js/transactions": "~2.5.0", "@scure/base": "~2.2.0", "@solana/web3.js": "~1.98.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", diff --git a/packages/wallet-extensions/src/evm-extensions/index.ts b/packages/wallet-extensions/src/evm-extensions/index.ts index cc71981..22d0c8e 100644 --- a/packages/wallet-extensions/src/evm-extensions/index.ts +++ b/packages/wallet-extensions/src/evm-extensions/index.ts @@ -19,19 +19,19 @@ type Eip1193EventProvider = Eip1193Provider & { }; export type EVMWalletOptions = - | WalletOption.BRAVE - | WalletOption.OKX_MOBILE - | WalletOption.METAMASK - | WalletOption.COINBASE_WEB - | WalletOption.EIP6963; + | typeof WalletOption.BRAVE + | typeof WalletOption.OKX_MOBILE + | typeof WalletOption.METAMASK + | typeof WalletOption.COINBASE_WEB + | typeof WalletOption.EIP6963; const getWalletForType = ( walletType: - | WalletOption.BRAVE - | WalletOption.OKX_MOBILE - | WalletOption.METAMASK - | WalletOption.COINBASE_WEB - | WalletOption.EIP6963, + | typeof WalletOption.BRAVE + | typeof WalletOption.OKX_MOBILE + | typeof WalletOption.METAMASK + | typeof WalletOption.COINBASE_WEB + | typeof WalletOption.EIP6963, ) => { switch (walletType) { case WalletOption.COINBASE_WEB: diff --git a/packages/wallet-extensions/src/keplr/index.ts b/packages/wallet-extensions/src/keplr/index.ts index 105bdfc..11cca36 100644 --- a/packages/wallet-extensions/src/keplr/index.ts +++ b/packages/wallet-extensions/src/keplr/index.ts @@ -8,12 +8,12 @@ const keplrSupportedChainIds = [ChainId.Cosmos, ChainId.Kujira, ChainId.Noble, C export const keplrWallet: ExtensionWallet< "connectKeplr", Chain[], - [chains: Chain[], walletType?: WalletOption.KEPLR | WalletOption.LEAP] + [chains: Chain[], walletType?: typeof WalletOption.KEPLR | typeof WalletOption.LEAP] > = createWallet({ connect: ({ addChain, supportedChains }) => async function connectKeplr( chains: Chain[], - walletType: WalletOption.KEPLR | WalletOption.LEAP = WalletOption.KEPLR, + walletType: typeof WalletOption.KEPLR | typeof WalletOption.LEAP = WalletOption.KEPLR, ) { const extensionKey = walletType === WalletOption.LEAP ? "leap" : "keplr"; const filteredChains = filterSupportedChains({ chains, supportedChains, walletType }); diff --git a/packages/wallet-hardware/package.json b/packages/wallet-hardware/package.json index c8e72be..8f7a03b 100644 --- a/packages/wallet-hardware/package.json +++ b/packages/wallet-hardware/package.json @@ -22,7 +22,7 @@ "@near-js/transactions": "~2.5.0", "@scure/base": "2.2.0", "@scure/bip32": "2.2.0", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", diff --git a/packages/wallet-mobile/package.json b/packages/wallet-mobile/package.json index d720de1..881bcd0 100644 --- a/packages/wallet-mobile/package.json +++ b/packages/wallet-mobile/package.json @@ -1,7 +1,7 @@ { "author": "swapkit", "dependencies": { - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/wallet-core": "^4.3.19" }, diff --git a/packages/wallets/package.json b/packages/wallets/package.json index 764f21c..2bd6cb2 100644 --- a/packages/wallets/package.json +++ b/packages/wallets/package.json @@ -16,12 +16,14 @@ "@scure/base": "~2.2.0", "@scure/bip39": "~2.2.0", "@solana/web3.js": "~1.98.4", - "@swapkit/helpers": "^4.20.1", + "@swapkit/helpers": "0.0.0-rc-20260728115044", "@swapkit/toolboxes": "^4.28.0", "@swapkit/utxo-signer": "^2.3.0", "@swapkit/wallet-core": "^4.3.19", "@swapkit/wallet-extensions": "workspace:*", "@swapkit/wallet-hardware": "workspace:*", + "@ton/core": "~0.63.1", + "@tonconnect/ui": "~3.0.0", "@walletconnect/modal": "~2.7.0", "@walletconnect/sign-client": "~2.23.9", "cosmjs-types": "~0.10.1", @@ -46,6 +48,8 @@ "@radixdlt/radix-dapp-toolkit": "2.3.0", "@scure/base": "2.2.0", "@solana/web3.js": "1.98.4", + "@ton/core": "0.63.1", + "@tonconnect/ui": "3.0.0", "@walletconnect/logger": "3.0.2", "@walletconnect/modal": "2.7.0", "@walletconnect/sign-client": "2.23.9", @@ -184,6 +188,12 @@ "require": "./dist/src/talisman.cjs", "types": "./dist/types/talisman.d.ts" }, + "./tonconnect": { + "bun": "./src/tonconnect/index.ts", + "default": "./dist/src/tonconnect/index.js", + "require": "./dist/src/tonconnect/index.cjs", + "types": "./dist/types/tonconnect/index.d.ts" + }, "./trezor": { "bun": "./src/trezor.ts", "default": "./dist/src/trezor.js", @@ -215,21 +225,12 @@ "types": "./dist/types/xaman/index.d.ts" } }, - "files": [ - "dist/", - "src/" - ], + "files": ["dist/", "src/"], "homepage": "https://github.com/swapkit/wallets", "license": "SEE LICENSE IN LICENSE", "name": "@swapkit/wallets", - "publishConfig": { - "access": "public" - }, - "repository": { - "directory": "packages/wallets", - "type": "git", - "url": "git+https://github.com/swapkit/wallets.git" - }, + "publishConfig": { "access": "public" }, + "repository": { "directory": "packages/wallets", "type": "git", "url": "git+https://github.com/swapkit/wallets.git" }, "scripts": { "build": "bun run ./build.ts", "build:clean": "rm -rf dist && bun run ./build.ts", diff --git a/packages/wallets/src/tonconnect/helpers.ts b/packages/wallets/src/tonconnect/helpers.ts new file mode 100644 index 0000000..88bbed2 --- /dev/null +++ b/packages/wallets/src/tonconnect/helpers.ts @@ -0,0 +1,55 @@ +import { AssetValue, Chain, type GenericTransferParams, SwapKitError } from "@swapkit/helpers"; +import type { TONTransactionInput } from "@swapkit/toolboxes/ton"; +import type { TonConnectUI } from "@tonconnect/ui"; +import { match } from "ts-pattern"; +import type { TonConnectConfig } from "./types"; +import { sendTonConnectTransaction } from "./walletMethods"; + +interface GetWalletForChainParams { + address: string; + chain: Chain; + config: TonConnectConfig; + tonConnectUI: TonConnectUI; +} + +export function getWalletForChain({ address, chain, config, tonConnectUI }: GetWalletForChainParams) { + return match(chain) + .with(Chain.Ton, async () => { + const { getTONToolbox } = await import("@swapkit/toolboxes/ton"); + // Signerless toolbox: message building, balances, and fee estimation happen + // locally; signing and broadcasting are delegated to the connected wallet. + const toolbox = getTONToolbox(); + + const signAndBroadcastTransaction = (transaction: TONTransactionInput) => + sendTonConnectTransaction({ tonConnectUI, transaction, validSeconds: config.requestTimeoutSeconds }); + + const transfer = async ({ assetValue, recipient, memo }: GenericTransferParams) => { + const transaction = await toolbox.createTransaction({ assetValue, memo, recipient, sender: address }); + return signAndBroadcastTransaction(transaction); + }; + + const estimateTransactionFee = ({ assetValue }: GenericTransferParams) => { + // The signerless toolbox's estimateTransactionFee needs a configured wallet + // and silently falls back to a flat 0.01 TON when getWallet() throws — badly + // underestimating jettons. Mirror the toolbox's own transfer budget instead: + // jettons attach ~0.05 TON gas + 0.01 TON forward, native transfers ~0.01 TON. + return Promise.resolve(AssetValue.from({ chain: Chain.Ton, value: assetValue.isGasAsset ? "0.01" : "0.06" })); + }; + + return { + ...toolbox, + estimateTransactionFee, + getAddress: () => address, + // TON Connect signs and broadcasts atomically; a detached external-message + // signature is not part of the protocol. + sign: () => { + throw new SwapKitError("core_wallet_sign_message_not_supported", { wallet: "TON Connect" }); + }, + signAndBroadcastTransaction, + transfer, + }; + }) + .otherwise(() => { + throw new SwapKitError("wallet_chain_not_supported", { chain, wallet: "TON Connect" }); + }); +} diff --git a/packages/wallets/src/tonconnect/index.ts b/packages/wallets/src/tonconnect/index.ts new file mode 100644 index 0000000..a6e8741 --- /dev/null +++ b/packages/wallets/src/tonconnect/index.ts @@ -0,0 +1,43 @@ +import { Chain, filterSupportedChains, WalletOption } from "@swapkit/helpers"; +import { createWallet, getWalletSupportedChains } from "@swapkit/wallet-core"; +// Deep importers of @swapkit/wallets/tonconnect need the registration too; +// the module cache keeps this idempotent with the eager import in utils.ts. +import "./register"; +import { getWalletForChain } from "./helpers"; +import type { TonConnectConfig } from "./types"; +import { connectTonConnect, getTonConnectInstance } from "./walletMethods"; + +export const tonconnectWallet = createWallet({ + connect: ({ addChain, supportedChains: walletSupportedChains, walletType }) => + async function connectTonConnectWallet(chains: Chain[], config: TonConnectConfig = {}) { + const supportedChains = filterSupportedChains({ chains, supportedChains: walletSupportedChains, walletType }); + + const tonConnectUI = await getTonConnectInstance(config); + const address = await connectTonConnect(tonConnectUI); + + const promises = supportedChains.map(async (chain) => { + const walletMethods = await getWalletForChain({ address, chain, config, tonConnectUI }); + + addChain({ + ...walletMethods, + address, + balance: [], + chain, + disconnect: () => tonConnectUI.disconnect(), + walletType: WalletOption.TON_CONNECT, + }); + }); + + await Promise.all(promises); + return true; + }, + directSigningSupport: { [Chain.Ton]: true }, + name: "connectTonConnect", + supportedChains: [Chain.Ton], + walletType: WalletOption.TON_CONNECT, +}); + +export const TON_CONNECT_SUPPORTED_CHAINS = getWalletSupportedChains(tonconnectWallet); +export type TonConnectSupportedChain = (typeof TON_CONNECT_SUPPORTED_CHAINS)[number]; + +export type { TonConnectConfig } from "./types"; diff --git a/packages/wallets/src/tonconnect/register.ts b/packages/wallets/src/tonconnect/register.ts new file mode 100644 index 0000000..ae4f932 --- /dev/null +++ b/packages/wallets/src/tonconnect/register.ts @@ -0,0 +1,9 @@ +import { registerWalletOption } from "@swapkit/helpers"; + +declare module "@swapkit/helpers" { + interface WalletOptionRegistry { + TON_CONNECT: "TON_CONNECT"; + } +} + +registerWalletOption("TON_CONNECT", "TON_CONNECT"); diff --git a/packages/wallets/src/tonconnect/types.ts b/packages/wallets/src/tonconnect/types.ts new file mode 100644 index 0000000..2afe69f --- /dev/null +++ b/packages/wallets/src/tonconnect/types.ts @@ -0,0 +1,26 @@ +import type { TonConnectUI, TonConnectUiCreateOptions } from "@tonconnect/ui"; + +export type TonConnectConfig = { + /** + * Reuse an existing TonConnectUI instance (e.g. the one created by + * `@tonconnect/ui-react`'s TonConnectUIProvider). When provided, `manifestUrl` + * and `uiOptions` are ignored. + */ + instance?: TonConnectUI; + /** + * URL of the dApp's publicly hosted tonconnect-manifest.json. + * Required when no `instance` is provided. + * @see https://docs.ton.org/applications/ton-connect/get-started + */ + manifestUrl?: string; + /** + * Extra TonConnectUI creation options (theme, wallets list configuration, etc.) + * forwarded when SwapKit creates the instance. + */ + uiOptions?: Omit; + /** + * How long (in seconds) a sendTransaction request stays valid before the + * wallet refuses it (`validUntil`). Defaults to 300 seconds. + */ + requestTimeoutSeconds?: number; +}; diff --git a/packages/wallets/src/tonconnect/walletMethods.ts b/packages/wallets/src/tonconnect/walletMethods.ts new file mode 100644 index 0000000..5222f0b --- /dev/null +++ b/packages/wallets/src/tonconnect/walletMethods.ts @@ -0,0 +1,154 @@ +import { SwapKitError } from "@swapkit/helpers"; +import type { TONTransactionInput } from "@swapkit/toolboxes/ton"; +// Type-only: @tonconnect/ui's module init requires localStorage/DOM, so every +// value import from it must stay behind a dynamic import (SSR/node consumers +// and the bun test runner crash on an eager load). +import type { Account, CHAIN, TonConnectUI } from "@tonconnect/ui"; +import type { TonConnectConfig } from "./types"; + +const DEFAULT_VALID_SECONDS = 300; +// SendMode.CARRY_ALL_REMAINING_BALANCE — cannot be expressed over TON Connect, +// where the wallet chooses the send mode (typically PAY_GAS_SEPARATELY + IGNORE_ERRORS). +const CARRY_ALL_REMAINING_BALANCE = 128; +// CHAIN.MAINNET from @tonconnect/protocol; inlined so this module never loads +// @tonconnect/ui at evaluation time. +const TON_MAINNET = "-239" as CHAIN.MAINNET; + +let sharedInstance: TonConnectUI | undefined; + +export async function getTonConnectInstance(config: TonConnectConfig = {}): Promise { + if (config.instance) { + // Remember injected instances (e.g. from @tonconnect/ui-react) so later + // config-less calls reuse them instead of demanding a manifestUrl. + sharedInstance = config.instance; + return config.instance; + } + if (sharedInstance) return sharedInstance; + + if (!config.manifestUrl) { + throw new SwapKitError("wallet_missing_params", { param: "manifestUrl", wallet: "TON Connect" }); + } + + const { TonConnectUI } = await import("@tonconnect/ui"); + const tonConnectUI = new TonConnectUI({ manifestUrl: config.manifestUrl, ...config.uiOptions }); + + try { + // Abort connections coming from a non-mainnet wallet early. Only possible + // before a connection is established, hence the try/catch around restore races. + tonConnectUI.setConnectionNetwork(TON_MAINNET); + } catch { + // A session was already restored — the network is validated in connectTonConnect instead. + } + + sharedInstance = tonConnectUI; + return tonConnectUI; +} + +export async function connectTonConnect(tonConnectUI: TonConnectUI): Promise { + await tonConnectUI.connectionRestored; + + const account = + tonConnectUI.connected && tonConnectUI.account + ? tonConnectUI.account + : await new Promise((resolve, reject) => { + const unsubscribeModal = tonConnectUI.onModalStateChange((state) => { + if (state.status === "closed" && state.closeReason === "action-cancelled") { + cleanup(); + reject(new SwapKitError("wallet_connection_rejected_by_user", { wallet: "TON Connect" })); + } + }); + + const unsubscribeStatus = tonConnectUI.onStatusChange( + (wallet) => { + if (wallet) { + cleanup(); + resolve(wallet.account); + } + }, + (error) => { + cleanup(); + reject(new SwapKitError("core_wallet_connection_failed", { error, wallet: "TON Connect" })); + }, + ); + + function cleanup() { + unsubscribeStatus(); + unsubscribeModal(); + } + + tonConnectUI.openModal().catch((error) => { + cleanup(); + reject(new SwapKitError("core_wallet_connection_failed", { error, wallet: "TON Connect" })); + }); + }); + + if (account.chain !== TON_MAINNET) { + await tonConnectUI.disconnect().catch(() => undefined); + throw new SwapKitError("wallet_chain_not_supported", { chain: account.chain, wallet: "TON Connect" }); + } + + // TON Connect exposes the address in raw `:` form; + // convert to the user-friendly non-bounceable base64url form (UQ…) — + // wallet (account) addresses are non-bounceable by TEP-2 convention. + const { Address } = await import("@ton/core"); + return Address.parse(account.address).toString({ bounceable: false, urlSafe: true }); +} + +// TON Connect wallets reject raw `0:` destinations — normalize to TEP-2 friendly form. +// The bounce flag travels inside the friendly address, so the conversion must preserve the +// TON toolbox's convention (raw-form destinations default to bounceable=true, refunding +// failed contract sends). @tonconnect/sdk's toUserFriendlyAddress can only emit the +// non-bounceable form and must not be used here; already-friendly addresses keep the +// caller's explicit flag untouched. +export function toFriendlyDestination(address: string, AddressCtor: typeof import("@ton/core").Address) { + return AddressCtor.isFriendly(address) + ? address + : AddressCtor.parse(address).toString({ bounceable: true, urlSafe: true }); +} + +export async function sendTonConnectTransaction({ + tonConnectUI, + transaction, + validSeconds = DEFAULT_VALID_SECONDS, +}: { + tonConnectUI: TonConnectUI; + transaction: TONTransactionInput; + validSeconds?: number; +}): Promise { + const messages = Array.isArray(transaction) ? transaction : transaction.messages; + // The legacy bare-array shape carries sendMode per message (the toolbox reads + // messages[0].sendMode the same way) — the sweep guard must see it too. + const sendMode = Array.isArray(transaction) ? transaction[0]?.sendMode : transaction.sendMode; + + if (!messages.length) { + throw new SwapKitError("wallet_missing_params", { param: "messages", wallet: "TON Connect" }); + } + + if (sendMode !== undefined && (sendMode & CARRY_ALL_REMAINING_BALANCE) !== 0) { + throw new SwapKitError({ + errorKey: "core_swap_invalid_params", + info: { reason: "TON Connect does not support sweep (CARRY_ALL_REMAINING_BALANCE) transactions", sendMode }, + }); + } + + if (!tonConnectUI.connected) { + throw new SwapKitError("core_wallet_connection_not_found"); + } + + const { Address, Cell } = await import("@ton/core"); + + const { boc } = await tonConnectUI.sendTransaction({ + messages: messages.map(({ address, amount, payload, stateInit }) => ({ + address: toFriendlyDestination(address, Address), + amount, + payload, + stateInit, + })), + network: TON_MAINNET, + validUntil: Math.floor(Date.now() / 1000) + validSeconds, + }); + + // Same hash the TON toolbox returns from broadcastTransaction: + // the hash of the signed external message cell. + return Cell.fromBase64(boc).hash().toString("hex"); +} diff --git a/packages/wallets/src/types.ts b/packages/wallets/src/types.ts index 8132cb4..98615ba 100644 --- a/packages/wallets/src/types.ts +++ b/packages/wallets/src/types.ts @@ -23,6 +23,7 @@ import type { metamaskWallet } from "./metamask"; import type { walletSelectorWallet } from "./near-wallet-selector"; import type { passkeysWallet } from "./passkeys"; import type { radixWallet } from "./radix"; +import type { tonconnectWallet } from "./tonconnect"; import type { walletconnectWallet } from "./walletconnect"; import type { xamanWallet } from "./xaman"; @@ -52,6 +53,7 @@ export type SKWallets = { [WalletOption.POLKADOT_JS]: typeof polkadotWallet; [WalletOption.RADIX_WALLET]: typeof radixWallet; [WalletOption.TALISMAN]: typeof talismanWallet; + [WalletOption.TON_CONNECT]: typeof tonconnectWallet; [WalletOption.TREZOR]: typeof trezorWallet; [WalletOption.TRONLINK]: typeof tronlinkWallet; [WalletOption.TRUSTWALLET_WEB]: typeof trustwalletWallet; @@ -128,6 +130,7 @@ export type SKWalletsSupportedChains = { [WalletOption.POLKADOT_JS]: typeof polkadotWallet.connectPolkadotJs.supportedChains; [WalletOption.RADIX_WALLET]: typeof radixWallet.connectRadixWallet.supportedChains; [WalletOption.TALISMAN]: typeof talismanWallet.connectTalisman.supportedChains; + [WalletOption.TON_CONNECT]: typeof tonconnectWallet.connectTonConnect.supportedChains; [WalletOption.TREZOR]: typeof trezorWallet.connectTrezor.supportedChains; [WalletOption.TRONLINK]: typeof tronlinkWallet.connectTronLink.supportedChains; [WalletOption.TRUSTWALLET_WEB]: typeof trustwalletWallet.connectTrustWallet.supportedChains; diff --git a/packages/wallets/src/utils.ts b/packages/wallets/src/utils.ts index c1dcecb..23f0c3a 100644 --- a/packages/wallets/src/utils.ts +++ b/packages/wallets/src/utils.ts @@ -1,4 +1,7 @@ import { WalletOption } from "@swapkit/helpers"; +// Registers WalletOption.TON_CONNECT before the match below reads it; the +// heavy TON Connect client itself still loads lazily via the match arm. +import "./tonconnect/register"; import type { SKWallets } from "./types"; export async function loadWallet(walletOption: W): Promise { @@ -50,6 +53,7 @@ export async function loadWallet(walletOption: W): Pr .with(WalletOption.RADIX_WALLET, async () => (await import("./radix")).radixWallet) .with(WalletOption.TALISMAN, async () => (await import("@swapkit/wallet-extensions/talisman")).talismanWallet) .with(WalletOption.TRONLINK, async () => (await import("@swapkit/wallet-extensions/tronlink")).tronlinkWallet) + .with(WalletOption.TON_CONNECT, async () => (await import("./tonconnect")).tonconnectWallet) .with(WalletOption.WALLET_SELECTOR, async () => (await import("./near-wallet-selector")).walletSelectorWallet) .with(WalletOption.XAMAN, async () => (await import("./xaman")).xamanWallet) .exhaustive(); diff --git a/packages/wallets/test/tonconnect.test.ts b/packages/wallets/test/tonconnect.test.ts new file mode 100644 index 0000000..9c2d7ed --- /dev/null +++ b/packages/wallets/test/tonconnect.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "bun:test"; +import { SwapKitError, WalletOption } from "@swapkit/helpers"; +import { Address, beginCell } from "@ton/core"; +import type { TonConnectUI } from "@tonconnect/ui"; + +import "../src/tonconnect/register"; +import { sendTonConnectTransaction, toFriendlyDestination } from "../src/tonconnect/walletMethods"; + +const RAW_ADDRESS = "0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8"; +const BOUNCEABLE = Address.parse(RAW_ADDRESS).toString({ bounceable: true, urlSafe: true }); +const NON_BOUNCEABLE = Address.parse(RAW_ADDRESS).toString({ bounceable: false, urlSafe: true }); +const VALID_BOC = beginCell().endCell().toBoc().toString("base64"); + +function makeFakeTonConnectUI(sentRequests: { address: string }[][]) { + return { + connected: true, + sendTransaction: (request: { messages: { address: string }[] }) => { + sentRequests.push(request.messages); + return Promise.resolve({ boc: VALID_BOC }); + }, + } as unknown as TonConnectUI; +} + +describe("WalletOption registration", () => { + it("registers TON_CONNECT in the extensible WalletOption registry", () => { + expect(WalletOption.TON_CONNECT).toBe("TON_CONNECT"); + }); +}); + +describe("toFriendlyDestination", () => { + it("converts raw-form destinations to the BOUNCEABLE friendly form (toolbox convention)", () => { + const friendly = toFriendlyDestination(RAW_ADDRESS, Address); + + expect(friendly).toBe(BOUNCEABLE); + expect(Address.parseFriendly(friendly).isBounceable).toBe(true); + }); + + it("keeps already-friendly addresses untouched, preserving the caller's bounce flag", () => { + expect(toFriendlyDestination(NON_BOUNCEABLE, Address)).toBe(NON_BOUNCEABLE); + expect(toFriendlyDestination(BOUNCEABLE, Address)).toBe(BOUNCEABLE); + }); +}); + +describe("sendTonConnectTransaction", () => { + it("normalizes raw destinations to bounceable friendly form in the wallet request", async () => { + const sent: { address: string }[][] = []; + const hash = await sendTonConnectTransaction({ + tonConnectUI: makeFakeTonConnectUI(sent), + transaction: { messages: [{ address: RAW_ADDRESS, amount: "1" }] }, + }); + + expect(sent[0]?.[0]?.address).toBe(BOUNCEABLE); + expect(hash).toMatch(/^[0-9a-f]{64}$/); + }); + + it("rejects sweep sendMode on the object transaction shape", () => { + expect( + sendTonConnectTransaction({ + tonConnectUI: makeFakeTonConnectUI([]), + transaction: { messages: [{ address: RAW_ADDRESS, amount: "1" }], sendMode: 128 }, + }), + ).rejects.toBeInstanceOf(SwapKitError); + }); + + it("rejects sweep sendMode carried on the legacy bare-array shape", () => { + expect( + sendTonConnectTransaction({ + tonConnectUI: makeFakeTonConnectUI([]), + transaction: [{ address: RAW_ADDRESS, amount: "1", sendMode: 128 }], + }), + ).rejects.toBeInstanceOf(SwapKitError); + }); + + it("rejects empty message lists", () => { + expect( + sendTonConnectTransaction({ tonConnectUI: makeFakeTonConnectUI([]), transaction: [] }), + ).rejects.toBeInstanceOf(SwapKitError); + }); +});