├── .github └── workflows │ ├── publish.yml │ └── static.yml ├── .gitignore ├── .yarn └── releases │ └── yarn-3.4.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── karma.conf.js ├── karma.setup.js ├── package.json ├── src ├── client │ ├── TonClient.spec.ts │ ├── TonClient.ts │ ├── TonClient4.spec.ts │ ├── TonClient4.ts │ └── api │ │ ├── HttpApi.ts │ │ └── TonCache.ts ├── config │ ├── ConfigParser.spec.ts │ └── ConfigParser.ts ├── elector │ ├── ElectorContract.spec.ts │ └── ElectorContract.ts ├── index.ts ├── jetton │ ├── JettonMaster.spec.ts │ ├── JettonMaster.ts │ └── JettonWallet.ts ├── multisig │ ├── MultisigOrder.spec.ts │ ├── MultisigOrder.ts │ ├── MultisigOrderBuilder.ts │ ├── MultisigWallet.spec.ts │ └── MultisigWallet.ts ├── utils │ ├── createTestClient.ts │ ├── createTestClient4.ts │ ├── fees.spec.ts │ ├── fees.ts │ ├── maybe.ts │ ├── randomTestKey.ts │ ├── testWallets.ts │ ├── time.ts │ └── toUrlSafe.ts └── wallets │ ├── WalletContractV1R1.spec.ts │ ├── WalletContractV1R1.ts │ ├── WalletContractV1R2.spec.ts │ ├── WalletContractV1R2.ts │ ├── WalletContractV1R3.spec.ts │ ├── WalletContractV1R3.ts │ ├── WalletContractV2R1.spec.ts │ ├── WalletContractV2R1.ts │ ├── WalletContractV2R2.spec.ts │ ├── WalletContractV2R2.ts │ ├── WalletContractV3R1.spec.ts │ ├── WalletContractV3R1.ts │ ├── WalletContractV3R2.spec.ts │ ├── WalletContractV3R2.ts │ ├── WalletContractV3Types.ts │ ├── WalletContractV4.spec.ts │ ├── WalletContractV4.ts │ ├── WalletContractV5Beta.ts │ ├── WalletContractV5R1.ts │ ├── signing │ ├── createWalletTransfer.ts │ └── singer.ts │ ├── v5beta │ ├── WalletContractV5Beta.spec.ts │ ├── WalletContractV5Beta.ts │ ├── WalletV5BetaActions.spec.ts │ ├── WalletV5BetaActions.ts │ ├── WalletV5BetaWalletId.spec.ts │ ├── WalletV5BetaWalletId.ts │ └── WalletV5OutActions.ts │ └── v5r1 │ ├── WalletContractV5R1.spec.ts │ ├── WalletContractV5R1.ts │ ├── WalletV5R1Actions.spec.ts │ ├── WalletV5R1Actions.ts │ ├── WalletV5R1WalletId.spec.ts │ └── WalletV5R1WalletId.ts ├── tsconfig.json └── yarn.lock /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: Build, Test and Publish 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Use Node.js 18 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: '18.x' 16 | always-auth: true 17 | - name: Install Yarn 18 | run: npm install -g yarn 19 | - name: Install dependencies 20 | run: yarn 21 | - name: Build 22 | run: yarn build 23 | - name: Test 24 | run: yarn test 25 | - name: Publish 26 | env: 27 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 28 | run: yarn publish --access public 29 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Node.js 34 | uses: actions/setup-node@v3 35 | - run: npm install 36 | - run: npm run docs 37 | - name: Setup Pages 38 | uses: actions/configure-pages@v2 39 | - name: Upload artifact 40 | uses: actions/upload-pages-artifact@v1 41 | with: 42 | path: './docs' 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v1 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /dist/ 3 | /node_modules/ 4 | /package-lock.json 5 | 6 | 7 | # Yarn >2.0.0 8 | .yarn/* 9 | !.yarn/releases 10 | !.yarn/plugins 11 | !.yarn/sdks 12 | !.yarn/versions -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [15.2.1] - 2025-02-07 8 | 9 | ## Fixed 10 | - `TonClient` `extra_currencies` field is now optional 11 | 12 | ## [15.2.0] - 2025-01-31 13 | 14 | ## Added 15 | - Extracurrencies support (thx @Trinketer22) 16 | 17 | ## [15.1.0] - 2024-10-09 18 | 19 | This update requires `@ton/core` >0.59.0 20 | 21 | ## Fixed 22 | - `TonClient4` and `TonClient` providers restrict using method id as number 23 | - Updated typescript to 5.6.3 24 | 25 | ## [15.0.0] - 2024-08-16 26 | - Make spelling consistent for wallets 27 | 28 | ## [14.0.0] - 2024-07-16 29 | 30 | ## Added 31 | - Added V5 wallet support (thx Tonkeeper Team) 32 | - Fixed stack serialization for TonCenter v2 client (thx @aspite) 33 | 34 | ## Fixed 35 | - Types for different wallet versions 36 | 37 | ## [13.11.2] - 2024-05-31 38 | 39 | ## Added 40 | - Ability to pass interceptor for TonClientV4 API calls 41 | 42 | ## Fixed 43 | - TonClient minor type issues 44 | 45 | ## [13.11.1] - 2024-02-26 46 | 47 | ## Fixed 48 | - Added xports for `HttpApiParameters`/`TonClientParameters` 49 | - Added `TonClient.getTransactions` missing `archival` parameter 50 | - Updated packages 51 | 52 | ## [13.11.0] - 2024-02-23 53 | 54 | This update requires `@ton/core` >0.56.0 55 | 56 | ## Fixed 57 | - Updated `TonClient4` and `TonClient` to match contract providers at `@ton/core@0.56.0` 58 | 59 | ## [13.10.0] - 2024-02-06 60 | 61 | ## Added 62 | - Locate tx methods in `TonClient` (thx @krigga) 63 | 64 | ## Fixed 65 | - Vue.js compilation (thx @d0rich) 66 | - Allow to use `HttpApi` property in `TonClient` inheritants (thx @ernieyang09) 67 | 68 | ## [13.9.0] - 2023-10-25 69 | 70 | ## Removed 71 | - `WalletV5` contract due to the unreadiness 72 | 73 | ## [13.8.0] - 2023-10-24 74 | 75 | ## Added 76 | - `TonClient4.getAccountTransactionsParsed` method (thanks @vzhovnitsky) 77 | - `WalletV5` contract (thanks @siandreev) 78 | - blockchain fees estimation via `computeStorageFees`/`computeExternalMessageFees`/`computeGasPrices`/`computeMessageForwardFees` (thanks @vzhovnitsky) 79 | 80 | ## Fixed 81 | - Uri encode get method name for `TonClient4` (thanks @krigga) 82 | - Improved `parseStackItem` due to toncenter.com bug 83 | 84 | 85 | ## [13.7.0] - 2023-09-18 86 | 87 | ## Added 88 | - `sendOrderWithoutSecretKey` method to `MultisigWallet` 89 | 90 | ## Fixed 91 | - Uri encode get method name for `TonClient4` 92 | 93 | ## [13.6.1] - 2023-08-24 94 | 95 | ## Fixed 96 | - `TonClient.getAccountTransactions` return type 97 | 98 | ## [13.6.0] - 2023-08-17 99 | ## Added 100 | - `ElectorContract` 101 | - `parseFullConfig` and config params parsing methods 102 | 103 | ## [13.5.1] - 2023-07-14 104 | ## Changed 105 | - Migrated to `@ton/crypto` package instead of `ton-crypto` 106 | - Migrated to `@ton/core` instead of `ton-core` 107 | - Migrated to `@ton/emulator` instead of `ton-emulator` 108 | - Renamed package to `@ton/ton` 109 | 110 | 111 | ## [13.5.0] - 2023-05-10 112 | ## Fixed 113 | - Replaced `io-ts` with `zod` to reduce web bundle size 114 | - Removed unimplemented method `getOneTransaction` from `TonClient4` 115 | 116 | ## [13.4.1] - 2023-03-02 117 | 118 | ## Added 119 | - call get method aliases for TonClient (#7) 120 | - add isContractDeployed to TonClient4 (#6) 121 | 122 | ## Fixed 123 | - Updated `ton-core` to depend from 0.48.0 124 | - Fixed typos in `SendMode` 125 | 126 | ## [13.4.0] - 2023-03-01 127 | 128 | ## Added 129 | - `MultisigWallet`, `MultisigOrder` and `MultisigOrderBuilder` 130 | 131 | ## [13.3.0] - 2023-01-05 132 | 133 | ## Added 134 | - `getTransaction` to `TonClient4` to get a single transaction by id 135 | 136 | ## [13.2.0] - 2022-12-31 137 | 138 | ## Changed 139 | - Updaded `ton-core` and renambed `AccountState` to `ContractState` 140 | - Replaced internal usafe of a `openContract` with `ton-core` one 141 | 142 | ## [13.1.0] - 2022-12-31 143 | 144 | ## Changed 145 | - Upgraded `ton-core` and removed legacy usage of `Message` type 146 | 147 | ## [13.0.0] - 2022-12-29 148 | 149 | ## Changed 150 | - Large refactoring, removing a lot of obsolete features and replacing low level classes like `Cell` with `ton-core` implementation 151 | - New way to work with contracts 152 | - Explicit work with wallet contracts 153 | - Unify stack operations in `TonClient` and `TonClient4` 154 | - Merged `TupleSlice` and `TupleSlice4` into `TupleReader` from `ton-core` 155 | 156 | ## Removed 157 | - Removed magical `Wallet` operations 158 | 159 | ## [12.3.3] - 2022-12-22 160 | # Changed 161 | - Improved BOC serialization 162 | 163 | ## [12.3.2] 164 | - Fix unicode symbols in `readString` function 165 | 166 | ## [10.4.0] 167 | - `TonClient4` - client for new API 168 | 169 | ## [10.0.0]-[10.3.0] 170 | - Exotic Cells parsing 171 | - `readBitString` 172 | - VM Stack parsing 173 | 174 | ## [9.2.0] 175 | - Builder and dict builder 176 | 177 | ## [9.1.0] 178 | - Support for API token 179 | 180 | ## [9.0.0] 181 | - Synchronous Cell's `hash` and a lot of related functions like `contractAddress`. 182 | 183 | ## [6.10.0] 184 | - Better compatibility with webpack 185 | 186 | ## [6.8.0] 187 | - Allow large comments 188 | 189 | ## [6.7.0] 190 | - Exported all parsing methods and `contractAddress` 191 | 192 | ## [6.6.0] 193 | - ADNL address 194 | 195 | ## [6.5.2] 196 | - Improve Internal/External messages typings 197 | 198 | ## [6.5.0-6.5.1] 199 | - Ability to include first transaction in getTransactions method 200 | 201 | ## [6.4.0] 202 | - Better webpack support 203 | 204 | ## [6.3.0] 205 | 206 | - Added dictionary serialization 207 | - Added `equals` to Cell 208 | 209 | ## [6.1.0-6.2.1] 210 | 211 | - Added parsing of int (as addition to uint) in `BitStreamReader` and `Slice` 212 | 213 | ## [6.0.0] 214 | 215 | - [BREAKING] Change `RawMessage` to `CellMessage` and use `RawMessage` in parseTransaction 216 | - Improve parseTransaction typings. Added: 217 | - RawAccountStatus 218 | - RawCurrencyCollection 219 | - RawCommonMessageInfo 220 | - RawStateInit 221 | - RawMessage 222 | - RawHashUpdate 223 | - RawAccountStatusChange 224 | - RawStorageUsedShort 225 | - RawStoragePhase 226 | - RawComputePhase 227 | - RawActionPhase 228 | - RawBouncePhase 229 | - RawTransactionDescription 230 | - RawTransaction 231 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2023 Whales Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TON JS Client 2 | 3 | [![Version npm](https://img.shields.io/npm/v/ton.svg?logo=npm)](https://www.npmjs.com/package/ton) 4 | 5 | Cross-platform client for TON blockchain. 6 | 7 | ## Features 8 | 9 | - 🚀 Create new wallets 10 | - 🍰 Get balance 11 | - ✈️ Transfers 12 | 13 | ## Install 14 | 15 | ```bash 16 | yarn add @ton/ton @ton/crypto @ton/core buffer 17 | ``` 18 | 19 | #### Browser polyfill 20 | 21 | ```js 22 | // Add before using library 23 | require("buffer"); 24 | ``` 25 | 26 | ## Usage 27 | 28 | To use this library you need HTTP API endpoint, you can use one of the public endpoints: 29 | 30 | - Mainnet: https://toncenter.com/api/v2/jsonRPC 31 | - Testnet: https://testnet.toncenter.com/api/v2/jsonRPC 32 | 33 | ```js 34 | import { TonClient, WalletContractV4, internal } from "@ton/ton"; 35 | import { mnemonicNew, mnemonicToPrivateKey } from "@ton/crypto"; 36 | 37 | // Create Client 38 | const client = new TonClient({ 39 | endpoint: 'https://toncenter.com/api/v2/jsonRPC', 40 | }); 41 | 42 | // Generate new key 43 | let mnemonics = await mnemonicNew(); 44 | let keyPair = await mnemonicToPrivateKey(mnemonics); 45 | 46 | // Create wallet contract 47 | let workchain = 0; // Usually you need a workchain 0 48 | let wallet = WalletContractV4.create({ workchain, publicKey: keyPair.publicKey }); 49 | let contract = client.open(wallet); 50 | 51 | // Get balance 52 | let balance: bigint = await contract.getBalance(); 53 | 54 | // Create a transfer 55 | let seqno: number = await contract.getSeqno(); 56 | let transfer = await contract.createTransfer({ 57 | seqno, 58 | secretKey: keyPair.secretKey, 59 | messages: [internal({ 60 | value: '1.5', 61 | to: 'EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N', 62 | body: 'Hello world', 63 | })] 64 | }); 65 | 66 | ``` 67 | 68 | ## Docs 69 | 70 | [Documentation](https://ton-community.github.io/ton/) 71 | 72 | ## Acknowledgements 73 | 74 | This library is developed by the [Whales Corp.](https://tonwhales.com/) and maintained by [Dan Volkov](https://github.com/dvlkv). 75 | 76 | ## License 77 | 78 | MIT 79 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testPathIgnorePatterns: ["/node_modules/","/dist/"], 6 | testTimeout: 60000, 7 | moduleNameMapper: { 8 | '^axios$': require.resolve('axios'), 9 | } 10 | }; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | plugins: [ 4 | 'karma-webpack', 5 | 'karma-jasmine', 6 | 'karma-chrome-launcher' 7 | ], 8 | 9 | webpack: { 10 | resolve: { 11 | fallback: { "crypto": false }, 12 | extensions: ['.js', '.ts', '.json'] 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.ts$/, 18 | use: 'ts-loader' 19 | }, 20 | ], 21 | } 22 | // Your webpack config here 23 | }, 24 | 25 | // base path that will be used to resolve all patterns (eg. files, exclude) 26 | basePath: '', 27 | 28 | // frameworks to use 29 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 30 | frameworks: ['jasmine'], 31 | 32 | // list of files / patterns to load in the browser 33 | // Here I'm including all of the the Jest tests which are all under the __tests__ directory. 34 | // You may need to tweak this patter to find your test files/ 35 | files: ['./karma.setup.js', 'src/**/*.ts'], 36 | 37 | browsers: ['ChromeHeadless'], 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | './karma.setup.js': ['webpack'], 43 | 'src/**/*.ts': ['webpack'], 44 | }, 45 | }); 46 | }; -------------------------------------------------------------------------------- /karma.setup.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | window.Buffer = Buffer; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ton/ton", 3 | "version": "15.2.1", 4 | "repository": "https://github.com/ton-org/ton.git", 5 | "author": "Whales Corp. ", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "docs": "rm -fr docs && typedoc src/index.ts", 13 | "build": "rm -fr dist && tsc --declaration", 14 | "test": "jest --verbose --runInBand", 15 | "release": "yarn build && yarn release-it --npm.yarn1" 16 | }, 17 | "devDependencies": { 18 | "@release-it/keep-a-changelog": "^5.0.0", 19 | "@ton/core": "^0.60.0", 20 | "@ton/crypto": "3.2.0", 21 | "@ton/emulator": "^2.1.1", 22 | "@types/jest": "^27.0.1", 23 | "@types/node": "^16.7.10", 24 | "buffer": "^6.0.3", 25 | "expect": "^27.1.0", 26 | "jest": "^27.1.0", 27 | "jest-mock": "^27.1.0", 28 | "karma": "^6.3.4", 29 | "karma-chrome-launcher": "^3.1.0", 30 | "karma-jasmine": "^4.0.1", 31 | "karma-typescript": "^5.5.2", 32 | "karma-webpack": "^5.0.0", 33 | "prando": "^6.0.1", 34 | "release-it": "^17.1.1", 35 | "ts-jest": "^27.0.5", 36 | "ts-loader": "^9.2.5", 37 | "ts-node": "^10.7.0", 38 | "typedoc": "^0.23.24", 39 | "typescript": "^5.6.3", 40 | "webpack": "^5.51.2" 41 | }, 42 | "dependencies": { 43 | "axios": "^1.6.7", 44 | "dataloader": "^2.0.0", 45 | "symbol.inspect": "1.0.1", 46 | "teslabot": "^1.3.0", 47 | "zod": "^3.21.4" 48 | }, 49 | "peerDependencies": { 50 | "@ton/core": ">=0.60.0", 51 | "@ton/crypto": ">=3.2.0" 52 | }, 53 | "publishConfig": { 54 | "access": "public", 55 | "registry": "https://registry.npmjs.org/" 56 | }, 57 | "release-it": { 58 | "github": { 59 | "release": true 60 | }, 61 | "plugins": { 62 | "@release-it/keep-a-changelog": { 63 | "filename": "CHANGELOG.md" 64 | } 65 | } 66 | }, 67 | "packageManager": "yarn@3.4.1" 68 | } 69 | -------------------------------------------------------------------------------- /src/client/TonClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell } from '@ton/core'; 2 | import { TonClient } from './TonClient'; 3 | 4 | let describeConditional = process.env.TEST_CLIENTS ? describe : describe.skip; 5 | 6 | describeConditional('TonClient', () => { 7 | let client = new TonClient({ 8 | endpoint: 'https://toncenter.com/api/v2/jsonRPC', 9 | }); 10 | const testAddress = Address.parse('EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N'); 11 | 12 | it('should get contract state', async () => { 13 | let state = await client.getContractState(testAddress); 14 | console.log(state); 15 | }); 16 | 17 | it('should get balance', async () => { 18 | let balance = await client.getBalance(testAddress); 19 | console.log(balance); 20 | }); 21 | 22 | it('should get transactions', async () => { 23 | let transactions = await client.getTransactions(testAddress, { limit: 3 }); 24 | console.log(transactions); 25 | }); 26 | 27 | it('should get single transaction', async () => { 28 | let info = await client.getTransaction(testAddress, '37508996000003', 'xiwW9EROcDMWFibmm2YNW/2kTaDW5qwRJxveEf4xUQA='); 29 | console.log(info); 30 | }); 31 | 32 | it('should run method', async () => { 33 | let seqno = await client.runMethod(testAddress, 'seqno'); 34 | console.log(seqno); 35 | }); 36 | 37 | it('should get mc info', async () => { 38 | let info = await client.getMasterchainInfo(); 39 | 40 | let shardInfo = await client.getShardTransactions(info.workchain, info.latestSeqno, info.shard); 41 | let wcShards = await client.getWorkchainShards(info.latestSeqno); 42 | 43 | console.log(info, shardInfo, wcShards); 44 | }); 45 | 46 | it('should check contract is deployed', async () => { 47 | expect(await client.isContractDeployed(testAddress)).toBe(true); 48 | }); 49 | 50 | it('should get extra currency info', async () => { 51 | // EC is rolled out only in testned yet 52 | let testClient = new TonClient({ 53 | endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC' 54 | }); 55 | 56 | let testAddr = Address.parse("0:D36CFC9E0C57F43C1A719CB9F540ED87A694693AE1535B7654B645F52814AFD7"); 57 | 58 | let res = await testClient.getContractState(testAddr); 59 | let expectedEc = res.extra_currencies?.find(e => e.id == 100)!; 60 | expect(expectedEc).not.toBeUndefined(); 61 | expect(BigInt(expectedEc.amount)).toBe(10000000n); 62 | }); 63 | 64 | it('should locate source/result tx', async () => { 65 | let source = Address.parse('UQDDT0TOC4PMp894jtCo3-d1-8ltSjXMX2EuWww_pCNibsUH'); 66 | let createdLt = '37508996000002'; 67 | 68 | let infoSource = await client.tryLocateSourceTx(source, testAddress, createdLt); 69 | console.log(infoSource); 70 | 71 | let infoResult = await client.tryLocateResultTx(source, testAddress, createdLt); 72 | console.log(infoResult); 73 | }); 74 | }); -------------------------------------------------------------------------------- /src/client/TonClient4.spec.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell } from '@ton/core'; 2 | import { TonClient } from './TonClient'; 3 | import { TonClient4 } from './TonClient4'; 4 | import { backoff } from '../utils/time'; 5 | 6 | type ECMap = { [k: number]: bigint }; 7 | 8 | let describeConditional = process.env.TEST_CLIENTS ? describe : describe.skip; 9 | 10 | describeConditional('TonClient', () => { 11 | let client = new TonClient4({ 12 | endpoint: 'https://mainnet-v4.tonhubapi.com', 13 | }); 14 | const testAddress = Address.parse('EQBicYUqh1j9Lnqv9ZhECm0XNPaB7_HcwoBb3AJnYYfqB38_'); 15 | 16 | let seqno!: number; 17 | beforeAll(async () => { 18 | let last = await client.getLastBlock(); 19 | seqno = last.last.seqno; 20 | }); 21 | 22 | it('should get account with transactions', async () => { 23 | let account = await client.getAccount(seqno, testAddress); 24 | let accountLite = await client.getAccountLite(seqno, testAddress); 25 | 26 | let transactions = await client.getAccountTransactions(testAddress, BigInt(accountLite.account.last!.lt), Buffer.from(accountLite.account.last!.hash, 'base64')); 27 | let result = await client.isAccountChanged(seqno, testAddress, BigInt(accountLite.account.last!.lt)); 28 | console.log(transactions, result); 29 | 30 | console.log(account, accountLite); 31 | }); 32 | 33 | it('should get account parsed transactions', async () => { 34 | let accountLite = await backoff(async () => await client.getAccountLite(seqno, testAddress), true); 35 | let parsedTransactions = await backoff(async () => await client.getAccountTransactionsParsed(testAddress, BigInt(accountLite.account.last!.lt), Buffer.from(accountLite.account.last!.hash, 'base64'), 10), true); 36 | 37 | console.log(parsedTransactions.transactions.length); 38 | }, 60_000); 39 | 40 | it('should get config', async () => { 41 | let config = await client.getConfig(seqno); 42 | console.log(config); 43 | }); 44 | 45 | it('should get block', async () => { 46 | let result = await client.getBlock(seqno); 47 | console.log(result); 48 | }); 49 | 50 | it('should get extra currency info', async () => { 51 | let testAddresses = [ 52 | "-1:0000000000000000000000000000000000000000000000000000000000000000", 53 | "0:C4CAC12F5BC7EEF4CF5EC84EE68CCF860921A06CA0395EC558E53E37B13C3B08", 54 | "0:F5FFA780ACEE2A41663C1E32F50D771327275A42FC9D3FAB4F4D9CDE11CCA897" 55 | ].map(a => Address.parse(a)); 56 | 57 | let knownEc = [239, 4294967279]; 58 | let expectedEc: ECMap[] = [ 59 | {239: 663333333334n, 4294967279: 998444444446n}, 60 | {239: 989097920n}, 61 | {239: 666666666n, 4294967279: 777777777n} 62 | ]; 63 | 64 | for(let i = 0; i < testAddresses.length; i++) { 65 | let res = await backoff(async () => await client.getAccount(seqno, testAddresses[i]), false); 66 | let resLite = await backoff(async () => await client.getAccountLite(seqno, testAddresses[i]), false); 67 | let expected = expectedEc[i]; 68 | 69 | for(let testEc of knownEc) { 70 | let expCur = expected[testEc]; 71 | if(expCur) { 72 | expect(BigInt(res.account.balance.currencies[testEc])).toEqual(expCur); 73 | expect(BigInt(resLite.account.balance.currencies[testEc])).toEqual(expCur); 74 | } 75 | } 76 | } 77 | }); 78 | 79 | it('should run method', async () => { 80 | let result = await client.runMethod(seqno, testAddress, 'seqno'); 81 | console.log(result); 82 | }); 83 | }); -------------------------------------------------------------------------------- /src/client/api/TonCache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | 10 | export interface TonCache { 11 | set(namespace: string, key: string, value: string | null): Promise; 12 | get(namespace: string, key: string): Promise; 13 | } 14 | 15 | export class InMemoryCache implements TonCache { 16 | private cache = new Map(); 17 | 18 | set = async (namespace: string, key: string, value: string | null) => { 19 | if (value !== null) { 20 | this.cache.set(namespace + '$$' + key, value) 21 | } else { 22 | this.cache.delete(namespace + '$$' + key); 23 | } 24 | } 25 | 26 | get = async (namespace: string, key: string) => { 27 | let res = this.cache.get(namespace + '$$' + key); 28 | if (res !== undefined) { 29 | return res; 30 | } else { 31 | return null; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/config/ConfigParser.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { createTestClient4 } from "../utils/createTestClient4"; 10 | import { Address } from "@ton/core"; 11 | import { configParse5, configParse13, configParse17, configParse18, configParseValidatorSet, configParseBridge, configParse12, parseFullConfig, loadConfigParamById, loadConfigParamsAsSlice } from "./ConfigParser"; 12 | 13 | const client = createTestClient4("mainnet"); 14 | const KNOWN_BLOCK = 31091335; 15 | 16 | describe('ConfigContract', () => { 17 | 18 | // for some reason api returns 500 for this reques 19 | // it('should return correct burning config', async () => { 20 | // const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [13])).config.cell; 21 | // const config13 = configParse13(loadConfigParamById(serializedConfigsCell, 13).beginParse()); 22 | 23 | // console.log(config13); 24 | 25 | // }); 26 | 27 | it('should return correct complaint pricing', async () => { 28 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [5])).config.cell; 29 | const config5 = configParse5(loadConfigParamById(serializedConfigsCell, 5).beginParse()); 30 | 31 | expect(config5!.blackholeAddr!.equals(Address.parse('Ef___________________________________________7Sg'))).toBe(true); 32 | expect(config5!.feeBurnNominator).toEqual(1); 33 | expect(config5!.feeBurnDenominator).toEqual(2); 34 | }); 35 | 36 | it('should return correct workckain description', async () => { 37 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [12])).config.cell; 38 | const config12 = configParse12(loadConfigParamById(serializedConfigsCell, 12).beginParse()); 39 | 40 | expect(config12!.get(0)).toEqual({ 41 | enabledSince: 1573821854, 42 | actialMinSplit: 0, 43 | min_split: 0, 44 | max_split: 4, 45 | basic: true, 46 | active: true, 47 | accept_msgs: true, 48 | flags: 0, 49 | zerostateRootHash: Buffer.from('55b13f6d0e1d0c34c9c2160f6f918e92d82bf9ddcf8de2e4c94a3fdf39d15446', 'hex'), 50 | zerostateFileHash: Buffer.from('ee0bedfe4b32761fb35e9e1d8818ea720cad1a0e7b4d2ed673c488e72e910342', 'hex'), 51 | version: 0, 52 | format: { 53 | vmMode: 16140901064495857664n, 54 | vmVersion: 1073741823, 55 | }, 56 | 57 | }); 58 | }); 59 | 60 | it('should return correct config17', async () => { 61 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [17])).config.cell; 62 | const config17 = configParse17(loadConfigParamById(serializedConfigsCell, 17).beginParse()); 63 | 64 | expect(config17).toEqual({ 65 | minStake: 300000000000000n, 66 | maxStake: 10000000000000000n, 67 | minTotalStake: 75000000000000000n, 68 | maxStakeFactor: 196608 69 | }); 70 | }); 71 | 72 | it('should return correct config18', async () => { 73 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [18])).config.cell; 74 | const config18 = configParse18(loadConfigParamById(serializedConfigsCell, 18).beginParse()); 75 | 76 | expect(config18[0]).toEqual({ 77 | utime_since: 0, 78 | bit_price_ps: 1n, 79 | cell_price_ps: 500n, 80 | mc_bit_price_ps: 1000n, 81 | mc_cell_price_ps: 500000n 82 | }); 83 | }); 84 | 85 | it('should return correct prevValidators', async () => { 86 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [32])).config.cell; 87 | const config32 = configParseValidatorSet(loadConfigParamById(serializedConfigsCell, 32).beginParse()); 88 | 89 | expect(config32!.timeSince).toEqual(1689145096); 90 | expect(config32!.timeUntil).toEqual(1689210632); 91 | expect(config32!.total).toEqual(331); 92 | expect(config32!.main).toEqual(100); 93 | expect(config32!.totalWeight).toEqual(1152921504606846812n); 94 | expect(config32!.list.get(0)).toEqual({ 95 | publicKey: Buffer.from('9828e815ea69180cac1ae2b02f15f285a9cef71ec11c7709acc31128a303448c', 'hex'), 96 | weight: 5077814413300977n, 97 | adnlAddress: Buffer.from('e2e5cadaa61c6d84f86a3618d496ea0bd98c79edc796af9895b82fb83cb666b9', 'hex') 98 | }); 99 | }); 100 | 101 | it('should return correct ethereum bridge', async () => { 102 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK, [71])).config.cell; 103 | const config71 = configParseBridge(loadConfigParamById(serializedConfigsCell, 71).beginParse()); 104 | 105 | expect(config71!.bridgeAddress.equals(Address.parse('Ef_dJMSh8riPi3BTUTtcxsWjG8RLKnLctNjAM4rw8NN-xWdr'))).toBe(true); 106 | expect(config71!.oracleMultisigAddress.equals(Address.parse('Ef87m7_QrVM4uXAPCDM4DuF9Rj5Rwa5nHubwiQG96JmyAjQY'))).toBe(true); 107 | expect(config71!.oracles.get('Ef8DfObDUrNqz66pr_7xMbUYckUFbIIvRh1FSNeVSLWrvo1M')).toEqual(Buffer.from('000000000000000000000000cf4a7c26186aa41390e246fa04115a0495085ab9', 'hex')); 108 | expect(config71!.externalChainAddress).toEqual(Buffer.from('000000000000000000000000582d872a1b094fc48f5de31d3b73f2d9be47def1', 'hex')); 109 | 110 | }); 111 | 112 | it('should not reise error when loading full config', async () => { 113 | const serializedConfigsCell = (await client.getConfig(KNOWN_BLOCK)).config.cell; 114 | parseFullConfig(loadConfigParamsAsSlice(serializedConfigsCell)); 115 | }); 116 | }); -------------------------------------------------------------------------------- /src/elector/ElectorContract.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { createTestClient4 } from "../utils/createTestClient4"; 10 | import { Address } from "@ton/core"; 11 | import { ElectorContract } from "./ElectorContract"; 12 | 13 | const client = createTestClient4("mainnet"); 14 | const KNOWN_BLOCK = 31091335; 15 | const BLOCK_WITH_TWO_PAST_ELECTIONS_ENTRIES = 30910280; 16 | const BLOCK_WITH_COMPLAINTS = 20579335; 17 | const ELECTIONS_ID_WITH_COMPLAUINTS = 1652554259; 18 | const ecTwoPastElectionsEntries = client.openAt(BLOCK_WITH_TWO_PAST_ELECTIONS_ENTRIES, ElectorContract.create()); 19 | const ecKnownBlock = client.openAt(KNOWN_BLOCK, ElectorContract.create()); 20 | const ecWithComaplints = client.openAt(BLOCK_WITH_COMPLAINTS, ElectorContract.create()); 21 | 22 | 23 | 24 | describe('ElectorContract', () => { 25 | 26 | it('should return correct past elections list', async () => { 27 | expect(await ecTwoPastElectionsEntries.getPastElectionsList()).toEqual([ 28 | { id: 1688555272, unfreezeAt: 1688653586, stakeHeld: 32768 }, 29 | { id: 1688620808, unfreezeAt: 1688719112, stakeHeld: 32768 } 30 | ]); 31 | }); 32 | 33 | it('should return correct past elections records', async () => { 34 | const pastElections = await ecTwoPastElectionsEntries.getPastElections(); 35 | 36 | expect(pastElections[0].id).toEqual(1688555272); 37 | expect(pastElections[0].unfreezeAt).toEqual(1688653586); 38 | expect(pastElections[0].stakeHeld).toEqual(32768); 39 | expect(pastElections[0].totalStake).toEqual(223347831720943192n); 40 | expect(pastElections[0].bonuses).toEqual(53066684997045n); 41 | const knownFrozenValue0 = pastElections[0].frozen.get('12697811587540651918746850816771244166804229135431506663207437025351429985'); 42 | expect(knownFrozenValue0!["address"].equals(Address.parse('Ef-vmU4VjsKZhFfEvB-N_fXY8zcyH4ih6n9DcMtIAsy3YezN'))).toBe(true); 43 | expect(knownFrozenValue0!["weight"]).toEqual(4395984999565357n); 44 | expect(knownFrozenValue0!["stake"]).toEqual(851605000000000n); 45 | 46 | expect(pastElections[1].id).toEqual(1688620808); 47 | expect(pastElections[1].unfreezeAt).toEqual(1688719112); 48 | expect(pastElections[1].stakeHeld).toEqual(32768); 49 | expect(pastElections[1].totalStake).toEqual(223158712619365653n); 50 | expect(pastElections[1].bonuses).toEqual(15934890731182n); 51 | const knownFrozenValue1 = pastElections[1].frozen.get('216824161582481026645351194108767366817492989435791853445305829924424560264'); 52 | expect(knownFrozenValue1!["address"].equals(Address.parse('Ef_9j3g_jktlWpkCvQaEZ0qZ8qJH_fvyehUEAh0h5hZ1hCD6'))).toBe(true); 53 | expect(knownFrozenValue1!["weight"]).toEqual(2114850227378530n); 54 | expect(knownFrozenValue1!["stake"]).toEqual(409348990576338n); 55 | }); 56 | 57 | it('should return correct election entities', async () => { 58 | const electionEntities = await ecKnownBlock.getElectionEntities(); 59 | 60 | expect(electionEntities!.minStake).toEqual(300000000000000n); 61 | expect(electionEntities!.allStakes).toEqual(237218561486530661n); 62 | expect(electionEntities!.endElectionsTime).toEqual(1689267976); 63 | expect(electionEntities!.startWorkTime).toEqual(1689276168); 64 | expect(electionEntities!.entities[0].pubkey).toEqual(Buffer.from('020a19785bb59d046bf1e62745263cf2cc91e5a47db997249b60c159b19443e7', 'hex')); 65 | expect(electionEntities!.entities[0].stake).toEqual(380271797094836n); 66 | expect(electionEntities!.entities[0].address.equals(Address.parse('Ef8W1vCpA1tr9xr6QSXSxcVSdn1Sm7SYX_PCWQdClaWhales'))).toBe(true); 67 | expect(electionEntities!.entities[0].adnl).toEqual(Buffer.from('1e7a93ab3274c5367c6ab8ea77790ef69df9af53657aa9da883238013aa7c03a', 'hex')); 68 | 69 | }); 70 | 71 | it('should return correct election entities', async () => { 72 | const complaints = await ecWithComaplints.getComplaints(ELECTIONS_ID_WITH_COMPLAUINTS); 73 | expect(complaints[0].rewardAddress.equals(Address.parse('Ef9X6ObXojpUZza3NiS2TnRJ4KR7ler8cOjMRBt_swy4Qp2j'))).toBe(true); 74 | const actual = []; 75 | for (let index = 0; index < complaints.length; index++) { 76 | const i = complaints[index]; 77 | actual.push( 78 | { 79 | id: i.id, 80 | publicKey: i.publicKey, 81 | createdAt: i.createdAt, 82 | severity: i.severity, 83 | paid: i.paid, 84 | suggestedFine: i.suggestedFine, 85 | suggestedFinePart: i.suggestedFinePart, 86 | remainingWeight: i.remainingWeight, 87 | vsetId: i.vsetId 88 | }) 89 | } 90 | 91 | let reference = [ 92 | { 93 | id: 379521005702848989643384193113797265097098487558039864532068095002368386347n, 94 | publicKey: Buffer.from('acacc7367fc6e8f3e82bb28d839361ee66f34f1e340eed8c82b169f2445ad3d5', 'hex'), 95 | createdAt: 1652619999, 96 | severity: 2, 97 | paid: 73166159686n, 98 | suggestedFine: 101000000000n, 99 | suggestedFinePart: 0n, 100 | remainingWeight: -359195385677765603n, 101 | vsetId: 14191242232923186170167014319574873013310876234686300899233319663346106480898n 102 | }, 103 | { 104 | id: 93107436140086431965669461777665246404945492454841054914941956954309397807780n, 105 | publicKey: Buffer.from('f5d09e351ca99e8850f393f294cc1ea7ae1ee73685fdec549903f8b7cadac48c', 'hex'), 106 | createdAt: 1652619993, 107 | severity: 1, 108 | paid: 73342231921n, 109 | suggestedFine: 101000000000n, 110 | suggestedFinePart: 0n, 111 | remainingWeight: -359195385677765603n, 112 | vsetId: 14191242232923186170167014319574873013310876234686300899233319663346106480898n 113 | } 114 | ] 115 | 116 | expect(reference).toEqual(actual); 117 | }); 118 | }); -------------------------------------------------------------------------------- /src/elector/ElectorContract.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell, Contract, TupleReader, TupleBuilder, Dictionary, DictionaryValue, Slice, Builder, ContractProvider } from "@ton/core"; 2 | 3 | 4 | const FrozenDictValue: DictionaryValue<{ address: Address, weight: bigint, stake: bigint }> = { 5 | serialize(src: any, builder: Builder): void { 6 | throw Error("not implemented") 7 | }, 8 | parse(src: Slice): { address: Address, weight: bigint, stake: bigint } { 9 | const address = new Address(-1, src.loadBuffer(32)); 10 | const weight = src.loadUintBig(64); 11 | const stake = src.loadCoins(); 12 | return { address, weight, stake} 13 | } 14 | } 15 | 16 | const EntitiesDictValue: DictionaryValue<{ stake: bigint, address: Address, adnl: Buffer }> = { 17 | serialize(src: any, builder: Builder): void { 18 | throw Error("not implemented") 19 | }, 20 | parse(src: Slice): { stake: bigint, address: Address, adnl: Buffer } { 21 | const stake = src.loadCoins(); 22 | // skip time and maxFactor 23 | src.skip(64); 24 | const address = new Address(-1, src.loadBuffer(32)); 25 | const adnl = src.loadBuffer(32); 26 | return { stake, address, adnl} 27 | } 28 | } 29 | 30 | 31 | export class ElectorContract implements Contract { 32 | 33 | // Please note that we are NOT loading address from config to avoid mistake and send validator money to a wrong contract 34 | readonly address: Address = Address.parseRaw('-1:3333333333333333333333333333333333333333333333333333333333333333'); 35 | //readonly source: ContractSource = new UnknownContractSource('org.ton.elector', -1, 'Elector Contract'); 36 | 37 | static create() { 38 | return new ElectorContract(); 39 | } 40 | 41 | constructor() { 42 | } 43 | 44 | 45 | async getReturnedStake(provider: ContractProvider, address: Address): Promise { 46 | if (address.workChain !== -1) { 47 | throw Error('Only masterchain addresses could have stake'); 48 | } 49 | const res = await provider.get('compute_returned_stake', [{ type: 'int', value: BigInt('0x' + address.hash.toString('hex')) }]); 50 | return res.stack.readBigNumber(); 51 | } 52 | 53 | async getPastElectionsList(provider: ContractProvider) { 54 | const res = await provider.get('past_elections_list', []); 55 | const electionsListRaw = new TupleReader(res.stack.readLispList()); 56 | 57 | const elections: { id: number, unfreezeAt: number, stakeHeld: number }[] = []; 58 | 59 | while (electionsListRaw.remaining > 0) { 60 | const electionsListEntry = electionsListRaw.readTuple(); 61 | const id = electionsListEntry.readNumber(); 62 | const unfreezeAt = electionsListEntry.readNumber(); 63 | electionsListEntry.pop(); // Ignore vset_hash 64 | const stakeHeld = electionsListEntry.readNumber(); 65 | elections.push({ id, unfreezeAt, stakeHeld }); 66 | } 67 | return elections; 68 | } 69 | 70 | async getPastElections(provider: ContractProvider) { 71 | const res = await provider.get('past_elections', []); 72 | const electionsRaw = new TupleReader(res.stack.readLispList()); 73 | 74 | const elections: { id: number, unfreezeAt: number, stakeHeld: number, totalStake: bigint, bonuses: bigint, frozen: Map }[] = []; 75 | 76 | while (electionsRaw.remaining > 0) { 77 | const electionsEntry = electionsRaw.readTuple(); 78 | const id = electionsEntry.readNumber(); 79 | const unfreezeAt = electionsEntry.readNumber(); 80 | const stakeHeld = electionsEntry.readNumber(); 81 | electionsEntry.pop(); // Ignore vset_hash 82 | const frozenDict = electionsEntry.readCell(); 83 | const totalStake = electionsEntry.readBigNumber(); 84 | const bonuses = electionsEntry.readBigNumber(); 85 | let frozen: Map = new Map(); 86 | const frozenData = frozenDict.beginParse().loadDictDirect( 87 | Dictionary.Keys.Buffer(32), 88 | FrozenDictValue 89 | ); 90 | for (const [key, value] of frozenData) { 91 | frozen.set( 92 | BigInt("0x" + key.toString("hex")).toString(10), 93 | { address: value["address"], weight: value["weight"], stake: value["stake"]} 94 | ) 95 | } 96 | elections.push({ id, unfreezeAt, stakeHeld, totalStake, bonuses, frozen }); 97 | } 98 | return elections; 99 | } 100 | 101 | async getElectionEntities(provider: ContractProvider) { 102 | 103 | // 104 | // NOTE: this method doesn't call get method since for some reason it doesn't work 105 | // 106 | 107 | const account = await provider.getState() 108 | 109 | if (account.state.type !== 'active') { 110 | throw Error('Unexpected error'); 111 | } 112 | const cell = Cell.fromBoc(account.state.data! as Buffer)[0]; 113 | const cs = cell.beginParse(); 114 | if (!cs.loadBit()) { 115 | return null; 116 | } 117 | // (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1)); 118 | const sc = cs.loadRef().beginParse(); 119 | const startWorkTime = sc.loadUint(32); 120 | const endElectionsTime = sc.loadUint(32); 121 | const minStake = sc.loadCoins(); 122 | const allStakes = sc.loadCoins(); 123 | // var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256)); 124 | const entitiesData = sc.loadDict(Dictionary.Keys.Buffer(32), EntitiesDictValue); 125 | let entities: { pubkey: Buffer, stake: bigint, address: Address, adnl: Buffer }[] = []; 126 | // const failed = sc.loadBit(); 127 | // const finished = sc.loadBit(); 128 | 129 | if (entitiesData) { 130 | for (const [key, value] of entitiesData) { 131 | entities.push({ pubkey: key, stake: value["stake"], address: value["address"], adnl: value["adnl"] }); 132 | } 133 | } 134 | return { minStake, allStakes, endElectionsTime, startWorkTime, entities }; 135 | } 136 | 137 | // possible code for fetching data via get method if it is possible to set gas limit by request 138 | // async getElectionEntities(block: number) { 139 | 140 | // const res = await this.client.runMethod(block, this.address, 'participant_list_extended'); 141 | // if (res.exitCode !== 0 && res.exitCode !== 1) { 142 | // throw Error('Exit code: ' + res.exitCode); 143 | // } 144 | 145 | // let tuple = new TupleReader(res.result); 146 | // const startWorkTime = tuple.readNumber(); 147 | // const endElectionsTime = tuple.readNumber(); 148 | // const minStake = tuple.readBigNumber(); 149 | // const allStakes = tuple.readBigNumber(); 150 | // let entriesTuple = tuple.readTuple(); 151 | // const entriesRaw = new TupleReader(entriesTuple.readLispList()); 152 | // let entities: { pubkey: Buffer, stake: bigint, address: Address, adnl: Buffer }[] = []; 153 | // while (entriesRaw.remaining > 0) { 154 | // const electionsEntry = entriesRaw.readTuple(); 155 | // const pubkey = electionsEntry.readBuffer(); 156 | // const stake = electionsEntry.readBigNumber(); 157 | // const address = electionsEntry.readAddress(); 158 | // const adnl = electionsEntry.readBuffer(); 159 | // entities.push({ pubkey, stake, address, adnl }); 160 | // } 161 | 162 | 163 | // return { minStake, allStakes, endElectionsTime, startWorkTime, entities }; 164 | // } 165 | 166 | async getActiveElectionId(provider: ContractProvider) { 167 | const res = await provider.get('active_election_id', []); 168 | const electionId = res.stack.readNumber(); 169 | return electionId > 0 ? electionId : null; 170 | } 171 | 172 | async getComplaints(provider: ContractProvider, electionId: number) { 173 | const b = new TupleBuilder(); 174 | b.writeNumber(electionId); 175 | const res = await provider.get('list_complaints', b.build()); 176 | if (res.stack.peek().type === 'null') { 177 | return [] 178 | } 179 | //let tuple = new TupleReader(res.result); 180 | const complaintsRaw = new TupleReader(res.stack.readLispList()); 181 | 182 | const results: { 183 | id: bigint, 184 | publicKey: Buffer, 185 | createdAt: number, 186 | severity: number, 187 | paid: bigint, 188 | suggestedFine: bigint, 189 | suggestedFinePart: bigint, 190 | rewardAddress: Address, 191 | votes: number[], 192 | remainingWeight: bigint, 193 | vsetId: bigint 194 | }[] = []; 195 | 196 | while (complaintsRaw.remaining > 0) { 197 | const complaintsEntry = complaintsRaw.readTuple(); 198 | const id = complaintsEntry.readBigNumber(); 199 | const completeUnpackedComplaint = complaintsEntry.readTuple(); 200 | const unpackedComplaints = completeUnpackedComplaint.readTuple(); 201 | const publicKey = Buffer.from(unpackedComplaints.readBigNumber().toString(16), 'hex'); 202 | // prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block) 203 | // prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo; 204 | // no_blk_gen from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr; 205 | // no_blk_gen_diff prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr; 206 | const description = unpackedComplaints.readCell(); 207 | const createdAt = unpackedComplaints.readNumber(); 208 | const severity = unpackedComplaints.readNumber(); 209 | const rewardAddress = new Address(-1, Buffer.from(unpackedComplaints.readBigNumber().toString(16), 'hex')); 210 | const paid = unpackedComplaints.readBigNumber(); 211 | const suggestedFine = unpackedComplaints.readBigNumber(); 212 | const suggestedFinePart = unpackedComplaints.readBigNumber(); 213 | const votes: number[] = []; 214 | const votersListRaw = new TupleReader(completeUnpackedComplaint.readLispList()); 215 | while (votersListRaw.remaining > 0) { 216 | votes.push(votersListRaw.readNumber()); 217 | } 218 | const vsetId = completeUnpackedComplaint.readBigNumber(); 219 | const remainingWeight = completeUnpackedComplaint.readBigNumber(); 220 | 221 | results.push({ 222 | id, 223 | publicKey, 224 | createdAt, 225 | severity, 226 | paid, 227 | suggestedFine, 228 | suggestedFinePart, 229 | rewardAddress, 230 | votes, 231 | remainingWeight, 232 | vsetId 233 | }); 234 | } 235 | return results 236 | } 237 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | export * from '@ton/core'; 10 | 11 | // 12 | // toncenter Client 13 | // 14 | 15 | export { HttpApi, HttpApiParameters } from './client/api/HttpApi'; 16 | export { TonClient, TonClientParameters } from './client/TonClient'; 17 | 18 | // 19 | // API V4 Client 20 | // 21 | 22 | export { 23 | TonClient4, 24 | TonClient4Parameters 25 | } from './client/TonClient4'; 26 | 27 | // 28 | // Wallets 29 | // 30 | 31 | export { WalletContractV1R1 } from './wallets/WalletContractV1R1'; 32 | export { WalletContractV1R2 } from './wallets/WalletContractV1R2'; 33 | export { WalletContractV1R3 } from './wallets/WalletContractV1R3'; 34 | export { WalletContractV2R1 } from './wallets/WalletContractV2R1'; 35 | export { WalletContractV2R2 } from './wallets/WalletContractV2R2'; 36 | export { WalletContractV3R1 } from './wallets/WalletContractV3R1'; 37 | export { WalletContractV3R2 } from './wallets/WalletContractV3R2'; 38 | export { WalletContractV4 } from './wallets/WalletContractV4'; 39 | export { WalletContractV5Beta } from './wallets/WalletContractV5Beta'; 40 | export { WalletContractV5R1 } from './wallets/WalletContractV5R1'; 41 | 42 | // 43 | // Jettons 44 | // 45 | 46 | export { JettonMaster } from './jetton/JettonMaster'; 47 | export { JettonWallet } from './jetton/JettonWallet'; 48 | 49 | // 50 | // Multisig 51 | // 52 | 53 | export { MultisigOrder } from './multisig/MultisigOrder'; 54 | export { MultisigOrderBuilder } from './multisig/MultisigOrderBuilder' 55 | export { MultisigWallet } from './multisig/MultisigWallet' 56 | 57 | // 58 | // Elector 59 | // 60 | 61 | export { ElectorContract } from './elector/ElectorContract' 62 | 63 | // 64 | // Config 65 | // 66 | 67 | export { GasLimitsPrices, StoragePrices, MsgPrices, WorkchainDescriptor, 68 | configParse5, configParse8, configParse12, configParse13, 69 | configParse15, configParse16, configParse17, configParse18, 70 | configParse28, configParse29, configParse40, 71 | configParseBridge, configParseGasLimitsPrices, configParseMasterAddress, 72 | configParseMasterAddressRequired, configParseMsgPrices, 73 | configParseValidatorSet, configParseWorkchainDescriptor, 74 | parseBridge, parseProposalSetup, parseValidatorSet, parseVotingSetup, 75 | parseFullConfig, 76 | loadConfigParamById, loadConfigParamsAsSlice } from './config/ConfigParser' 77 | 78 | // 79 | // Fees 80 | // 81 | 82 | export { computeExternalMessageFees, computeFwdFees, computeGasPrices, computeMessageForwardFees, computeStorageFees } from './utils/fees'; -------------------------------------------------------------------------------- /src/jetton/JettonMaster.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address } from "@ton/core"; 10 | import { createTestClient } from "../utils/createTestClient"; 11 | import { JettonMaster } from "./JettonMaster"; 12 | import { JettonWallet } from "./JettonWallet"; 13 | 14 | describe('JettonMaster', () => { 15 | it('should resolve jetton wallet address', async () => { 16 | let client = createTestClient('mainnet'); 17 | let master = client.open(JettonMaster.create(Address.parse('EQDQoc5M3Bh8eWFephi9bClhevelbZZvWhkqdo80XuY_0qXv'))); 18 | let walletAddress = await master.getWalletAddress(Address.parse('EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C')); 19 | let jettonData = await master.getJettonData(); 20 | expect(walletAddress.equals(Address.parse('EQDslTlGmbLTFi0j4MPT7UVggWR7XRDI2bW6vmNG6Tc_FBDE'))).toBe(true); 21 | expect(jettonData.mintable).toBe(true); 22 | expect(jettonData.adminAddress.equals(Address.parse('EQCppzUtmGSMg3FIRlFLzhToqbaC0xjmjzOn0o7H4M8Aua1t'))).toBe(true); 23 | 24 | let wallet = client.open(JettonWallet.create(walletAddress)); 25 | let balance = await wallet.getBalance(); 26 | expect(balance).toBe(0n); 27 | }); 28 | }); -------------------------------------------------------------------------------- /src/jetton/JettonMaster.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Contract, ContractProvider } from "@ton/core"; 10 | 11 | export class JettonMaster implements Contract { 12 | 13 | static create(address: Address) { 14 | return new JettonMaster(address); 15 | } 16 | 17 | readonly address: Address; 18 | 19 | constructor(address: Address) { 20 | this.address = address; 21 | } 22 | 23 | async getWalletAddress(provider: ContractProvider, owner: Address) { 24 | let res = await provider.get('get_wallet_address', [{ type: 'slice', cell: beginCell().storeAddress(owner).endCell() }]); 25 | return res.stack.readAddress(); 26 | } 27 | 28 | async getJettonData(provider: ContractProvider) { 29 | let res = await provider.get('get_jetton_data', []); 30 | let totalSupply = res.stack.readBigNumber(); 31 | let mintable = res.stack.readBoolean(); 32 | let adminAddress = res.stack.readAddress(); 33 | let content = res.stack.readCell(); 34 | let walletCode = res.stack.readCell(); 35 | return { 36 | totalSupply, 37 | mintable, 38 | adminAddress, 39 | content, 40 | walletCode 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /src/jetton/JettonWallet.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, Contract, ContractProvider } from "@ton/core"; 10 | 11 | export class JettonWallet implements Contract { 12 | 13 | static create(address: Address) { 14 | return new JettonWallet(address); 15 | } 16 | 17 | readonly address: Address; 18 | 19 | private constructor(address: Address) { 20 | this.address = address; 21 | } 22 | 23 | async getBalance(provider: ContractProvider) { 24 | let state = await provider.getState(); 25 | if (state.state.type !== 'active') { 26 | return 0n; 27 | } 28 | let res = await provider.get('get_wallet_data', []); 29 | return res.stack.readBigNumber(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/multisig/MultisigOrder.ts: -------------------------------------------------------------------------------- 1 | /* Made by @Gusarich and @Miandic */ 2 | 3 | import { sign, signVerify } from '@ton/crypto'; 4 | import { beginCell, Cell } from '@ton/core'; 5 | import { MultisigWallet } from './MultisigWallet'; 6 | 7 | export class MultisigOrder { 8 | public readonly payload: Cell; 9 | public signatures: { [key: number]: Buffer } = {}; 10 | 11 | private constructor(payload: Cell) { 12 | this.payload = payload; 13 | } 14 | 15 | public static fromCell(cell: Cell): MultisigOrder { 16 | let s = cell.beginParse(); 17 | let signatures = s.loadMaybeRef()?.beginParse(); 18 | const messagesCell = s.asCell(); 19 | 20 | let order = new MultisigOrder(messagesCell); 21 | 22 | if (signatures) { 23 | while (signatures.remainingBits > 0) { 24 | const signature = signatures.loadBuffer(64); 25 | const ownerId = signatures.loadUint(8); 26 | order.signatures[ownerId] = signature; 27 | if (signatures.remainingRefs > 0) { 28 | signatures = signatures.loadRef().asSlice(); 29 | } else { 30 | signatures.skip(1); 31 | } 32 | } 33 | signatures.endParse(); 34 | } 35 | 36 | return order; 37 | } 38 | 39 | public static fromPayload(payload: Cell): MultisigOrder { 40 | return new MultisigOrder(payload); 41 | } 42 | 43 | public addSignature( 44 | ownerId: number, 45 | signature: Buffer, 46 | multisig: MultisigWallet 47 | ) { 48 | const signingHash = this.payload.hash(); 49 | if ( 50 | !signVerify( 51 | signingHash, 52 | signature, 53 | multisig.owners.get(ownerId)!.slice(0, -1) 54 | ) 55 | ) { 56 | throw Error('invalid signature'); 57 | } 58 | this.signatures[ownerId] = signature; 59 | } 60 | 61 | public sign(ownerId: number, secretKey: Buffer) { 62 | const signingHash = this.payload.hash(); 63 | this.signatures[ownerId] = sign(signingHash, secretKey); 64 | return signingHash; 65 | } 66 | 67 | public unionSignatures(other: MultisigOrder) { 68 | this.signatures = Object.assign({}, this.signatures, other.signatures); 69 | } 70 | 71 | public clearSignatures() { 72 | this.signatures = {}; 73 | } 74 | 75 | public toCell(ownerId: number): Cell { 76 | let b = beginCell().storeBit(0); 77 | for (const ownerId in this.signatures) { 78 | const signature = this.signatures[ownerId]; 79 | b = beginCell() 80 | .storeBit(1) 81 | .storeRef( 82 | beginCell() 83 | .storeBuffer(signature) 84 | .storeUint(parseInt(ownerId), 8) 85 | .storeBuilder(b) 86 | .endCell() 87 | ); 88 | } 89 | 90 | return beginCell() 91 | .storeUint(ownerId, 8) 92 | .storeBuilder(b) 93 | .storeBuilder(this.payload.asBuilder()) 94 | .endCell(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/multisig/MultisigOrderBuilder.ts: -------------------------------------------------------------------------------- 1 | /* Made by @Gusarich and @Miandic */ 2 | 3 | import { 4 | beginCell, 5 | Builder, 6 | MessageRelaxed, 7 | storeMessageRelaxed, 8 | } from '@ton/core'; 9 | import { MultisigOrder } from './MultisigOrder'; 10 | 11 | export class MultisigOrderBuilder { 12 | public messages: Builder = beginCell(); 13 | public queryId: bigint = 0n; 14 | private walletId: number; 15 | private queryOffset: number; 16 | 17 | constructor(walletId: number, offset?: number) { 18 | this.walletId = walletId; 19 | this.queryOffset = offset || 7200; 20 | } 21 | 22 | public addMessage(message: MessageRelaxed, mode: number) { 23 | if (this.messages.refs >= 4) { 24 | throw Error('only 4 refs are allowed'); 25 | } 26 | this.updateQueryId(); 27 | this.messages.storeUint(mode, 8); 28 | this.messages.storeRef( 29 | beginCell().store(storeMessageRelaxed(message)).endCell() 30 | ); 31 | } 32 | 33 | public clearMessages() { 34 | this.messages = beginCell(); 35 | } 36 | 37 | public build() { 38 | return MultisigOrder.fromPayload( 39 | beginCell() 40 | .storeUint(this.walletId, 32) 41 | .storeUint(this.queryId, 64) 42 | .storeBuilder(this.messages) 43 | .endCell() 44 | ); 45 | } 46 | 47 | private updateQueryId() { 48 | const time = BigInt(Math.floor(Date.now() / 1000 + this.queryOffset)); 49 | this.queryId = time << 32n; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/multisig/MultisigWallet.ts: -------------------------------------------------------------------------------- 1 | /* Made by @Gusarich and @Miandic */ 2 | 3 | import { TonClient } from '../index'; 4 | import { keyPairFromSecretKey, sign } from '@ton/crypto'; 5 | import { 6 | Address, 7 | beginCell, 8 | Cell, 9 | contractAddress, 10 | ContractProvider, 11 | Dictionary, 12 | Sender, 13 | SendMode, 14 | Slice, 15 | StateInit, 16 | } from '@ton/core'; 17 | import { MultisigOrder } from './MultisigOrder'; 18 | 19 | const MULTISIG_CODE = Cell.fromBase64( 20 | 'te6ccgECKwEABBgAART/APSkE/S88sgLAQIBIAIDAgFIBAUE2vIgxwCOgzDbPOCDCNcYIPkBAdMH2zwiwAAToVNxePQOb6Hyn9s8VBq6+RDyoAb0BCD5AQHTH1EYuvKq0z9wUwHwCgHCCAGDCryx8mhTFYBA9A5voSCYDqQgwgryZw7f+COqH1NAufJhVCOjU04gIyEiAgLMBgcCASAMDQIBIAgJAgFmCgsAA9GEAiPymAvHoHN9CYbZ5S7Z4BPHohwhJQAtAKkItdJEqCTItdKlwLUAdAT8ArobBKAATwhbpEx4CBukTDgAdAg10rDAJrUAvALyFjPFszJ4HHXI8gBzxb0AMmACASAODwIBIBQVARW77ZbVA0cFUg2zyCoCAUgQEQIBIBITAXOxHXQgwjXGCD5AQHTB4IB1MTtQ9hTIHj0Dm+h8p/XC/9eMfkQ8qCuAfQEIW6TW3Ey4PkBWNs8AaQBgJwA9rtqA6ADoAPoCAXoCEfyAgPyA3XlP+AXkegAA54tkwAAXrhlXP8EA1WZ2oexAAgEgFhcCASAYGQFRtyVbZ4YmRmpGEAgegc30McJNhFpAADMaYeYuAFrgJhwLb+4cC3d0bhAjAYm1WZtnhqvgb+2xxsoicAgej430pBHEoFpAADHDhBACGuQkuuBk9kUWE5kAOeLKhACQCB6IYFImHFImHFImXEA2YlzNijAjAgEgGhsAF7UGtc4QQDVZnah7EAIBIBwdAgOZOB4fARGsGm2eL4G2CUAjABWt+UEAzJV2oewYQAENqTbPBVfBYCMAFa3f3CCAarM7UPYgAiDbPALyZfgAUENxQxPbPO1UIyoACtP/0wcwBKDbPC+uUyCw8mISsQKkJbNTHLmwJYEA4aojoCi8sPJpggGGoPgBBZcCERACPj4wjo0REB/bPEDXePRDEL0F4lQWW1Rz51YQU9zbPFRxClR6vCQlKCYAIO1E0NMf0wfTB9M/9AT0BNEAXgGOGjDSAAHyo9MH0wdQA9cBIPkBBfkBFbrypFAD4GwhIddKqgIi10m68qtwVCATAAwByMv/ywcE1ts87VT4D3AlblOJvrGYEG4QLVDHXwePGzBUJANQTds8UFWgRlAQSRA6SwlTuds8UFQWf+L4AAeDJaGOLCaAQPSWb6UglDBTA7neII4WODk5CNIAAZfTBzAW8AcFkTDifwgHBZJsMeKz5jAGKicoKQBgcI4pA9CDCNcY0wf0BDBTFnj0Dm+h8qXXC/9URUT5EPKmrlIgsVIDvRShI27mbCIyAH5SML6OIF8D+ACTItdKmALTB9QC+wAC6DJwyMoAQBSAQPRDAvAHjhdxyMsAFMsHEssHWM8BWM8WQBOAQPRDAeIBII6KEEUQNEMA2zztVJJfBuIqABzIyx/LB8sHyz/0APQAyQ==' 21 | ); 22 | 23 | export class MultisigWallet { 24 | public owners: Dictionary; 25 | public workchain: number; 26 | public walletId: number; 27 | public k: number; 28 | public address: Address; 29 | public provider: ContractProvider | null = null; 30 | public init: StateInit; 31 | 32 | constructor( 33 | publicKeys: Buffer[], 34 | workchain: number, 35 | walletId: number, 36 | k: number, 37 | opts?: { 38 | address?: Address; 39 | provider?: ContractProvider; 40 | client?: TonClient; 41 | } 42 | ) { 43 | this.owners = Dictionary.empty(); 44 | this.workchain = workchain; 45 | this.walletId = walletId; 46 | this.k = k; 47 | for (let i = 0; i < publicKeys.length; i += 1) { 48 | this.owners.set(i, Buffer.concat([publicKeys[i], Buffer.alloc(1)])); 49 | } 50 | this.init = { 51 | code: MULTISIG_CODE, 52 | data: beginCell() 53 | .storeUint(this.walletId, 32) 54 | .storeUint(this.owners.size, 8) 55 | .storeUint(this.k, 8) 56 | .storeUint(0, 64) 57 | .storeDict( 58 | this.owners, 59 | Dictionary.Keys.Uint(8), 60 | Dictionary.Values.Buffer(33) 61 | ) 62 | .storeBit(0) 63 | .endCell(), 64 | }; 65 | this.address = opts?.address || contractAddress(workchain, this.init); 66 | if (opts?.provider) { 67 | this.provider = opts.provider; 68 | } else if (opts?.client) { 69 | this.provider = opts.client.provider(this.address, { 70 | code: this.init.code!, 71 | data: this.init.data!, 72 | }); 73 | } 74 | } 75 | 76 | static async fromAddress( 77 | address: Address, 78 | opts: { provider?: ContractProvider; client?: TonClient } 79 | ): Promise { 80 | let provider: ContractProvider; 81 | if (opts.provider) { 82 | provider = opts.provider; 83 | } else { 84 | if (!opts.client) { 85 | throw Error('Either provider or client must be specified'); 86 | } 87 | provider = opts.client.provider(address, { 88 | code: null, 89 | data: null, 90 | }); 91 | } 92 | 93 | const contractState = (await provider.getState()).state; 94 | if (contractState.type !== 'active') { 95 | throw Error('Contract must be active'); 96 | } 97 | 98 | const data: Slice = Cell.fromBoc(contractState.data!)[0].beginParse(); 99 | const walletId: number = data.loadUint(32); 100 | data.skip(8); 101 | const k: number = data.loadUint(8); 102 | data.skip(64); 103 | const owners = data.loadDict( 104 | Dictionary.Keys.Uint(8), 105 | Dictionary.Values.Buffer(33) 106 | ); 107 | let publicKeys: Buffer[] = []; 108 | for (const [key, value] of owners) { 109 | const publicKey = value.subarray(0, 32); 110 | publicKeys.push(publicKey); 111 | } 112 | 113 | return new MultisigWallet(publicKeys, address.workChain, walletId, k, { 114 | address, 115 | provider, 116 | client: opts.client, 117 | }); 118 | } 119 | 120 | public async deployExternal(provider?: ContractProvider) { 121 | if (!provider && !this.provider) { 122 | throw Error('you must specify provider if there is no such property in MultisigWallet instance'); 123 | } 124 | if (!provider) { 125 | provider = this.provider!; 126 | } 127 | await provider.external(Cell.EMPTY); 128 | } 129 | 130 | public async deployInternal(sender: Sender, value: bigint = 1000000000n) { 131 | await sender.send({ 132 | sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, 133 | to: this.address, 134 | value: value, 135 | init: this.init, 136 | body: Cell.EMPTY, 137 | bounce: true, 138 | }); 139 | } 140 | 141 | public async sendOrder( 142 | order: MultisigOrder, 143 | secretKey: Buffer, 144 | provider?: ContractProvider 145 | ) { 146 | if (!provider && !this.provider) { 147 | throw Error('you must specify provider if there is no such property in MultisigWallet instance'); 148 | } 149 | if (!provider) { 150 | provider = this.provider!; 151 | } 152 | 153 | let publicKey: Buffer = keyPairFromSecretKey(secretKey).publicKey; 154 | let ownerId: number = this.getOwnerIdByPubkey(publicKey); 155 | let cell = order.toCell(ownerId); 156 | 157 | let signature = sign(cell.hash(), secretKey); 158 | cell = beginCell() 159 | .storeBuffer(signature) 160 | .storeSlice(cell.asSlice()) 161 | .endCell(); 162 | 163 | await provider.external(cell); 164 | } 165 | 166 | public async sendOrderWithoutSecretKey( 167 | order: MultisigOrder, 168 | signature: Buffer, 169 | ownerId: number, 170 | provider?: ContractProvider 171 | ) { 172 | if (!provider && !this.provider) { 173 | throw Error( 174 | 'you must specify provider if there is no such property in MultisigWallet instance' 175 | ); 176 | } 177 | if (!provider) { 178 | provider = this.provider!; 179 | } 180 | 181 | let cell = order.toCell(ownerId); 182 | 183 | cell = beginCell() 184 | .storeBuffer(signature) 185 | .storeSlice(cell.asSlice()) 186 | .endCell(); 187 | 188 | await provider.external(cell); 189 | } 190 | 191 | public getOwnerIdByPubkey(publicKey: Buffer) { 192 | for (const [key, value] of this.owners) { 193 | if (value.subarray(0, 32).equals(publicKey)) { 194 | return key; 195 | } 196 | } 197 | throw Error('public key is not an owner'); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/utils/createTestClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { TonClient } from "../client/TonClient"; 10 | 11 | export function createTestClient(net?: 'testnet' | 'mainnet') { 12 | return new TonClient({ 13 | endpoint: net === 'mainnet' ? 'https://mainnet.tonhubapi.com/jsonRPC' : 'https://testnet.toncenter.com/api/v2/jsonRPC', 14 | apiKey: net !== 'mainnet' ? '32df40f4ffc11053334bcdf09c7d3a9e6487ee0cb715edf8cf667c543edb10ca' : undefined 15 | }); 16 | } -------------------------------------------------------------------------------- /src/utils/createTestClient4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { TonClient4 } from "../client/TonClient4"; 10 | 11 | export function createTestClient4(net?: 'testnet' | 'mainnet') { 12 | return new TonClient4({ endpoint: net === 'mainnet' ? 'https://mainnet-v4.tonhubapi.com' : 'https://testnet-v4.tonhubapi.com' }); 13 | } -------------------------------------------------------------------------------- /src/utils/fees.spec.ts: -------------------------------------------------------------------------------- 1 | import { computeStorageFees, computeGasPrices, computeExternalMessageFees, computeMessageForwardFees } from './fees'; 2 | import { Cell, storeMessage, storeMessageRelaxed, external, comment, internal, Address, SendMode, fromNano } from '@ton/core'; 3 | import { WalletContractV4 } from '../wallets/WalletContractV4'; 4 | 5 | describe('estimateFees', () => { 6 | it('should estimate fees correctly', () => { 7 | const config = { 8 | storage: [{ utime_since: 0, bit_price_ps: BigInt(1), cell_price_ps: BigInt(500), mc_bit_price_ps: BigInt(1000), mc_cell_price_ps: BigInt(500000) }], 9 | workchain: { 10 | gas: { flatLimit: BigInt(100), flatGasPrice: BigInt(100000), price: BigInt(65536000) }, 11 | message: { lumpPrice: BigInt(1000000), bitPrice: BigInt(65536000), cellPrice: BigInt(6553600000), firstFrac: 21845 } 12 | }, 13 | }; 14 | 15 | const storageStats = [{ 16 | lastPaid: 1696792239, duePayment: null, 17 | used: { bits: 6888, cells: 14, publicCells: 0 } 18 | }] 19 | 20 | const gasUsageByOutMsgs: { [key: number]: number } = { 1: 3308, 2: 3950, 3: 4592, 4: 5234 }; 21 | 22 | const contract = WalletContractV4.create({ workchain: 0, publicKey: Buffer.from('MUP3GpbKCQu64L4PIU0QprZxmSUygHcaYKuo2tZYA1c=', 'base64') }); 23 | 24 | const body = comment('Test message fees estimation'); 25 | const testAddress = Address.parse('EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N'); 26 | 27 | // Create transfer 28 | let intMessage = internal({ 29 | to: testAddress, 30 | value: 1400000000n, 31 | bounce: true, 32 | body, 33 | }); 34 | 35 | let transfer = contract.createTransfer({ 36 | seqno: 14, 37 | secretKey: Buffer.alloc(64), 38 | sendMode: SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATELY, 39 | messages: [intMessage] 40 | }); 41 | 42 | const externalMessage = external({ 43 | to: contract.address, 44 | body: transfer, 45 | init: null 46 | }); 47 | 48 | let inMsg = new Cell().asBuilder(); 49 | storeMessage(externalMessage)(inMsg); 50 | 51 | let outMsg = new Cell().asBuilder(); 52 | storeMessageRelaxed(intMessage)(outMsg); 53 | 54 | // Storage fees 55 | let storageFees = BigInt(0); 56 | for (let storageStat of storageStats) { 57 | if (storageStat) { 58 | const computed = computeStorageFees({ 59 | lastPaid: storageStat.lastPaid, 60 | masterchain: false, 61 | now: 1697445678, // Mon Oct 16 2023 11:42:56 GMT+0300 62 | special: false, 63 | storagePrices: config.storage, 64 | storageStat: { 65 | bits: storageStat.used.bits, 66 | cells: storageStat.used.cells, 67 | publicCells: storageStat.used.publicCells 68 | } 69 | }); 70 | storageFees = storageFees + computed; 71 | } 72 | } 73 | 74 | expect(fromNano(storageFees)).toBe('0.000138473'); 75 | 76 | // Calculate import fees 77 | let importFees = computeExternalMessageFees(config.workchain.message as any, inMsg.endCell()); 78 | 79 | expect(fromNano(importFees)).toBe('0.001772'); 80 | 81 | // Any transaction use this amount of gas 82 | const gasUsed = gasUsageByOutMsgs[1]; 83 | let gasFees = computeGasPrices( 84 | BigInt(gasUsed), 85 | { flatLimit: config.workchain.gas.flatLimit, flatPrice: config.workchain.gas.flatGasPrice, price: config.workchain.gas.price } 86 | ); 87 | 88 | expect(fromNano(gasFees)).toBe('0.003308'); 89 | 90 | // Total 91 | let total = BigInt(0); 92 | total += storageFees; 93 | total += importFees; 94 | total += gasFees; 95 | 96 | // Forward fees 97 | let fwdFees = computeMessageForwardFees(config.workchain.message as any, outMsg.endCell()); 98 | 99 | expect(fromNano(fwdFees.fees)).toBe('0.000333328'); 100 | 101 | total += fwdFees.fees; 102 | 103 | expect(fromNano(total)).toBe('0.005551801'); 104 | }); 105 | }); -------------------------------------------------------------------------------- /src/utils/fees.ts: -------------------------------------------------------------------------------- 1 | import { Builder, Cell, loadMessageRelaxed, storeStateInit } from '@ton/core'; 2 | import { MsgPrices, StoragePrices } from '../config/ConfigParser'; 3 | 4 | // 5 | // Source: https://github.com/ton-foundation/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/transaction.cpp#L425 6 | // 7 | 8 | export function computeStorageFees(data: { 9 | now: number 10 | lastPaid: number 11 | storagePrices: StoragePrices[] 12 | storageStat: { cells: number, bits: number, publicCells: number } 13 | special: boolean 14 | masterchain: boolean 15 | }) { 16 | const { 17 | lastPaid, 18 | now, 19 | storagePrices, 20 | storageStat, 21 | special, 22 | masterchain 23 | } = data; 24 | if (now <= lastPaid || storagePrices.length === 0 || now < storagePrices[0].utime_since || special) { 25 | return BigInt(0); 26 | } 27 | let upto = Math.max(lastPaid, storagePrices[0].utime_since); 28 | let total = BigInt(0); 29 | for (let i = 0; i < storagePrices.length && upto < now; i++) { 30 | let valid_until = (i < storagePrices.length - 1 ? Math.min(now, storagePrices[i + 1].utime_since) : now); 31 | let payment = BigInt(0); 32 | if (upto < valid_until) { 33 | let delta = valid_until - upto; 34 | payment += (BigInt(storageStat.cells) * (masterchain ? storagePrices[i].mc_cell_price_ps : storagePrices[i].cell_price_ps)); 35 | payment += (BigInt(storageStat.bits) * (masterchain ? storagePrices[i].mc_bit_price_ps : storagePrices[i].bit_price_ps)); 36 | payment = payment * BigInt(delta); 37 | } 38 | upto = valid_until; 39 | total += payment; 40 | } 41 | 42 | return shr16ceil(total); 43 | } 44 | 45 | // 46 | // Source: https://github.com/ton-foundation/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/transaction.cpp#L1218 47 | // 48 | 49 | export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) { 50 | return msgPrices.lumpPrice + (shr16ceil(msgPrices.bitPrice * bits + (msgPrices.cellPrice * cells))); 51 | } 52 | 53 | // 54 | // Source: https://github.com/ton-foundation/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/transaction.cpp#L761 55 | // 56 | 57 | export function computeGasPrices(gasUsed: bigint, prices: { flatLimit: bigint, flatPrice: bigint, price: bigint }) { 58 | if (gasUsed <= prices.flatLimit) { 59 | return prices.flatPrice; 60 | } else { 61 | // td::rshift(gas_price256 * (gas_used - cfg.flat_gas_limit), 16, 1) + cfg.flat_gas_price 62 | return prices.flatPrice + ((prices.price * (gasUsed - prices.flatLimit)) >> 16n); 63 | } 64 | } 65 | 66 | // 67 | // Source: https://github.com/ton-foundation/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/transaction.cpp#L530 68 | // 69 | 70 | export function computeExternalMessageFees(msgPrices: MsgPrices, cell: Cell) { 71 | 72 | // Collect stats 73 | let storageStats = collectCellStats(cell); 74 | storageStats.bits -= cell.bits.length; 75 | storageStats.cells -= 1; 76 | 77 | return computeFwdFees(msgPrices, BigInt(storageStats.cells), BigInt(storageStats.bits)); 78 | } 79 | 80 | export function computeMessageForwardFees(msgPrices: MsgPrices, cell: Cell) { 81 | let msg = loadMessageRelaxed(cell.beginParse()); 82 | let storageStats: { bits: number, cells: number } = { bits: 0, cells: 0 }; 83 | 84 | // Init 85 | if (msg.init) { 86 | const rawBuilder = new Cell().asBuilder(); 87 | storeStateInit(msg.init)(rawBuilder); 88 | const raw = rawBuilder.endCell(); 89 | 90 | let c = collectCellStats(raw); 91 | c.bits -= raw.bits.length; 92 | c.cells -= 1; 93 | storageStats.bits += c.bits; 94 | storageStats.cells += c.cells; 95 | } 96 | 97 | // Body 98 | let bc = collectCellStats(msg.body); 99 | bc.bits -= msg.body.bits.length; 100 | bc.cells -= 1; 101 | storageStats.bits += bc.bits; 102 | storageStats.cells += bc.cells; 103 | 104 | // NOTE: Extra currencies are ignored for now 105 | 106 | let fees = computeFwdFees(msgPrices, BigInt(storageStats.cells), BigInt(storageStats.bits)); 107 | let res = (fees * BigInt(msgPrices.firstFrac)) >> 16n; 108 | let remaining = fees - res; 109 | return { fees: res, remaining }; 110 | } 111 | 112 | function collectCellStats(cell: Cell): { bits: number, cells: number } { 113 | let bits = cell.bits.length; 114 | let cells = 1; 115 | for (let ref of cell.refs) { 116 | let r = collectCellStats(ref); 117 | cells += r.cells; 118 | bits += r.bits; 119 | } 120 | return { bits, cells }; 121 | } 122 | 123 | function shr16ceil(src: bigint) { 124 | let rem = src % 65536n; 125 | let res = src >> 16n; 126 | if (rem !== 0n) { 127 | res += 1n; 128 | } 129 | return res; 130 | } -------------------------------------------------------------------------------- /src/utils/maybe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | export type Maybe = T | null | undefined; -------------------------------------------------------------------------------- /src/utils/randomTestKey.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import Prando from 'prando'; 10 | import { keyPairFromSeed } from '@ton/crypto'; 11 | 12 | export function randomTestKey(seed: string) { 13 | let random = new Prando(seed); 14 | let res = Buffer.alloc(32); 15 | for (let i = 0; i < res.length; i++) { 16 | res[i] = random.nextInt(0, 256); 17 | } 18 | return keyPairFromSeed(res); 19 | } -------------------------------------------------------------------------------- /src/utils/testWallets.ts: -------------------------------------------------------------------------------- 1 | import { OpenedContract } from '@ton/core'; 2 | import { WalletContractV5R1 } from '../wallets/v5r1/WalletContractV5R1'; 3 | import { WalletContractV5Beta } from '../wallets/v5beta/WalletContractV5Beta'; 4 | import { WalletContractV4 } from '../wallets/WalletContractV4'; 5 | import { WalletContractV3R2 } from '../wallets/WalletContractV3R2'; 6 | import { WalletContractV3R1 } from '../wallets/WalletContractV3R1'; 7 | import { WalletContractV2R2 } from '../wallets/WalletContractV2R2'; 8 | import { WalletContractV2R1 } from '../wallets/WalletContractV2R1'; 9 | import { WalletContractV1R2 } from '../wallets/WalletContractV1R2'; 10 | import { WalletContractV1R1 } from '../wallets/WalletContractV1R1'; 11 | 12 | 13 | type WalletContract = WalletContractV5R1 | WalletContractV5Beta | WalletContractV4 | WalletContractV3R2 | WalletContractV3R1 | WalletContractV2R2 | WalletContractV2R1 | WalletContractV1R2 | WalletContractV1R1; 14 | 15 | export const tillNextSeqno = async(wallet: OpenedContract, oldSeqno: number, maxTries: number = 10) => { 16 | let seqNoAfter = oldSeqno; 17 | let tried = 0; 18 | 19 | do { 20 | await new Promise((resolve, reject) => { 21 | setTimeout(resolve, 2000); 22 | }); 23 | seqNoAfter = await wallet.getSeqno(); 24 | if(tried++ > maxTries) { 25 | throw Error("To many retries, transaction likely failed!"); 26 | } 27 | } while(seqNoAfter == oldSeqno); 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | export function exponentialBackoffDelay(currentFailureCount: number, minDelay: number, maxDelay: number, maxFailureCount: number) { 10 | let maxDelayRet = minDelay + ((maxDelay - minDelay) / maxFailureCount) * Math.max(currentFailureCount, maxFailureCount); 11 | return Math.round(Math.random() * maxDelayRet); 12 | } 13 | 14 | export async function delay(ms: number) { 15 | return new Promise(resolve => setTimeout(resolve, ms)); 16 | } 17 | 18 | export function delayBreakable(ms: number) { 19 | // We can cancel delay from outer code 20 | let promiseResolver: ((value?: any | PromiseLike) => void) | null = null; 21 | let resolver = () => { 22 | if (promiseResolver) { 23 | promiseResolver(); 24 | } 25 | }; 26 | let promise = new Promise(resolve => { 27 | promiseResolver = resolve; 28 | setTimeout(resolve, ms); 29 | }); 30 | return { promise, resolver }; 31 | } 32 | 33 | const promise = new Promise(() => { }); 34 | 35 | export function forever() { 36 | return promise; 37 | } 38 | 39 | export async function backoff(callback: () => Promise, log: boolean): Promise { 40 | let currentFailureCount = 0; 41 | const minDelay = 500; 42 | const maxDelay = 15000; 43 | const maxFailureCount = 50; 44 | while (true) { 45 | try { 46 | return await callback(); 47 | } catch (e) { 48 | if (currentFailureCount > 3) { 49 | if (log) { 50 | console.warn(e); 51 | } 52 | } 53 | if (currentFailureCount < maxFailureCount) { 54 | currentFailureCount++; 55 | } 56 | 57 | let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount); 58 | await delay(waitForRequest); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/utils/toUrlSafe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | export function toUrlSafe(src: string) { 10 | while (src.indexOf('/') >= 0) { 11 | src = src.replace('/', '_'); 12 | } 13 | while (src.indexOf('+') >= 0) { 14 | src = src.replace('+', '-'); 15 | } 16 | while (src.indexOf('=') >= 0) { 17 | src = src.replace('=', ''); 18 | } 19 | return src; 20 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R1.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV1R1 } from "./WalletContractV1R1"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV1R1', () => { 16 | 17 | it('should has balance and correct address', async () => { 18 | 19 | // Create contract 20 | let client = createTestClient4(); 21 | let key = randomTestKey('v4-treasure'); 22 | let contract = client.open(WalletContractV1R1.create({ workchain: 0, publicKey: key.publicKey })); 23 | let balance = await contract.getBalance(); 24 | 25 | // Check parameters 26 | expect(contract.address.equals(Address.parse('EQCtW_zzk6n82ebaVQFq8P_04wOemYhtwqMd3NuArmPODRvD'))).toBe(true); 27 | expect(balance > 0n).toBe(true); 28 | }); 29 | 30 | it('should perform transfer', async () => { 31 | // Create contract 32 | let client = createTestClient4(); 33 | let key = randomTestKey('v4-treasure'); 34 | let contract = client.open(WalletContractV1R1.create({ workchain: 0, publicKey: key.publicKey })); 35 | 36 | // Prepare transfer 37 | let seqno = await contract.getSeqno(); 38 | let transfer = contract.createTransfer({ 39 | seqno, 40 | secretKey: key.secretKey, 41 | message: internal({ 42 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 43 | value: '0.1', 44 | body: 'Hello, world!' 45 | }) 46 | }); 47 | 48 | // Perform transfer 49 | await contract.send(transfer); 50 | await tillNextSeqno(contract, seqno); 51 | }); 52 | 53 | it('should perform extra currency transfer', async () => { 54 | // Create contract 55 | let client = createTestClient4(); 56 | let key = randomTestKey('v4-treasure'); 57 | let contract = client.open(WalletContractV1R1.create({ workchain: 0, publicKey: key.publicKey })); 58 | 59 | // Prepare transfer 60 | let seqno = await contract.getSeqno(); 61 | let transfer = contract.createTransfer({ 62 | seqno, 63 | secretKey: key.secretKey, 64 | message: internal({ 65 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 66 | value: '0.01', 67 | extracurrency: {100: BigInt(10 ** 6)}, 68 | body: 'Hello, extra currency v1r1!' 69 | }) 70 | }); 71 | 72 | // Perform transfer 73 | await contract.send(transfer); 74 | await tillNextSeqno(contract, seqno); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV1 } from "./signing/createWalletTransfer"; 12 | 13 | export class WalletContractV1R1 implements Contract { 14 | 15 | static create(args: { workchain: number, publicKey: Buffer }) { 16 | return new WalletContractV1R1(args.workchain, args.publicKey); 17 | } 18 | 19 | readonly workchain: number; 20 | readonly publicKey: Buffer; 21 | readonly address: Address; 22 | readonly init: { data: Cell, code: Cell }; 23 | 24 | private constructor(workchain: number, publicKey: Buffer) { 25 | this.workchain = workchain; 26 | this.publicKey = publicKey; 27 | 28 | // Build initial code and data 29 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEARAAAhP8AIN2k8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVEH98Ik=', 'base64'))[0]; 30 | let data = beginCell() 31 | .storeUint(0, 32) // Seqno 32 | .storeBuffer(publicKey) 33 | .endCell(); 34 | this.init = { code, data }; 35 | this.address = contractAddress(workchain, { code, data }); 36 | } 37 | 38 | /** 39 | * Get Wallet Balance 40 | */ 41 | async getBalance(provider: ContractProvider) { 42 | let state = await provider.getState(); 43 | return state.balance; 44 | } 45 | 46 | /** 47 | * Get Wallet Seqno 48 | */ 49 | async getSeqno(provider: ContractProvider) { 50 | let state = await provider.getState(); 51 | if (state.state.type === 'active') { 52 | return Cell.fromBoc(state.state.data!)[0].beginParse().loadUint(32); 53 | } else { 54 | return 0; 55 | } 56 | } 57 | 58 | /** 59 | * Send signed transfer 60 | */ 61 | async send(provider: ContractProvider, message: Cell) { 62 | await provider.external(message); 63 | } 64 | 65 | /** 66 | * Sign and send transfer 67 | */ 68 | async sendTransfer(provider: ContractProvider, args: { 69 | seqno: number, 70 | secretKey: Buffer, 71 | message?: Maybe, 72 | sendMode?: Maybe 73 | }) { 74 | let transfer = this.createTransfer(args); 75 | await this.send(provider, transfer); 76 | } 77 | 78 | /** 79 | * Create signed transfer 80 | */ 81 | createTransfer(args: { 82 | seqno: number, 83 | secretKey: Buffer, 84 | message?: Maybe, 85 | sendMode?: Maybe, 86 | }) { 87 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 88 | if (args.sendMode !== null && args.sendMode !== undefined) { 89 | sendMode = args.sendMode; 90 | } 91 | return createWalletTransferV1({ 92 | seqno: args.seqno, 93 | sendMode, 94 | secretKey: args.secretKey, 95 | message: args.message 96 | }); 97 | } 98 | 99 | /** 100 | * Create sender 101 | */ 102 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 103 | return { 104 | send: async (args) => { 105 | let seqno = await this.getSeqno(provider); 106 | let transfer = this.createTransfer({ 107 | seqno, 108 | secretKey, 109 | sendMode: args.sendMode, 110 | message: internal({ 111 | to: args.to, 112 | value: args.value, 113 | extracurrency: args.extracurrency, 114 | init: args.init, 115 | body: args.body, 116 | bounce: args.bounce 117 | }) 118 | }); 119 | await this.send(provider, transfer); 120 | } 121 | }; 122 | } 123 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R2.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV1R2 } from "./WalletContractV1R2"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV1R2', () => { 16 | it('should has balance and correct address', async () => { 17 | 18 | // Create contract 19 | let client = createTestClient4(); 20 | let key = randomTestKey('v4-treasure'); 21 | let contract = client.open(WalletContractV1R2.create({ workchain: 0, publicKey: key.publicKey })); 22 | let balance = await contract.getBalance(); 23 | 24 | // Check parameters 25 | expect(contract.address.equals(Address.parse('EQATDkvcCA2fFWbSTHMpGCrjkNGqgEywES15ZS11HHY3UuxK'))).toBe(true); 26 | expect(balance > 0n).toBe(true); 27 | }); 28 | it('should perform transfer', async () => { 29 | // Create contract 30 | let client = createTestClient4(); 31 | let key = randomTestKey('v4-treasure'); 32 | let contract = client.open(WalletContractV1R2.create({ workchain: 0, publicKey: key.publicKey })); 33 | 34 | // Prepare transfer 35 | let seqno = await contract.getSeqno(); 36 | let transfer = contract.createTransfer({ 37 | seqno, 38 | secretKey: key.secretKey, 39 | message: internal({ 40 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 41 | value: '0.1', 42 | body: 'Hello, world!' 43 | }) 44 | }); 45 | 46 | // Perform transfer 47 | await contract.send(transfer); 48 | await tillNextSeqno(contract, seqno); 49 | }); 50 | 51 | it('should perform extra currency transfer', async () => { 52 | // Create contract 53 | let client = createTestClient4(); 54 | let key = randomTestKey('v4-treasure'); 55 | let contract = client.open(WalletContractV1R2.create({ workchain: 0, publicKey: key.publicKey })); 56 | 57 | // Prepare transfer 58 | let seqno = await contract.getSeqno(); 59 | let transfer = contract.createTransfer({ 60 | seqno, 61 | secretKey: key.secretKey, 62 | message: internal({ 63 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 64 | value: '0.01', 65 | extracurrency: {100: BigInt(10 ** 6)}, 66 | body: 'Hello, extra currency v1r2!' 67 | }) 68 | }); 69 | 70 | // Perform transfer 71 | await contract.send(transfer); 72 | await tillNextSeqno(contract, seqno); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV1 } from "./signing/createWalletTransfer"; 12 | 13 | export class WalletContractV1R2 implements Contract { 14 | 15 | static create(args: { workchain: number, publicKey: Buffer }) { 16 | return new WalletContractV1R2(args.workchain, args.publicKey); 17 | } 18 | 19 | readonly workchain: number; 20 | readonly publicKey: Buffer; 21 | readonly address: Address; 22 | readonly init: { data: Cell, code: Cell }; 23 | 24 | private constructor(workchain: number, publicKey: Buffer) { 25 | this.workchain = workchain; 26 | this.publicKey = publicKey; 27 | 28 | // Build initial code and data 29 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVNDieG8=', 'base64'))[0]; 30 | let data = beginCell() 31 | .storeUint(0, 32) // Seqno 32 | .storeBuffer(publicKey) 33 | .endCell(); 34 | this.init = { code, data }; 35 | this.address = contractAddress(workchain, { code, data }); 36 | } 37 | 38 | /** 39 | * Get Wallet Balance 40 | */ 41 | async getBalance(provider: ContractProvider) { 42 | let state = await provider.getState(); 43 | return state.balance; 44 | } 45 | 46 | /** 47 | * Get Wallet Seqno 48 | */ 49 | async getSeqno(provider: ContractProvider) { 50 | let state = await provider.getState(); 51 | if (state.state.type === 'active') { 52 | let res = await provider.get('seqno', []); 53 | return res.stack.readNumber(); 54 | } else { 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Send signed transfer 61 | */ 62 | async send(provider: ContractProvider, message: Cell) { 63 | await provider.external(message); 64 | } 65 | 66 | /** 67 | * Sign and send transfer 68 | */ 69 | async sendTransfer(provider: ContractProvider, args: { 70 | seqno: number, 71 | secretKey: Buffer, 72 | message?: Maybe, 73 | sendMode?: Maybe 74 | }) { 75 | let transfer = this.createTransfer(args); 76 | await this.send(provider, transfer); 77 | } 78 | 79 | /** 80 | * Create signed transfer 81 | */ 82 | createTransfer(args: { 83 | seqno: number, 84 | secretKey: Buffer, 85 | message?: Maybe, 86 | sendMode?: Maybe, 87 | }) { 88 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 89 | if (args.sendMode !== null && args.sendMode !== undefined) { 90 | sendMode = args.sendMode; 91 | } 92 | return createWalletTransferV1({ 93 | seqno: args.seqno, 94 | sendMode, 95 | secretKey: args.secretKey, 96 | message: args.message 97 | }); 98 | } 99 | 100 | /** 101 | * Create sender 102 | */ 103 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 104 | return { 105 | send: async (args) => { 106 | let seqno = await this.getSeqno(provider); 107 | let transfer = this.createTransfer({ 108 | seqno, 109 | secretKey, 110 | sendMode: args.sendMode, 111 | message: internal({ 112 | to: args.to, 113 | value: args.value, 114 | extracurrency: args.extracurrency, 115 | init: args.init, 116 | body: args.body, 117 | bounce: args.bounce 118 | }) 119 | }); 120 | await this.send(provider, transfer); 121 | } 122 | }; 123 | } 124 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R3.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV1R3 } from "./WalletContractV1R3"; 13 | 14 | describe('WalletContractV1R3', () => { 15 | it('should has balance and correct address', async () => { 16 | 17 | // Create contract 18 | let client = createTestClient4(); 19 | let key = randomTestKey('v4-treasure'); 20 | let contract = client.open(WalletContractV1R3.create({ workchain: 0, publicKey: key.publicKey })); 21 | let balance = await contract.getBalance(); 22 | 23 | // Check parameters 24 | expect(contract.address.equals(Address.parse('EQBRRPBUtgzq5om6O4rtxwPW4hyDxiXYeIko27tvsm97kUw3'))).toBe(true); 25 | expect(balance > 0n).toBe(true); 26 | }); 27 | it('should perform transfer', async () => { 28 | // Create contract 29 | let client = createTestClient4(); 30 | let key = randomTestKey('v4-treasure'); 31 | let contract = client.open(WalletContractV1R3.create({ workchain: 0, publicKey: key.publicKey })); 32 | 33 | // Prepare transfer 34 | let seqno = await contract.getSeqno(); 35 | let transfer = contract.createTransfer({ 36 | seqno, 37 | secretKey: key.secretKey, 38 | message: internal({ 39 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 40 | value: '0.1', 41 | body: 'Hello, world!' 42 | }) 43 | }); 44 | 45 | // Perform transfer 46 | await contract.send(transfer); 47 | }); 48 | }); -------------------------------------------------------------------------------- /src/wallets/WalletContractV1R3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV1 } from "./signing/createWalletTransfer"; 12 | 13 | export class WalletContractV1R3 implements Contract { 14 | 15 | static create(args: { workchain: number, publicKey: Buffer }) { 16 | return new WalletContractV1R3(args.workchain, args.publicKey); 17 | } 18 | 19 | readonly workchain: number; 20 | readonly publicKey: Buffer; 21 | readonly address: Address; 22 | readonly init: { data: Cell, code: Cell }; 23 | 24 | private constructor(workchain: number, publicKey: Buffer) { 25 | this.workchain = workchain; 26 | this.publicKey = publicKey; 27 | 28 | // Build initial code and data 29 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVLW4bkI=', 'base64'))[0]; 30 | let data = beginCell() 31 | .storeUint(0, 32) // Seqno 32 | .storeBuffer(publicKey) 33 | .endCell(); 34 | this.init = { code, data }; 35 | this.address = contractAddress(workchain, { code, data }); 36 | } 37 | 38 | /** 39 | * Get Wallet Balance 40 | */ 41 | async getBalance(provider: ContractProvider) { 42 | let state = await provider.getState(); 43 | return state.balance; 44 | } 45 | 46 | /** 47 | * Get Wallet Seqno 48 | */ 49 | async getSeqno(provider: ContractProvider) { 50 | let state = await provider.getState(); 51 | if (state.state.type === 'active') { 52 | let res = await provider.get('seqno', []); 53 | return res.stack.readNumber(); 54 | } else { 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Send signed transfer 61 | */ 62 | async send(executor: ContractProvider, message: Cell) { 63 | await executor.external(message); 64 | } 65 | 66 | /** 67 | * Sign and send transfer 68 | */ 69 | async sendTransfer(provider: ContractProvider, args: { 70 | seqno: number, 71 | secretKey: Buffer, 72 | message?: Maybe, 73 | sendMode?: Maybe 74 | }) { 75 | let transfer = this.createTransfer(args); 76 | await this.send(provider, transfer); 77 | } 78 | 79 | /** 80 | * Create signed transfer 81 | */ 82 | createTransfer(args: { 83 | seqno: number, 84 | secretKey: Buffer, 85 | message?: Maybe 86 | sendMode?: Maybe, 87 | }) { 88 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 89 | if (args.sendMode !== null && args.sendMode !== undefined) { 90 | sendMode = args.sendMode; 91 | } 92 | return createWalletTransferV1({ 93 | seqno: args.seqno, 94 | sendMode: sendMode, 95 | secretKey: args.secretKey, 96 | message: args.message 97 | }); 98 | } 99 | 100 | /** 101 | * Create sender 102 | */ 103 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 104 | return { 105 | send: async (args) => { 106 | let seqno = await this.getSeqno(provider); 107 | let transfer = this.createTransfer({ 108 | seqno, 109 | secretKey, 110 | sendMode: args.sendMode, 111 | message: internal({ 112 | to: args.to, 113 | value: args.value, 114 | init: args.init, 115 | body: args.body, 116 | bounce: args.bounce 117 | }) 118 | }); 119 | await this.send(provider, transfer); 120 | } 121 | }; 122 | } 123 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV2R1.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV2R1 } from "./WalletContractV2R1"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV2R1', () => { 16 | it('should has balance and correct address', async () => { 17 | 18 | // Create contract 19 | let client = createTestClient4(); 20 | let key = randomTestKey('v4-treasure'); 21 | let contract = client.open(WalletContractV2R1.create({ workchain: 0, publicKey: key.publicKey })); 22 | let balance = await contract.getBalance(); 23 | 24 | // Check parameters 25 | expect(contract.address.equals(Address.parse('EQD3ES67JiTYq5y2eE1-fivl5kANn-gKDDjvpbxNCQWPzs4D'))).toBe(true); 26 | expect(balance > 0n).toBe(true); 27 | }); 28 | it('should perform transfer', async () => { 29 | // Create contract 30 | let client = createTestClient4(); 31 | let key = randomTestKey('v4-treasure'); 32 | let contract = client.open(WalletContractV2R1.create({ workchain: 0, publicKey: key.publicKey })); 33 | 34 | // Prepare transfer 35 | let seqno = await contract.getSeqno(); 36 | let transfer = contract.createTransfer({ 37 | seqno, 38 | secretKey: key.secretKey, 39 | messages: [internal({ 40 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 41 | value: '0.1', 42 | body: 'Hello, world!' 43 | })] 44 | }); 45 | 46 | // Perform transfer 47 | await contract.send(transfer); 48 | await tillNextSeqno(contract, seqno); 49 | }); 50 | 51 | it('should perform extra currency transfer', async () => { 52 | // Create contract 53 | let client = createTestClient4(); 54 | let key = randomTestKey('v4-treasure'); 55 | let contract = client.open(WalletContractV2R1.create({ workchain: 0, publicKey: key.publicKey })); 56 | 57 | // Prepare transfer 58 | let seqno = await contract.getSeqno(); 59 | let transfer = contract.createTransfer({ 60 | seqno, 61 | secretKey: key.secretKey, 62 | messages: [internal({ 63 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 64 | value: '0.01', 65 | extracurrency: {100: BigInt(10 ** 6)}, 66 | body: 'Hello, extra currency v2r1!' 67 | })] 68 | }); 69 | 70 | // Perform transfer 71 | await contract.send(transfer); 72 | await tillNextSeqno(contract, seqno); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV2R1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV2 } from "./signing/createWalletTransfer"; 12 | 13 | export class WalletContractV2R1 implements Contract { 14 | 15 | static create(args: { workchain: number, publicKey: Buffer }) { 16 | return new WalletContractV2R1(args.workchain, args.publicKey); 17 | } 18 | 19 | readonly workchain: number; 20 | readonly publicKey: Buffer; 21 | readonly address: Address; 22 | readonly init: { data: Cell, code: Cell }; 23 | 24 | private constructor(workchain: number, publicKey: Buffer) { 25 | this.workchain = workchain; 26 | this.publicKey = publicKey; 27 | 28 | // Build initial code and data 29 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VShNwu2', 'base64'))[0]; 30 | let data = beginCell() 31 | .storeUint(0, 32) // Seqno 32 | .storeBuffer(publicKey) 33 | .endCell(); 34 | this.init = { code, data }; 35 | this.address = contractAddress(workchain, { code, data }); 36 | } 37 | 38 | /** 39 | * Get Wallet Balance 40 | */ 41 | async getBalance(provider: ContractProvider) { 42 | let state = await provider.getState(); 43 | return state.balance; 44 | } 45 | 46 | /** 47 | * Get Wallet Seqno 48 | */ 49 | async getSeqno(provider: ContractProvider) { 50 | let state = await provider.getState(); 51 | if (state.state.type === 'active') { 52 | let res = await provider.get('seqno', []); 53 | return res.stack.readNumber(); 54 | } else { 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Send signed transfer 61 | */ 62 | async send(provider: ContractProvider, message: Cell) { 63 | await provider.external(message); 64 | } 65 | 66 | /** 67 | * Sign and send transfer 68 | */ 69 | async sendTransfer(provider: ContractProvider, args: { 70 | seqno: number, 71 | secretKey: Buffer, 72 | messages: MessageRelaxed[], 73 | sendMode?: Maybe, 74 | timeout?: Maybe 75 | }) { 76 | let transfer = this.createTransfer(args); 77 | await this.send(provider, transfer); 78 | } 79 | 80 | /** 81 | * Create signed transfer 82 | */ 83 | createTransfer(args: { 84 | seqno: number, 85 | secretKey: Buffer, 86 | messages: MessageRelaxed[], 87 | sendMode?: Maybe, 88 | timeout?: Maybe 89 | }) { 90 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 91 | if (args.sendMode !== null && args.sendMode !== undefined) { 92 | sendMode = args.sendMode; 93 | } 94 | return createWalletTransferV2({ 95 | seqno: args.seqno, 96 | sendMode, 97 | secretKey: args.secretKey, 98 | messages: args.messages, 99 | timeout: args.timeout 100 | }); 101 | } 102 | 103 | /** 104 | * Create sender 105 | */ 106 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 107 | return { 108 | send: async (args) => { 109 | let seqno = await this.getSeqno(provider); 110 | let transfer = this.createTransfer({ 111 | seqno, 112 | secretKey, 113 | sendMode: args.sendMode, 114 | messages: [internal({ 115 | to: args.to, 116 | value: args.value, 117 | extracurrency: args.extracurrency, 118 | init: args.init, 119 | body: args.body, 120 | bounce: args.bounce 121 | })] 122 | }); 123 | await this.send(provider, transfer); 124 | } 125 | }; 126 | } 127 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV2R2.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV2R2 } from "./WalletContractV2R2"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV2R2', () => { 16 | it('should has balance and correct address', async () => { 17 | 18 | // Create contract 19 | let client = createTestClient4(); 20 | let key = randomTestKey('v4-treasure'); 21 | let contract = client.open(WalletContractV2R2.create({ workchain: 0, publicKey: key.publicKey })); 22 | let balance = await contract.getBalance(); 23 | 24 | // Check parameters 25 | expect(contract.address.equals(Address.parse('EQAkAcNLtzCHudScK9Hsk9I_7SrunBWf_9VrA2xJmGebwEsl'))).toBe(true); 26 | expect(balance > 0n).toBe(true); 27 | }); 28 | it('should perform transfer', async () => { 29 | // Create contract 30 | let client = createTestClient4(); 31 | let key = randomTestKey('v4-treasure'); 32 | let contract = client.open(WalletContractV2R2.create({ workchain: 0, publicKey: key.publicKey })); 33 | 34 | // Prepare transfer 35 | let seqno = await contract.getSeqno(); 36 | let transfer = contract.createTransfer({ 37 | seqno, 38 | secretKey: key.secretKey, 39 | messages: [internal({ 40 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 41 | value: '0.1', 42 | body: 'Hello, world!' 43 | })] 44 | }); 45 | 46 | // Perform transfer 47 | await contract.send(transfer); 48 | await tillNextSeqno(contract, seqno); 49 | }); 50 | 51 | it('should perfrorm extra currency transfer', async () => { 52 | // Create contract 53 | let client = createTestClient4(); 54 | let key = randomTestKey('v4-treasure'); 55 | let contract = client.open(WalletContractV2R2.create({ workchain: 0, publicKey: key.publicKey })); 56 | 57 | // Prepare transfer 58 | let seqno = await contract.getSeqno(); 59 | let transfer = contract.createTransfer({ 60 | seqno, 61 | secretKey: key.secretKey, 62 | messages: [internal({ 63 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 64 | value: '0.01', 65 | extracurrency: {100: BigInt(10 ** 6)}, 66 | body: 'Hello, extra currency v2r2!' 67 | })] 68 | }); 69 | 70 | // Perform transfer 71 | await contract.send(transfer); 72 | await tillNextSeqno(contract, seqno); 73 | 74 | 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV2R2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV2 } from "./signing/createWalletTransfer"; 12 | 13 | export class WalletContractV2R2 implements Contract { 14 | 15 | static create(args: { workchain: number, publicKey: Buffer }) { 16 | return new WalletContractV2R2(args.workchain, args.publicKey); 17 | } 18 | 19 | readonly workchain: number; 20 | readonly publicKey: Buffer; 21 | readonly address: Address; 22 | readonly init: { data: Cell, code: Cell }; 23 | 24 | private constructor(workchain: number, publicKey: Buffer) { 25 | this.workchain = workchain; 26 | this.publicKey = publicKey; 27 | 28 | // Build initial code and data 29 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAYwAAwv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQETNeh', 'base64'))[0]; 30 | let data = beginCell() 31 | .storeUint(0, 32) // Seqno 32 | .storeBuffer(publicKey) 33 | .endCell(); 34 | this.init = { code, data }; 35 | this.address = contractAddress(workchain, { code, data }); 36 | } 37 | 38 | /** 39 | * Get Wallet Balance 40 | */ 41 | async getBalance(provider: ContractProvider) { 42 | let state = await provider.getState(); 43 | return state.balance; 44 | } 45 | 46 | /** 47 | * Get Wallet Seqno 48 | */ 49 | async getSeqno(provider: ContractProvider) { 50 | let state = await provider.getState(); 51 | if (state.state.type === 'active') { 52 | let res = await provider.get('seqno', []); 53 | return res.stack.readNumber(); 54 | } else { 55 | return 0; 56 | } 57 | } 58 | 59 | /** 60 | * Send signed transfer 61 | */ 62 | async send(provider: ContractProvider, message: Cell) { 63 | await provider.external(message); 64 | } 65 | 66 | /** 67 | * Sign and send transfer 68 | */ 69 | async sendTransfer(provider: ContractProvider, args: { 70 | seqno: number, 71 | secretKey: Buffer, 72 | messages: MessageRelaxed[], 73 | sendMode?: Maybe, 74 | timeout?: Maybe 75 | }) { 76 | let transfer = this.createTransfer(args); 77 | await this.send(provider, transfer); 78 | } 79 | 80 | /** 81 | * Create signed transfer 82 | */ 83 | createTransfer(args: { 84 | seqno: number, 85 | secretKey: Buffer, 86 | messages: MessageRelaxed[], 87 | sendMode?: Maybe, 88 | timeout?: Maybe 89 | }) { 90 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 91 | if (args.sendMode !== null && args.sendMode !== undefined) { 92 | sendMode = args.sendMode; 93 | } 94 | return createWalletTransferV2({ 95 | seqno: args.seqno, 96 | sendMode, 97 | secretKey: args.secretKey, 98 | messages: args.messages, 99 | timeout: args.timeout 100 | }); 101 | } 102 | 103 | /** 104 | * Create sender 105 | */ 106 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 107 | return { 108 | send: async (args) => { 109 | let seqno = await this.getSeqno(provider); 110 | let transfer = this.createTransfer({ 111 | seqno, 112 | secretKey, 113 | sendMode: args.sendMode, 114 | messages: [internal({ 115 | to: args.to, 116 | value: args.value, 117 | extracurrency: args.extracurrency, 118 | init: args.init, 119 | body: args.body, 120 | bounce: args.bounce 121 | })] 122 | }); 123 | await this.send(provider, transfer); 124 | } 125 | }; 126 | } 127 | } -------------------------------------------------------------------------------- /src/wallets/WalletContractV3R1.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV3R1 } from "./WalletContractV3R1"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV3R1', () => { 16 | it('should has balance and correct address', async () => { 17 | 18 | // Create contract 19 | let client = createTestClient4(); 20 | let key = randomTestKey('v4-treasure'); 21 | let contract = client.open(WalletContractV3R1.create({ workchain: 0, publicKey: key.publicKey })); 22 | let balance = await contract.getBalance(); 23 | 24 | // Check parameters 25 | expect(contract.address.equals(Address.parse('EQBJp7j5N40GXJbAqFSnfTV1Af4ZTyHIMpRbKcudNhWJbbNO'))).toBe(true); 26 | expect(balance > 0n).toBe(true); 27 | }); 28 | it('should perform transfer', async () => { 29 | // Create contract 30 | let client = createTestClient4(); 31 | let key = randomTestKey('v4-treasure'); 32 | let contract = client.open(WalletContractV3R1.create({ workchain: 0, publicKey: key.publicKey })); 33 | 34 | // Prepare transfer 35 | let seqno = await contract.getSeqno(); 36 | let transfer = contract.createTransfer({ 37 | seqno, 38 | secretKey: key.secretKey, 39 | messages: [internal({ 40 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 41 | value: '0.1', 42 | body: 'Hello, world!' 43 | })] 44 | }); 45 | 46 | // Perform transfer 47 | await contract.send(transfer); 48 | await tillNextSeqno(contract, seqno); 49 | }); 50 | 51 | it('should perform extra currency transfer', async () => { 52 | // Create contract 53 | let client = createTestClient4(); 54 | let key = randomTestKey('v4-treasure'); 55 | let contract = client.open(WalletContractV3R1.create({ workchain: 0, publicKey: key.publicKey })); 56 | 57 | // Prepare transfer 58 | let seqno = await contract.getSeqno(); 59 | let transfer = contract.createTransfer({ 60 | seqno, 61 | secretKey: key.secretKey, 62 | messages: [internal({ 63 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 64 | value: '0.05', 65 | extracurrency: {100: BigInt(10 ** 6)}, 66 | body: 'Hello, extra currency v3r1!' 67 | })] 68 | }); 69 | 70 | // Perform transfer 71 | await contract.send(transfer); 72 | await tillNextSeqno(contract, seqno); 73 | }); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV3R1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV3 } from "./signing/createWalletTransfer"; 12 | import { WalletV3SendArgsSignable, WalletV3SendArgsSigned } from "./WalletContractV3Types"; 13 | 14 | export class WalletContractV3R1 implements Contract { 15 | 16 | static create(args: { workchain: number, publicKey: Buffer, walletId?: Maybe }) { 17 | return new WalletContractV3R1(args.workchain, args.publicKey, args.walletId); 18 | } 19 | 20 | readonly workchain: number; 21 | readonly publicKey: Buffer; 22 | readonly address: Address; 23 | readonly walletId: number; 24 | readonly init: { data: Cell, code: Cell }; 25 | 26 | private constructor(workchain: number, publicKey: Buffer, walletId?: Maybe) { 27 | 28 | // Resolve parameters 29 | this.workchain = workchain; 30 | this.publicKey = publicKey; 31 | if (walletId !== null && walletId !== undefined) { 32 | this.walletId = walletId; 33 | } else { 34 | this.walletId = 698983191 + workchain; 35 | } 36 | 37 | // Build initial code and data 38 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA=', 'base64'))[0]; 39 | let data = beginCell() 40 | .storeUint(0, 32) // Seqno 41 | .storeUint(this.walletId, 32) 42 | .storeBuffer(publicKey) 43 | .endCell(); 44 | this.init = { code, data }; 45 | this.address = contractAddress(workchain, { code, data }); 46 | } 47 | 48 | /** 49 | * Get wallet balance 50 | */ 51 | async getBalance(provider: ContractProvider) { 52 | let state = await provider.getState(); 53 | return state.balance; 54 | } 55 | 56 | /** 57 | * Get Wallet Seqno 58 | */ 59 | async getSeqno(provider: ContractProvider) { 60 | let state = await provider.getState(); 61 | if (state.state.type === 'active') { 62 | let res = await provider.get('seqno', []); 63 | return res.stack.readNumber(); 64 | } else { 65 | return 0; 66 | } 67 | } 68 | 69 | /** 70 | * Send signed transfer 71 | */ 72 | async send(provider: ContractProvider, message: Cell) { 73 | await provider.external(message); 74 | } 75 | 76 | /** 77 | * Sign and send transfer 78 | */ 79 | async sendTransfer(provider: ContractProvider, args: { 80 | seqno: number, 81 | secretKey: Buffer, 82 | messages: MessageRelaxed[], 83 | sendMode?: Maybe, 84 | timeout?: Maybe 85 | }) { 86 | let transfer = this.createTransfer(args); 87 | await this.send(provider, transfer); 88 | } 89 | 90 | 91 | /** 92 | * Create transfer 93 | */ 94 | createTransfer(args: T) { 95 | return createWalletTransferV3({ 96 | ...args, 97 | sendMode: args.sendMode ?? SendMode.PAY_GAS_SEPARATELY, 98 | walletId: this.walletId 99 | }); 100 | } 101 | 102 | /** 103 | * Create sender 104 | */ 105 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 106 | return { 107 | send: async (args) => { 108 | let seqno = await this.getSeqno(provider); 109 | let transfer = this.createTransfer({ 110 | seqno, 111 | secretKey, 112 | sendMode: args.sendMode, 113 | messages: [internal({ 114 | to: args.to, 115 | value: args.value, 116 | extracurrency: args.extracurrency, 117 | init: args.init, 118 | body: args.body, 119 | bounce: args.bounce 120 | })] 121 | }); 122 | await this.send(provider, transfer); 123 | } 124 | }; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV3R2.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { createTestClient4 } from "../utils/createTestClient4"; 11 | import { Address, internal } from "@ton/core"; 12 | import { WalletContractV3R2 } from "./WalletContractV3R2"; 13 | import { tillNextSeqno } from "../utils/testWallets"; 14 | 15 | describe('WalletContractV3R1', () => { 16 | 17 | it('should has balance and correct address', async () => { 18 | 19 | // Create contract 20 | let client = createTestClient4(); 21 | let key = randomTestKey('v4-treasure'); 22 | let contract = client.open(WalletContractV3R2.create({ workchain: 0, publicKey: key.publicKey })); 23 | let balance = await contract.getBalance(); 24 | 25 | // Check parameters 26 | expect(contract.address.equals(Address.parse('EQA0D_5WdusaCB-SpnoE6l5TzdBmgOkzTcXrdh0px6g3zJSk'))).toBe(true); 27 | expect(balance > 0n).toBe(true); 28 | }); 29 | 30 | it('should perform transfer', async () => { 31 | // Create contract 32 | let client = createTestClient4(); 33 | let key = randomTestKey('v4-treasure'); 34 | let contract = client.open(WalletContractV3R2.create({ workchain: 0, publicKey: key.publicKey })); 35 | 36 | // Prepare transfer 37 | let seqno = await contract.getSeqno(); 38 | let transfer = contract.createTransfer({ 39 | seqno, 40 | secretKey: key.secretKey, 41 | messages: [internal({ 42 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 43 | value: '0.1', 44 | body: 'Hello, world!' 45 | })] 46 | }); 47 | 48 | // Perform transfer 49 | await contract.send(transfer); 50 | 51 | await tillNextSeqno(contract, seqno); 52 | }); 53 | 54 | it('should perform extra currency transfer', async () => { 55 | // Create contract 56 | let client = createTestClient4(); 57 | let key = randomTestKey('v4-treasure'); 58 | let contract = client.open(WalletContractV3R2.create({ workchain: 0, publicKey: key.publicKey })); 59 | 60 | // Prepare transfer 61 | let seqno = await contract.getSeqno(); 62 | let transfer = contract.createTransfer({ 63 | seqno, 64 | secretKey: key.secretKey, 65 | messages: [internal({ 66 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 67 | value: '0.05', 68 | extracurrency: {100: BigInt(10 ** 6)}, 69 | body: 'Hello, extra currency v3r2!' 70 | })] 71 | }); 72 | 73 | // Perform transfer 74 | await contract.send(transfer); 75 | 76 | await tillNextSeqno(contract, seqno); 77 | 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV3R2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV3 } from "./signing/createWalletTransfer"; 12 | import { WalletV3SendArgsSignable, WalletV3SendArgsSigned } from "./WalletContractV3Types"; 13 | 14 | 15 | export class WalletContractV3R2 implements Contract { 16 | 17 | static create(args: { workchain: number, publicKey: Buffer, walletId?: Maybe }) { 18 | return new WalletContractV3R2(args.workchain, args.publicKey, args.walletId); 19 | } 20 | 21 | readonly workchain: number; 22 | readonly publicKey: Buffer; 23 | readonly address: Address; 24 | readonly walletId: number; 25 | readonly init: { data: Cell, code: Cell }; 26 | 27 | private constructor(workchain: number, publicKey: Buffer, walletId?: Maybe) { 28 | 29 | // Resolve parameters 30 | this.workchain = workchain; 31 | this.publicKey = publicKey; 32 | if (walletId !== null && walletId !== undefined) { 33 | this.walletId = walletId; 34 | } else { 35 | this.walletId = 698983191 + workchain; 36 | } 37 | 38 | // Build initial code and data 39 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0=', 'base64'))[0]; 40 | let data = beginCell() 41 | .storeUint(0, 32) // Seqno 42 | .storeUint(this.walletId, 32) 43 | .storeBuffer(publicKey) 44 | .endCell(); 45 | this.init = { code, data }; 46 | this.address = contractAddress(workchain, { code, data }); 47 | } 48 | 49 | /** 50 | * Get wallet balance 51 | */ 52 | async getBalance(provider: ContractProvider) { 53 | let state = await provider.getState(); 54 | return state.balance; 55 | } 56 | 57 | /** 58 | * Get Wallet Seqno 59 | */ 60 | async getSeqno(provider: ContractProvider) { 61 | let state = await provider.getState(); 62 | if (state.state.type === 'active') { 63 | let res = await provider.get('seqno', []); 64 | return res.stack.readNumber(); 65 | } else { 66 | return 0; 67 | } 68 | } 69 | 70 | /** 71 | * Send signed transfer 72 | */ 73 | async send(provider: ContractProvider, message: Cell) { 74 | await provider.external(message); 75 | } 76 | 77 | /** 78 | * Sign and send transfer 79 | */ 80 | async sendTransfer(provider: ContractProvider, args: { 81 | seqno: number, 82 | secretKey: Buffer, 83 | messages: MessageRelaxed[], 84 | sendMode?: Maybe, 85 | timeout?: Maybe 86 | }) { 87 | let transfer = this.createTransfer(args); 88 | await this.send(provider, transfer); 89 | } 90 | 91 | /** 92 | * Create transfer 93 | */ 94 | createTransfer(args: T) { 95 | return createWalletTransferV3({ 96 | ...args, 97 | sendMode: args.sendMode ?? SendMode.PAY_GAS_SEPARATELY, 98 | walletId: this.walletId 99 | }); 100 | } 101 | 102 | /** 103 | * Create sender 104 | */ 105 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 106 | return { 107 | send: async (args) => { 108 | let seqno = await this.getSeqno(provider); 109 | let transfer = this.createTransfer({ 110 | seqno, 111 | secretKey, 112 | sendMode: args.sendMode, 113 | messages: [internal({ 114 | to: args.to, 115 | value: args.value, 116 | extracurrency: args.extracurrency, 117 | init: args.init, 118 | body: args.body, 119 | bounce: args.bounce 120 | })] 121 | }); 122 | await this.send(provider, transfer); 123 | } 124 | }; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV3Types.ts: -------------------------------------------------------------------------------- 1 | import { MessageRelaxed, SendMode } from "@ton/core"; 2 | import { Maybe } from "../utils/maybe"; 3 | import { SendArgsSignable } from "./signing/singer"; 4 | import {SendArgsSigned} from "./signing/singer"; 5 | 6 | 7 | export type WalletV3BasicSendArgs = { 8 | seqno: number, 9 | messages: MessageRelaxed[] 10 | sendMode?: Maybe, 11 | timeout?: Maybe, 12 | } 13 | 14 | export type WalletV3SendArgsSigned = WalletV3BasicSendArgs & SendArgsSigned; 15 | export type WalletV3SendArgsSignable = WalletV3BasicSendArgs & SendArgsSignable; 16 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV4.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { randomTestKey } from "../utils/randomTestKey"; 10 | import { tillNextSeqno } from "../utils/testWallets"; 11 | import { WalletContractV4 } from "./WalletContractV4"; 12 | import { createTestClient4 } from "../utils/createTestClient4"; 13 | import { Address, internal } from "@ton/core"; 14 | 15 | describe('WalletContractV4', () => { 16 | 17 | it('should has balance and correct address', async () => { 18 | 19 | // Create contract 20 | let client = createTestClient4(); 21 | let key = randomTestKey('v4-treasure'); 22 | let contract = client.open(WalletContractV4.create({ workchain: 0, publicKey: key.publicKey })); 23 | let balance = await contract.getBalance(); 24 | 25 | // Check parameters 26 | expect(contract.address.equals(Address.parse('EQDnBF4JTFKHTYjulEJyNd4dstLGH1m51UrLdu01_tw4z2Au'))).toBe(true); 27 | expect(balance > 0n).toBe(true); 28 | }); 29 | 30 | it('should perform transfer', async () => { 31 | // Create contract 32 | let client = createTestClient4(); 33 | let key = randomTestKey('v4-treasure'); 34 | let contract = client.open(WalletContractV4.create({ workchain: 0, publicKey: key.publicKey })); 35 | 36 | // Prepare transfer 37 | let seqno = await contract.getSeqno(); 38 | let transfer = contract.createTransfer({ 39 | seqno, 40 | secretKey: key.secretKey, 41 | messages: [internal({ 42 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 43 | value: '0.1', 44 | body: 'Hello world: 1' 45 | }), internal({ 46 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 47 | value: '0.1', 48 | body: 'Hello world: 2' 49 | })] 50 | }); 51 | 52 | // Perform transfer 53 | await contract.send(transfer); 54 | // Awaiting update 55 | await tillNextSeqno(contract, seqno); 56 | }); 57 | 58 | it('should perform extra currency transfer', async () => { 59 | // Create contract 60 | let client = createTestClient4(); 61 | let key = randomTestKey('v4-treasure'); 62 | let contract = client.open(WalletContractV4.create({ workchain: 0, publicKey: key.publicKey })); 63 | 64 | // Prepare transfer 65 | let seqno = await contract.getSeqno(); 66 | let transfer = contract.createTransfer({ 67 | seqno, 68 | secretKey: key.secretKey, 69 | messages: [internal({ 70 | to: 'kQD6oPnzaaAMRW24R8F0_nlSsJQni0cGHntR027eT9_sgtwt', 71 | value: '0.01', 72 | extracurrency: {100: BigInt(10 ** 6)}, 73 | body: 'Hello extra currency v4' 74 | })] 75 | }); 76 | 77 | // Perform transfer 78 | await contract.send(transfer); 79 | // Awaiting update 80 | await tillNextSeqno(contract, seqno); 81 | 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, internal, MessageRelaxed, Sender, SendMode } from "@ton/core"; 10 | import { Maybe } from "../utils/maybe"; 11 | import { createWalletTransferV4 } from "./signing/createWalletTransfer"; 12 | import { SendArgsSignable, SendArgsSigned } from "./signing/singer"; 13 | 14 | 15 | export type WalletV4BasicSendArgs = { 16 | seqno: number, 17 | messages: MessageRelaxed[] 18 | sendMode?: Maybe, 19 | timeout?: Maybe, 20 | } 21 | 22 | export type Wallet4SendArgsSigned = WalletV4BasicSendArgs & SendArgsSigned; 23 | export type Wallet4SendArgsSignable = WalletV4BasicSendArgs & SendArgsSignable; 24 | 25 | export class WalletContractV4 implements Contract { 26 | 27 | static create(args: { workchain: number, publicKey: Buffer, walletId?: Maybe }) { 28 | return new WalletContractV4(args.workchain, args.publicKey, args.walletId); 29 | } 30 | 31 | readonly workchain: number; 32 | readonly publicKey: Buffer; 33 | readonly address: Address; 34 | readonly walletId: number; 35 | readonly init: { data: Cell, code: Cell }; 36 | 37 | private constructor(workchain: number, publicKey: Buffer, walletId?: Maybe) { 38 | 39 | // Resolve parameters 40 | this.workchain = workchain; 41 | this.publicKey = publicKey; 42 | if (walletId !== null && walletId !== undefined) { 43 | this.walletId = walletId; 44 | } else { 45 | this.walletId = 698983191 + workchain; 46 | } 47 | 48 | // Build initial code and data 49 | let code = Cell.fromBoc(Buffer.from('te6ccgECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA==', 'base64'))[0]; 50 | let data = beginCell() 51 | .storeUint(0, 32) // Seqno 52 | .storeUint(this.walletId, 32) 53 | .storeBuffer(this.publicKey) 54 | .storeBit(0) // Empty plugins dict 55 | .endCell(); 56 | this.init = { code, data }; 57 | this.address = contractAddress(workchain, { code, data }); 58 | } 59 | 60 | /** 61 | * Get Wallet Balance 62 | */ 63 | async getBalance(provider: ContractProvider) { 64 | let state = await provider.getState(); 65 | return state.balance; 66 | } 67 | 68 | /** 69 | * Get Wallet Seqno 70 | */ 71 | async getSeqno(provider: ContractProvider) { 72 | let state = await provider.getState(); 73 | if (state.state.type === 'active') { 74 | let res = await provider.get('seqno', []); 75 | return res.stack.readNumber(); 76 | } else { 77 | return 0; 78 | } 79 | } 80 | 81 | /** 82 | * Send signed transfer 83 | */ 84 | async send(provider: ContractProvider, message: Cell) { 85 | await provider.external(message); 86 | } 87 | 88 | /** 89 | * Sign and send transfer 90 | */ 91 | async sendTransfer(provider: ContractProvider, args: { 92 | seqno: number, 93 | secretKey: Buffer, 94 | messages: MessageRelaxed[] 95 | sendMode?: Maybe, 96 | timeout?: Maybe, 97 | }) { 98 | let transfer = this.createTransfer(args); 99 | await this.send(provider, transfer); 100 | } 101 | 102 | /** 103 | * Create signed transfer 104 | */ 105 | createTransfer(args:T ){ 106 | return createWalletTransferV4({ 107 | ...args, 108 | sendMode: args.sendMode ?? SendMode.PAY_GAS_SEPARATELY, 109 | walletId: this.walletId 110 | }); 111 | } 112 | 113 | /** 114 | * Create sender 115 | */ 116 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 117 | return { 118 | send: async (args) => { 119 | let seqno = await this.getSeqno(provider); 120 | let transfer = this.createTransfer({ 121 | seqno, 122 | secretKey, 123 | sendMode: args.sendMode, 124 | messages: [internal({ 125 | to: args.to, 126 | value: args.value, 127 | extracurrency: args.extracurrency, 128 | init: args.init, 129 | body: args.body, 130 | bounce: args.bounce 131 | })] 132 | }); 133 | await this.send(provider, transfer); 134 | } 135 | }; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/wallets/WalletContractV5Beta.ts: -------------------------------------------------------------------------------- 1 | export * from './v5beta/WalletContractV5Beta'; 2 | export * from './v5beta/WalletV5BetaActions'; 3 | export * from './v5beta/WalletV5BetaWalletId'; -------------------------------------------------------------------------------- /src/wallets/WalletContractV5R1.ts: -------------------------------------------------------------------------------- 1 | export * from './v5r1/WalletContractV5R1'; 2 | export * from './v5r1/WalletV5R1Actions'; 3 | export * from './v5r1/WalletV5R1WalletId'; -------------------------------------------------------------------------------- /src/wallets/signing/createWalletTransfer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { beginCell, Builder, Cell, MessageRelaxed, OutActionSendMsg, storeMessageRelaxed } from "@ton/core"; 10 | import { sign } from "@ton/crypto"; 11 | import { Maybe } from "../../utils/maybe"; 12 | import { 13 | WalletV5BetaSendArgsSignable, 14 | WalletContractV5Beta, 15 | WalletV5BetaPackedCell, 16 | WalletV5BetaSendArgs, 17 | WalletV5BetaSendArgsExtensionAuth 18 | } from "../v5beta/WalletContractV5Beta"; 19 | import { 20 | storeOutListExtendedV5Beta 21 | } from "../v5beta/WalletV5BetaActions"; 22 | import { signPayload } from "./singer"; 23 | import { Wallet4SendArgsSignable, Wallet4SendArgsSigned } from "../WalletContractV4"; 24 | import { WalletV3SendArgsSignable, WalletV3SendArgsSigned } from "../WalletContractV3Types"; 25 | import {OutActionExtended} from "../v5beta/WalletV5OutActions"; 26 | import { 27 | Wallet5VR1SendArgsExtensionAuth, 28 | WalletV5R1SendArgsSignable, 29 | WalletContractV5R1, 30 | WalletV5R1PackedCell, 31 | WalletV5R1SendArgs 32 | } from "../v5r1/WalletContractV5R1"; 33 | import {patchV5R1ActionsSendMode, storeOutListExtendedV5R1} from "../v5r1/WalletV5R1Actions"; 34 | 35 | 36 | function packSignatureToFront(signature: Buffer, signingMessage: Builder): Cell { 37 | const body = beginCell() 38 | .storeBuffer(signature) 39 | .storeBuilder(signingMessage) 40 | .endCell(); 41 | 42 | return body; 43 | } 44 | 45 | function packSignatureToTail(signature: Buffer, signingMessage: Builder): Cell { 46 | const body = beginCell() 47 | .storeBuilder(signingMessage) 48 | .storeBuffer(signature) 49 | .endCell(); 50 | 51 | return body; 52 | } 53 | 54 | export function createWalletTransferV1(args: { seqno: number, sendMode: number, message: Maybe, secretKey: Buffer }) { 55 | 56 | // Create message 57 | let signingMessage = beginCell() 58 | .storeUint(args.seqno, 32); 59 | if (args.message) { 60 | signingMessage.storeUint(args.sendMode, 8); 61 | signingMessage.storeRef(beginCell().store(storeMessageRelaxed(args.message))); 62 | } 63 | 64 | // Sign message 65 | let signature = sign(signingMessage.endCell().hash(), args.secretKey); 66 | 67 | // Body 68 | const body = beginCell() 69 | .storeBuffer(signature) 70 | .storeBuilder(signingMessage) 71 | .endCell(); 72 | 73 | return body; 74 | } 75 | 76 | export function createWalletTransferV2(args: { seqno: number, sendMode: number, messages: MessageRelaxed[], secretKey: Buffer, timeout?: Maybe }) { 77 | 78 | // Check number of messages 79 | if (args.messages.length > 4) { 80 | throw Error("Maximum number of messages in a single transfer is 4"); 81 | } 82 | 83 | // Create message 84 | let signingMessage = beginCell() 85 | .storeUint(args.seqno, 32); 86 | if (args.seqno === 0) { 87 | for (let i = 0; i < 32; i++) { 88 | signingMessage.storeBit(1); 89 | } 90 | } else { 91 | signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds 92 | } 93 | for (let m of args.messages) { 94 | signingMessage.storeUint(args.sendMode, 8); 95 | signingMessage.storeRef(beginCell().store(storeMessageRelaxed(m))); 96 | } 97 | 98 | // Sign message 99 | let signature = sign(signingMessage.endCell().hash(), args.secretKey); 100 | 101 | // Body 102 | const body = beginCell() 103 | .storeBuffer(signature) 104 | .storeBuilder(signingMessage) 105 | .endCell(); 106 | 107 | return body; 108 | } 109 | 110 | export function createWalletTransferV3( 111 | args: T & { sendMode: number, walletId: number } 112 | ) { 113 | 114 | // Check number of messages 115 | if (args.messages.length > 4) { 116 | throw Error("Maximum number of messages in a single transfer is 4"); 117 | } 118 | 119 | // Create message to sign 120 | let signingMessage = beginCell() 121 | .storeUint(args.walletId, 32); 122 | if (args.seqno === 0) { 123 | for (let i = 0; i < 32; i++) { 124 | signingMessage.storeBit(1); 125 | } 126 | } else { 127 | signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds 128 | } 129 | signingMessage.storeUint(args.seqno, 32); 130 | for (let m of args.messages) { 131 | signingMessage.storeUint(args.sendMode, 8); 132 | signingMessage.storeRef(beginCell().store(storeMessageRelaxed(m))); 133 | } 134 | 135 | return signPayload( 136 | args, 137 | signingMessage, 138 | packSignatureToFront, 139 | ) as T extends WalletV3SendArgsSignable ? Promise : Cell; 140 | } 141 | 142 | export function createWalletTransferV4( 143 | args: T & { sendMode: number, walletId: number } 144 | ) { 145 | 146 | // Check number of messages 147 | if (args.messages.length > 4) { 148 | throw Error("Maximum number of messages in a single transfer is 4"); 149 | } 150 | 151 | let signingMessage = beginCell() 152 | .storeUint(args.walletId, 32); 153 | if (args.seqno === 0) { 154 | for (let i = 0; i < 32; i++) { 155 | signingMessage.storeBit(1); 156 | } 157 | } else { 158 | signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds 159 | } 160 | signingMessage.storeUint(args.seqno, 32); 161 | signingMessage.storeUint(0, 8); // Simple order 162 | for (let m of args.messages) { 163 | signingMessage.storeUint(args.sendMode, 8); 164 | signingMessage.storeRef(beginCell().store(storeMessageRelaxed(m))); 165 | } 166 | 167 | return signPayload( 168 | args, 169 | signingMessage, 170 | packSignatureToFront, 171 | ) as T extends Wallet4SendArgsSignable ? Promise : Cell; 172 | } 173 | 174 | export function createWalletTransferV5Beta( 175 | args: T extends WalletV5BetaSendArgsExtensionAuth 176 | ? T & { actions: (OutActionSendMsg | OutActionExtended)[]} 177 | : T & { actions: (OutActionSendMsg | OutActionExtended)[], walletId: (builder: Builder) => void } 178 | ): WalletV5BetaPackedCell { 179 | // Check number of actions 180 | if (args.actions.length > 255) { 181 | throw Error("Maximum number of OutActions in a single request is 255"); 182 | } 183 | 184 | if (args.authType === 'extension') { 185 | return beginCell() 186 | .storeUint(WalletContractV5Beta.OpCodes.auth_extension, 32) 187 | .store(storeOutListExtendedV5Beta(args.actions)) 188 | .endCell() as WalletV5BetaPackedCell; 189 | } 190 | 191 | const signingMessage = beginCell() 192 | .storeUint(args.authType === 'internal' 193 | ? WalletContractV5Beta.OpCodes.auth_signed_internal 194 | : WalletContractV5Beta.OpCodes.auth_signed_external, 32) 195 | .store(args.walletId); 196 | 197 | if (args.seqno === 0) { 198 | for (let i = 0; i < 32; i++) { 199 | signingMessage.storeBit(1); 200 | } 201 | } else { 202 | signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds 203 | } 204 | 205 | signingMessage 206 | .storeUint(args.seqno, 32) 207 | .store(storeOutListExtendedV5Beta(args.actions)); 208 | 209 | return signPayload( 210 | args, 211 | signingMessage, 212 | packSignatureToTail, 213 | ) as T extends WalletV5BetaSendArgsSignable ? Promise : Cell; 214 | } 215 | 216 | export function createWalletTransferV5R1( 217 | args: T extends Wallet5VR1SendArgsExtensionAuth 218 | ? T & { actions: (OutActionSendMsg | OutActionExtended)[]} 219 | : T & { actions: (OutActionSendMsg | OutActionExtended)[], walletId: (builder: Builder) => void } 220 | ): WalletV5R1PackedCell { 221 | // Check number of actions 222 | if (args.actions.length > 255) { 223 | throw Error("Maximum number of OutActions in a single request is 255"); 224 | } 225 | args = {...args}; 226 | 227 | if (args.authType === 'extension') { 228 | return beginCell() 229 | .storeUint(WalletContractV5R1.OpCodes.auth_extension, 32) 230 | .storeUint(args.queryId ?? 0, 64) 231 | .store(storeOutListExtendedV5R1(args.actions)) 232 | .endCell() as WalletV5R1PackedCell; 233 | } 234 | 235 | args.actions = patchV5R1ActionsSendMode(args.actions, args.authType); 236 | 237 | const signingMessage = beginCell() 238 | .storeUint(args.authType === 'internal' 239 | ? WalletContractV5R1.OpCodes.auth_signed_internal 240 | : WalletContractV5R1.OpCodes.auth_signed_external, 32) 241 | .store(args.walletId); 242 | 243 | if (args.seqno === 0) { 244 | for (let i = 0; i < 32; i++) { 245 | signingMessage.storeBit(1); 246 | } 247 | } else { 248 | signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds 249 | } 250 | 251 | signingMessage 252 | .storeUint(args.seqno, 32) 253 | .store(storeOutListExtendedV5R1(args.actions)); 254 | 255 | return signPayload( 256 | args, 257 | signingMessage, 258 | packSignatureToTail, 259 | ) as T extends WalletV5R1SendArgsSignable ? Promise : Cell; 260 | } 261 | -------------------------------------------------------------------------------- /src/wallets/signing/singer.ts: -------------------------------------------------------------------------------- 1 | import { Builder, Cell } from "@ton/core"; 2 | import { sign } from "@ton/crypto"; 3 | 4 | export type SendArgsSigned = { 5 | secretKey: Buffer; 6 | } 7 | 8 | export type SendArgsSignable = { 9 | signer: (message: Cell) => Promise; 10 | } 11 | 12 | export function signPayload( 13 | args: T, 14 | signingMessage: Builder, 15 | packMessage: (signature: Buffer, signingMessage: Builder) => Cell 16 | ): T extends SendArgsSignable ? Promise : Cell { 17 | 18 | if ('secretKey' in args) { 19 | /** 20 | * Client provider an secretKey to sign transaction. 21 | */ 22 | return packMessage( 23 | sign(signingMessage.endCell().hash(), args.secretKey), 24 | signingMessage 25 | ) as T extends SendArgsSignable ? Promise : Cell; 26 | } 27 | else { 28 | /** 29 | * Client use external storage for secretKey. 30 | * In this case lib could create a request to external resource to sign transaction. 31 | */ 32 | return args.signer(signingMessage.endCell()) 33 | .then(signature => packMessage(signature, signingMessage)) as T extends SendArgsSignable ? Promise : Cell; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletContractV5Beta.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { 10 | Address, 11 | beginCell, 12 | Cell, 13 | Contract, 14 | contractAddress, 15 | ContractProvider, 16 | Dictionary, 17 | internal, 18 | MessageRelaxed, 19 | OutActionSendMsg, Sender, 20 | SendMode 21 | } from "@ton/core"; 22 | import {Maybe} from "../../utils/maybe"; 23 | import {SendArgsSignable, SendArgsSigned} from "../signing/singer"; 24 | import {OutActionWalletV5} from "./WalletV5OutActions"; 25 | import {createWalletTransferV5Beta} from "../signing/createWalletTransfer"; 26 | import {storeWalletIdV5Beta, WalletIdV5Beta} from "./WalletV5BetaWalletId"; 27 | 28 | 29 | export type WalletV5BetaBasicSendArgs = { 30 | seqno: number; 31 | timeout?: Maybe; 32 | } 33 | 34 | export type WalletV5BetaSendArgsSigned = WalletV5BetaBasicSendArgs 35 | & SendArgsSigned 36 | & { authType?: 'external' | 'internal';}; 37 | 38 | export type WalletV5BetaSendArgsSignable = WalletV5BetaBasicSendArgs 39 | & SendArgsSignable 40 | & { authType?: 'external' | 'internal'; }; 41 | 42 | export type WalletV5BetaSendArgsExtensionAuth = WalletV5BetaBasicSendArgs & { 43 | authType: 'extension'; 44 | } 45 | 46 | export type WalletV5BetaSendArgs = 47 | | WalletV5BetaSendArgsSigned 48 | | WalletV5BetaSendArgsSignable 49 | | WalletV5BetaSendArgsExtensionAuth 50 | 51 | 52 | export type WalletV5BetaPackedCell = T extends WalletV5BetaSendArgsSignable ? Promise : Cell; 53 | 54 | /** 55 | * @deprecated 56 | * use WalletContractV5R1 instead 57 | */ 58 | export class WalletContractV5Beta implements Contract { 59 | 60 | static OpCodes = { 61 | auth_extension: 0x6578746e, 62 | auth_signed_external: 0x7369676e, 63 | auth_signed_internal: 0x73696e74 64 | } 65 | 66 | static create(args: { 67 | walletId?: Partial, 68 | publicKey: Buffer 69 | }) { 70 | const walletId = { 71 | networkGlobalId: args.walletId?.networkGlobalId ?? -239, 72 | workchain: args?.walletId?.workchain ?? 0, 73 | subwalletNumber: args?.walletId?.subwalletNumber ?? 0, 74 | walletVersion: args?.walletId?.walletVersion ?? 'v5' 75 | } 76 | return new WalletContractV5Beta(walletId, args.publicKey); 77 | } 78 | 79 | readonly address: Address; 80 | readonly init: { data: Cell, code: Cell }; 81 | 82 | private constructor( 83 | readonly walletId: WalletIdV5Beta, 84 | readonly publicKey: Buffer 85 | ) { 86 | this.walletId = walletId; 87 | 88 | // https://github.com/tonkeeper/w5/commit/fa1b372a417a32af104fe1b949b6b31d29cee349 code with library 89 | let code = Cell.fromBoc(Buffer.from('te6cckEBAQEAIwAIQgLkzzsvTG1qYeoPK1RH0mZ4WyavNjfbLe7mvNGqgm80Eg3NjhE=', 'base64'))[0]; 90 | let data = beginCell() 91 | .storeInt(0, 33) // Seqno 92 | .store(storeWalletIdV5Beta(this.walletId)) 93 | .storeBuffer(this.publicKey, 32) 94 | .storeBit(0) // Empty plugins dict 95 | .endCell(); 96 | this.init = { code, data }; 97 | this.address = contractAddress(this.walletId.workchain, { code, data }); 98 | } 99 | 100 | /** 101 | * Get Wallet Balance 102 | */ 103 | async getBalance(provider: ContractProvider) { 104 | let state = await provider.getState(); 105 | return state.balance; 106 | } 107 | 108 | /** 109 | * Get Wallet Seqno 110 | */ 111 | async getSeqno(provider: ContractProvider) { 112 | let state = await provider.getState(); 113 | if (state.state.type === 'active') { 114 | let res = await provider.get('seqno', []); 115 | return res.stack.readNumber(); 116 | } else { 117 | return 0; 118 | } 119 | } 120 | 121 | /** 122 | * Get Wallet Extensions 123 | */ 124 | async getExtensions(provider: ContractProvider) { 125 | let state = await provider.getState(); 126 | if (state.state.type === 'active') { 127 | const result = await provider.get('get_extensions', []); 128 | return result.stack.readCellOpt(); 129 | } else { 130 | return null; 131 | } 132 | } 133 | 134 | /** 135 | * Get Wallet Extensions 136 | */ 137 | async getExtensionsArray(provider: ContractProvider) { 138 | const extensions = await this.getExtensions(provider); 139 | if (!extensions) { 140 | return []; 141 | } 142 | 143 | const dict: Dictionary = Dictionary.loadDirect( 144 | Dictionary.Keys.BigUint(256), 145 | Dictionary.Values.BigInt(8), 146 | extensions 147 | ); 148 | 149 | return dict.keys().map(key => { 150 | const wc = dict.get(key)!; 151 | const addressHex = key ^ (wc + 1n); 152 | return Address.parseRaw(`${wc}:${addressHex.toString(16).padStart(64, "0")}`); 153 | }) 154 | } 155 | 156 | /** 157 | * Get is secret-key authentication enabled 158 | */ 159 | async getIsSecretKeyAuthEnabled(provider: ContractProvider) { 160 | let res = await provider.get('get_is_signature_auth_allowed', []); 161 | const result = res.stack.readNumber(); 162 | return result !== 0; 163 | } 164 | 165 | /** 166 | * Send signed transfer 167 | */ 168 | async send(provider: ContractProvider, message: Cell) { 169 | await provider.external(message); 170 | } 171 | 172 | /** 173 | * Sign and send transfer 174 | */ 175 | async sendTransfer(provider: ContractProvider, args: WalletV5BetaSendArgs & { messages: MessageRelaxed[]; sendMode: SendMode }) { 176 | const transfer = await this.createTransfer(args); 177 | await this.send(provider, transfer); 178 | } 179 | 180 | /** 181 | * Sign and send add extension request 182 | */ 183 | async sendAddExtension(provider: ContractProvider, args: WalletV5BetaSendArgs & { extensionAddress: Address }) { 184 | const request = await this.createAddExtension(args); 185 | await this.send(provider, request); 186 | } 187 | 188 | /** 189 | * Sign and send remove extension request 190 | */ 191 | async sendRemoveExtension(provider: ContractProvider, args: WalletV5BetaSendArgs & { extensionAddress: Address, }) { 192 | const request = await this.createRemoveExtension(args); 193 | await this.send(provider, request); 194 | } 195 | 196 | /** 197 | * Sign and send actions batch 198 | */ 199 | async sendActionsBatch(provider: ContractProvider, args: WalletV5BetaSendArgs & { actions: OutActionWalletV5[] }) { 200 | const request = await this.createRequest(args); 201 | await this.send(provider, request); 202 | } 203 | 204 | private createActions( args: { messages: MessageRelaxed[], sendMode: SendMode }) { 205 | const actions: OutActionSendMsg[] = args.messages.map(message => ({ type: 'sendMsg', mode: args.sendMode, outMsg: message})); 206 | return actions; 207 | } 208 | 209 | /** 210 | * Create signed transfer 211 | */ 212 | createTransfer(args: T & { messages: MessageRelaxed[]; sendMode: SendMode }): WalletV5BetaPackedCell { 213 | return this.createRequest({ 214 | ...args, 215 | actions: this.createActions({ messages: args.messages, sendMode: args.sendMode }) 216 | }) 217 | } 218 | 219 | 220 | /** 221 | * Create signed add extension request 222 | */ 223 | createAddExtension(args: T & { extensionAddress: Address }): WalletV5BetaPackedCell { 224 | return this.createRequest({ 225 | ...args, 226 | actions: [{ 227 | type: 'addExtension', 228 | address: args.extensionAddress 229 | }] 230 | }) 231 | } 232 | 233 | /** 234 | * Create signed remove extension request 235 | */ 236 | createRemoveExtension(args: T & { extensionAddress: Address }): WalletV5BetaPackedCell { 237 | return this.createRequest({ 238 | ...args, 239 | actions: [{ 240 | type: 'removeExtension', 241 | address: args.extensionAddress 242 | }] 243 | }) 244 | } 245 | 246 | /** 247 | * Create signed request or extension auth request 248 | */ 249 | createRequest(args: T & { actions: OutActionWalletV5[] }): 250 | WalletV5BetaPackedCell { 251 | if (args.authType === 'extension') { 252 | return createWalletTransferV5Beta( 253 | args as WalletV5BetaSendArgsExtensionAuth & { actions: OutActionWalletV5[] } 254 | ) as WalletV5BetaPackedCell 255 | } 256 | 257 | return createWalletTransferV5Beta({ 258 | ...(args as (WalletV5BetaSendArgsSigned | WalletV5BetaSendArgsSignable) & { actions: OutActionWalletV5[] }), 259 | walletId: storeWalletIdV5Beta(this.walletId) 260 | }) as WalletV5BetaPackedCell; 261 | } 262 | 263 | /** 264 | * Create sender 265 | */ 266 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 267 | return { 268 | send: async (args) => { 269 | let seqno = await this.getSeqno(provider); 270 | let transfer = this.createTransfer({ 271 | seqno, 272 | secretKey, 273 | sendMode: args.sendMode ?? SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, 274 | messages: [internal({ 275 | to: args.to, 276 | value: args.value, 277 | extracurrency: args.extracurrency, 278 | init: args.init, 279 | body: args.body, 280 | bounce: args.bounce 281 | })] 282 | }); 283 | await this.send(provider, transfer); 284 | } 285 | }; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletV5BetaActions.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | beginCell, 3 | SendMode, 4 | storeMessageRelaxed, 5 | Address, 6 | OutAction, 7 | MessageRelaxed, 8 | OutActionSendMsg 9 | } from "@ton/core"; 10 | import { 11 | loadOutListExtendedV5Beta, 12 | storeOutActionExtendedV5Beta, 13 | storeOutListExtendedV5Beta 14 | } from "./WalletV5BetaActions"; 15 | import {OutActionExtended} from "./WalletV5OutActions"; 16 | 17 | const mockMessageRelaxed1: MessageRelaxed = { 18 | info: { 19 | type: 'external-out', 20 | createdLt: 0n, 21 | createdAt: 0, 22 | dest: null, 23 | src: null 24 | }, 25 | body: beginCell().storeUint(0,8).endCell(), 26 | init: null 27 | } 28 | const mockAddress = Address.parseRaw('0:' + '1'.repeat(64)) 29 | 30 | describe('Wallet V5Beta actions', () => { 31 | const outActionSetIsPublicKeyEnabledTag = 0x20cbb95a; 32 | const outActionAddExtensionTag = 0x1c40db9f; 33 | const outActionRemoveExtensionTag = 0x5eaef4a4; 34 | const outActionSendMsgTag = 0x0ec3c86d; 35 | 36 | it('Should serialise setIsPublicKeyEnabled action with true flag', () => { 37 | const action = storeOutActionExtendedV5Beta({ 38 | type: 'setIsPublicKeyEnabled', 39 | isEnabled: true 40 | }) ; 41 | 42 | const actual = beginCell().store(action).endCell(); 43 | 44 | const expected = beginCell() 45 | .storeUint(outActionSetIsPublicKeyEnabledTag, 32) 46 | .storeBit(1) 47 | .endCell(); 48 | 49 | expect(expected.equals(actual)).toBeTruthy(); 50 | }); 51 | 52 | it('Should serialise setIsPublicKeyEnabled action with false flag', () => { 53 | const action = storeOutActionExtendedV5Beta({ 54 | type: 'setIsPublicKeyEnabled', 55 | isEnabled: false 56 | }) ; 57 | 58 | const actual = beginCell().store(action).endCell(); 59 | 60 | const expected = beginCell() 61 | .storeUint(outActionSetIsPublicKeyEnabledTag, 32) 62 | .storeBit(0) 63 | .endCell(); 64 | 65 | expect(expected.equals(actual)).toBeTruthy(); 66 | }); 67 | 68 | it('Should serialise add extension action', () => { 69 | const action = storeOutActionExtendedV5Beta({ 70 | type: 'addExtension', 71 | address: mockAddress 72 | }) ; 73 | 74 | const actual = beginCell().store(action).endCell(); 75 | 76 | const expected = beginCell() 77 | .storeUint(outActionAddExtensionTag, 32) 78 | .storeAddress(mockAddress) 79 | .endCell(); 80 | 81 | expect(expected.equals(actual)).toBeTruthy(); 82 | }); 83 | 84 | it('Should serialise remove extension action', () => { 85 | const action = storeOutActionExtendedV5Beta({ 86 | type: 'removeExtension', 87 | address: mockAddress 88 | }) ; 89 | 90 | const actual = beginCell().store(action).endCell(); 91 | 92 | const expected = beginCell() 93 | .storeUint(outActionRemoveExtensionTag, 32) 94 | .storeAddress(mockAddress) 95 | .endCell(); 96 | 97 | expect(expected.equals(actual)).toBeTruthy(); 98 | }); 99 | 100 | it('Should serialize extended out list', () => { 101 | const sendMode1 = SendMode.PAY_GAS_SEPARATELY; 102 | const isPublicKeyEnabled = false; 103 | 104 | const actions: (OutActionExtended | OutActionSendMsg)[] = [ 105 | { 106 | type: 'addExtension', 107 | address: mockAddress 108 | }, 109 | { 110 | type: 'setIsPublicKeyEnabled', 111 | isEnabled: isPublicKeyEnabled 112 | }, 113 | { 114 | type: 'sendMsg', 115 | mode: sendMode1, 116 | outMsg: mockMessageRelaxed1 117 | } 118 | ] 119 | 120 | const actual = beginCell().store(storeOutListExtendedV5Beta(actions)).endCell(); 121 | 122 | const expected = 123 | beginCell() 124 | .storeUint(1, 1) 125 | .storeUint(outActionAddExtensionTag, 32) 126 | .storeAddress(mockAddress) 127 | .storeRef( 128 | beginCell() 129 | .storeUint(1, 1) 130 | .storeUint(outActionSetIsPublicKeyEnabledTag, 32) 131 | .storeBit(isPublicKeyEnabled ? 1 : 0) 132 | .storeRef( 133 | beginCell() 134 | .storeUint(0, 1) 135 | .storeRef( 136 | beginCell() 137 | .storeRef(beginCell().endCell()) 138 | .storeUint(outActionSendMsgTag, 32) 139 | .storeUint(sendMode1, 8) 140 | .storeRef(beginCell().store(storeMessageRelaxed(mockMessageRelaxed1)).endCell()) 141 | .endCell() 142 | ) 143 | .endCell() 144 | ) 145 | .endCell() 146 | ) 147 | .endCell() 148 | 149 | 150 | 151 | expect(actual.equals(expected)).toBeTruthy(); 152 | }); 153 | 154 | it('Should deserialize extended out list', () => { 155 | const sendMode1 = SendMode.PAY_GAS_SEPARATELY; 156 | const isPublicKeyEnabled = true; 157 | 158 | const expected: (OutActionExtended | OutAction)[] = [ 159 | { 160 | type: 'addExtension', 161 | address: mockAddress 162 | }, 163 | { 164 | type: 'setIsPublicKeyEnabled', 165 | isEnabled: isPublicKeyEnabled 166 | }, 167 | { 168 | type: 'sendMsg', 169 | mode: sendMode1, 170 | outMsg: mockMessageRelaxed1 171 | } 172 | ] 173 | 174 | const serialized = 175 | beginCell() 176 | .storeUint(1, 1) 177 | .storeUint(outActionAddExtensionTag, 32) 178 | .storeAddress(mockAddress) 179 | .storeRef( 180 | beginCell() 181 | .storeUint(1, 1) 182 | .storeUint(outActionSetIsPublicKeyEnabledTag, 32) 183 | .storeBit(isPublicKeyEnabled ? 1 : 0) 184 | .storeRef( 185 | beginCell() 186 | .storeUint(0, 1) 187 | .storeRef( 188 | beginCell() 189 | .storeRef(beginCell().endCell()) 190 | .storeUint(outActionSendMsgTag, 32) 191 | .storeUint(sendMode1, 8) 192 | .storeRef(beginCell().store(storeMessageRelaxed(mockMessageRelaxed1)).endCell()) 193 | .endCell() 194 | ) 195 | .endCell() 196 | ) 197 | .endCell() 198 | ) 199 | .endCell() 200 | 201 | const actual = loadOutListExtendedV5Beta(serialized.beginParse()) 202 | 203 | expect(expected.length).toEqual(actual.length); 204 | expected.forEach((item1, index) => { 205 | const item2 = actual[index]; 206 | expect(item1.type).toEqual(item2.type); 207 | 208 | if (item1.type === 'sendMsg' && item2.type === 'sendMsg') { 209 | expect(item1.mode).toEqual(item2.mode); 210 | expect(item1.outMsg.body.equals(item2.outMsg.body)).toBeTruthy(); 211 | expect(item1.outMsg.info).toEqual(item2.outMsg.info); 212 | expect(item1.outMsg.init).toEqual(item2.outMsg.init); 213 | } 214 | 215 | if (item1.type === 'addExtension' && item2.type === 'addExtension') { 216 | expect(item1.address.equals(item2.address)).toBeTruthy(); 217 | } 218 | 219 | if (item1.type === 'setIsPublicKeyEnabled' && item2.type === 'setIsPublicKeyEnabled') { 220 | expect(item1.isEnabled).toEqual(item2.isEnabled); 221 | } 222 | }) 223 | }); 224 | }) 225 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletV5BetaActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | beginCell, 3 | Builder, 4 | loadOutList, 5 | OutActionSendMsg, 6 | Slice, 7 | storeOutList 8 | } from '@ton/core'; 9 | import { 10 | isOutActionExtended, 11 | OutActionAddExtension, 12 | OutActionExtended, 13 | OutActionRemoveExtension, 14 | OutActionSetIsPublicKeyEnabled 15 | } from "./WalletV5OutActions"; 16 | 17 | const outActionSetIsPublicKeyEnabledTag = 0x20cbb95a; 18 | function storeOutActionSetIsPublicKeyEnabled(action: OutActionSetIsPublicKeyEnabled) { 19 | return (builder: Builder) => { 20 | builder.storeUint(outActionSetIsPublicKeyEnabledTag, 32).storeUint(action.isEnabled ? 1 : 0, 1) 21 | } 22 | } 23 | 24 | const outActionAddExtensionTag = 0x1c40db9f; 25 | function storeOutActionAddExtension(action: OutActionAddExtension) { 26 | return (builder: Builder) => { 27 | builder.storeUint(outActionAddExtensionTag, 32).storeAddress(action.address) 28 | } 29 | } 30 | 31 | const outActionRemoveExtensionTag = 0x5eaef4a4; 32 | function storeOutActionRemoveExtension(action: OutActionRemoveExtension) { 33 | return (builder: Builder) => { 34 | builder.storeUint(outActionRemoveExtensionTag, 32).storeAddress(action.address) 35 | } 36 | } 37 | 38 | export function storeOutActionExtendedV5Beta(action: OutActionExtended) { 39 | switch (action.type) { 40 | case 'setIsPublicKeyEnabled': 41 | return storeOutActionSetIsPublicKeyEnabled(action); 42 | case 'addExtension': 43 | return storeOutActionAddExtension(action); 44 | case 'removeExtension': 45 | return storeOutActionRemoveExtension(action); 46 | default: 47 | throw new Error('Unknown action type' + (action as OutActionExtended)?.type); 48 | } 49 | } 50 | 51 | export function loadOutActionV5BetaExtended(slice: Slice): OutActionExtended { 52 | const tag = slice.loadUint(32); 53 | 54 | switch (tag) { 55 | case outActionSetIsPublicKeyEnabledTag: 56 | return { 57 | type: 'setIsPublicKeyEnabled', 58 | isEnabled: !!slice.loadUint(1) 59 | } 60 | case outActionAddExtensionTag: 61 | return { 62 | type: 'addExtension', 63 | address: slice.loadAddress() 64 | } 65 | case outActionRemoveExtensionTag: 66 | return { 67 | type: 'removeExtension', 68 | address: slice.loadAddress() 69 | } 70 | default: 71 | throw new Error(`Unknown extended out action tag 0x${tag.toString(16)}`); 72 | } 73 | } 74 | 75 | export function storeOutListExtendedV5Beta(actions: (OutActionExtended | OutActionSendMsg)[]) { 76 | const [action, ...rest] = actions; 77 | 78 | if (!action || !isOutActionExtended(action)) { 79 | if (actions.some(isOutActionExtended)) { 80 | throw new Error("Can't serialize actions list: all extended actions must be placed before out actions"); 81 | } 82 | 83 | return (builder: Builder) => { 84 | builder 85 | .storeUint(0, 1) 86 | .storeRef(beginCell().store(storeOutList(actions as OutActionSendMsg[])).endCell()) 87 | } 88 | } 89 | 90 | return (builder: Builder) => { 91 | builder.storeUint(1, 1) 92 | .store(storeOutActionExtendedV5Beta(action)) 93 | .storeRef(beginCell().store(storeOutListExtendedV5Beta(rest)).endCell()) 94 | } 95 | } 96 | 97 | export function loadOutListExtendedV5Beta(slice: Slice): (OutActionExtended | OutActionSendMsg)[] { 98 | const actions: (OutActionExtended | OutActionSendMsg)[] = []; 99 | 100 | while (slice.loadUint(1)) { 101 | const action = loadOutActionV5BetaExtended(slice); 102 | actions.push(action); 103 | 104 | slice = slice.loadRef().beginParse(); 105 | } 106 | 107 | const commonAction = loadOutList(slice.loadRef().beginParse()); 108 | if (commonAction.some(i => i.type === 'setCode')) { 109 | throw new Error("Can't deserialize actions list: only sendMsg actions are allowed for wallet v5"); 110 | } 111 | 112 | return actions.concat(commonAction as OutActionSendMsg[]); 113 | } 114 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletV5BetaWalletId.spec.ts: -------------------------------------------------------------------------------- 1 | import {beginCell} from "@ton/core"; 2 | import { 3 | loadWalletIdV5Beta, 4 | storeWalletIdV5Beta, 5 | WalletIdV5Beta 6 | } from "./WalletV5BetaWalletId"; 7 | 8 | describe('Wallet V5Beta wallet id', () => { 9 | it('Should serialise wallet id', () => { 10 | const walletId: WalletIdV5Beta = { 11 | walletVersion: 'v5', 12 | networkGlobalId: -239, 13 | workchain: 0, 14 | subwalletNumber: 0 15 | } 16 | 17 | const actual = beginCell().store(storeWalletIdV5Beta(walletId)).endCell(); 18 | 19 | const expected = beginCell() 20 | .storeInt(walletId.networkGlobalId, 32) 21 | .storeInt(walletId.workchain, 8) 22 | .storeUint(0, 8) 23 | .storeUint(walletId.subwalletNumber, 32) 24 | .endCell(); 25 | 26 | expect(expected.equals(actual)).toBeTruthy(); 27 | }); 28 | 29 | it('Should deserialise wallet id', () => { 30 | const expected: WalletIdV5Beta = { 31 | walletVersion: 'v5', 32 | networkGlobalId: -239, 33 | workchain: 0, 34 | subwalletNumber: 0 35 | } 36 | 37 | const actual = loadWalletIdV5Beta(beginCell() 38 | .storeInt(expected.networkGlobalId, 32) 39 | .storeInt(expected.workchain, 8) 40 | .storeUint(0, 8) 41 | .storeUint(expected.subwalletNumber, 32) 42 | .endCell().beginParse()); 43 | 44 | 45 | expect(expected).toEqual(actual); 46 | }); 47 | 48 | it('Should serialise wallet id', () => { 49 | const walletId: WalletIdV5Beta = { 50 | walletVersion: 'v5', 51 | networkGlobalId: -3, 52 | workchain: -1, 53 | subwalletNumber: 1234 54 | } 55 | 56 | const actual = beginCell().store(storeWalletIdV5Beta(walletId)).endCell(); 57 | 58 | const expected = beginCell() 59 | .storeInt(walletId.networkGlobalId, 32) 60 | .storeInt(walletId.workchain, 8) 61 | .storeUint(0, 8) 62 | .storeUint(walletId.subwalletNumber, 32) 63 | .endCell(); 64 | 65 | expect(expected.equals(actual)).toBeTruthy(); 66 | }); 67 | 68 | it('Should deserialise wallet id', () => { 69 | const expected: WalletIdV5Beta = { 70 | walletVersion: 'v5', 71 | networkGlobalId: -239, 72 | workchain: -1, 73 | subwalletNumber: 1 74 | } 75 | 76 | const actual = loadWalletIdV5Beta(beginCell() 77 | .storeInt(expected.networkGlobalId, 32) 78 | .storeInt(expected.workchain, 8) 79 | .storeUint(0, 8) 80 | .storeUint(expected.subwalletNumber, 32) 81 | .endCell().beginParse()); 82 | 83 | 84 | expect(expected).toEqual(actual); 85 | }); 86 | }) 87 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletV5BetaWalletId.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BitReader, 3 | BitString, 4 | Builder, 5 | Slice 6 | } from '@ton/core'; 7 | 8 | 9 | export interface WalletIdV5Beta { 10 | readonly walletVersion: 'v5'; 11 | 12 | /** 13 | * -239 is mainnet, -3 is testnet 14 | */ 15 | readonly networkGlobalId: number; 16 | 17 | readonly workchain: number; 18 | 19 | readonly subwalletNumber: number; 20 | } 21 | 22 | const walletV5BetaVersionsSerialisation: Record = { 23 | v5: 0 24 | }; 25 | export function loadWalletIdV5Beta(value: bigint | Buffer | Slice): WalletIdV5Beta { 26 | const bitReader = new BitReader( 27 | new BitString( 28 | typeof value === 'bigint' ? 29 | Buffer.from(value.toString(16), 'hex') : 30 | value instanceof Slice ? value.loadBuffer(10) : value, 31 | 0, 32 | 80 33 | ) 34 | ); 35 | const networkGlobalId = bitReader.loadInt(32); 36 | const workchain = bitReader.loadInt(8); 37 | const walletVersionRaw = bitReader.loadUint(8); 38 | const subwalletNumber = bitReader.loadUint(32); 39 | 40 | const walletVersion = Object.entries(walletV5BetaVersionsSerialisation).find( 41 | ([_, value]) => value === walletVersionRaw 42 | )?.[0] as WalletIdV5Beta['walletVersion'] | undefined; 43 | 44 | if (walletVersion === undefined) { 45 | throw new Error( 46 | `Can't deserialize walletId: unknown wallet version ${walletVersionRaw}` 47 | ); 48 | } 49 | 50 | return { networkGlobalId, workchain, walletVersion, subwalletNumber } 51 | } 52 | 53 | export function storeWalletIdV5Beta(walletId: WalletIdV5Beta) { 54 | return (builder: Builder) => { 55 | builder.storeInt(walletId.networkGlobalId, 32); 56 | builder.storeInt(walletId.workchain, 8); 57 | builder.storeUint(walletV5BetaVersionsSerialisation[walletId.walletVersion], 8); 58 | builder.storeUint(walletId.subwalletNumber, 32); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/wallets/v5beta/WalletV5OutActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | OutActionSendMsg 4 | } from '@ton/core'; 5 | 6 | export interface OutActionAddExtension { 7 | type: 'addExtension'; 8 | address: Address; 9 | } 10 | 11 | export interface OutActionRemoveExtension { 12 | type: 'removeExtension'; 13 | address: Address; 14 | } 15 | 16 | export interface OutActionSetIsPublicKeyEnabled { 17 | type: 'setIsPublicKeyEnabled'; 18 | isEnabled: boolean; 19 | } 20 | 21 | export type OutActionExtended = OutActionSetIsPublicKeyEnabled | OutActionAddExtension | OutActionRemoveExtension; 22 | export type OutActionWalletV5 = OutActionExtended | OutActionSendMsg; 23 | 24 | export function isOutActionExtended(action: OutActionSendMsg | OutActionExtended): action is OutActionExtended { 25 | return ( 26 | action.type === 'setIsPublicKeyEnabled' || action.type === 'addExtension' || action.type === 'removeExtension' 27 | ); 28 | } 29 | 30 | export function isOutActionBasic(action: OutActionSendMsg | OutActionExtended): action is OutActionSendMsg { 31 | return !isOutActionExtended(action); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/wallets/v5r1/WalletContractV5R1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Whales Corp. 3 | * All Rights Reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | import { 10 | Address, 11 | beginCell, 12 | Cell, 13 | Contract, 14 | contractAddress, 15 | ContractProvider, 16 | Dictionary, 17 | internal, 18 | MessageRelaxed, 19 | OutActionSendMsg, Sender, 20 | SendMode 21 | } from "@ton/core"; 22 | import {Maybe} from "../../utils/maybe"; 23 | import {createWalletTransferV5R1} from "../signing/createWalletTransfer"; 24 | import {SendArgsSignable, SendArgsSigned} from "../signing/singer"; 25 | import {OutActionWalletV5} from "../v5beta/WalletV5OutActions"; 26 | import { 27 | isWalletIdV5R1ClientContext, 28 | storeWalletIdV5R1, 29 | WalletIdV5R1, 30 | WalletIdV5R1ClientContext, 31 | WalletIdV5R1CustomContext 32 | } from "./WalletV5R1WalletId"; 33 | 34 | 35 | export type WalletV5R1BasicSendArgs = { 36 | seqno: number; 37 | timeout?: Maybe; 38 | } 39 | 40 | export type WalletV5R1SendArgsSinged = WalletV5R1BasicSendArgs 41 | & SendArgsSigned 42 | & { authType?: 'external' | 'internal';}; 43 | 44 | export type WalletV5R1SendArgsSignable = WalletV5R1BasicSendArgs 45 | & SendArgsSignable 46 | & { authType?: 'external' | 'internal'; }; 47 | 48 | export type Wallet5VR1SendArgsExtensionAuth = WalletV5R1BasicSendArgs & { 49 | authType: 'extension'; 50 | queryId?: bigint; 51 | } 52 | 53 | export type WalletV5R1SendArgs = 54 | | WalletV5R1SendArgsSinged 55 | | WalletV5R1SendArgsSignable 56 | | Wallet5VR1SendArgsExtensionAuth; 57 | 58 | export type WalletV5R1PackedCell = T extends WalletV5R1SendArgsSignable ? Promise : Cell; 59 | 60 | 61 | export class WalletContractV5R1 implements Contract { 62 | 63 | static OpCodes = { 64 | auth_extension: 0x6578746e, 65 | auth_signed_external: 0x7369676e, 66 | auth_signed_internal: 0x73696e74 67 | } 68 | 69 | static create(args: C extends WalletIdV5R1ClientContext ?{ 70 | walletId?: Maybe>, 71 | publicKey: Buffer 72 | } : { 73 | workchain?: number 74 | publicKey: Buffer 75 | walletId?: Maybe>> 76 | }) { 77 | let workchain = 0; 78 | 79 | if ('workchain' in args && args.workchain != undefined) { 80 | workchain = args.workchain; 81 | } 82 | 83 | if (args.walletId?.context && isWalletIdV5R1ClientContext(args.walletId.context) && args.walletId.context.workchain != undefined) { 84 | workchain = args.walletId.context.workchain; 85 | } 86 | 87 | return new WalletContractV5R1(workchain, args.publicKey, { 88 | networkGlobalId: args.walletId?.networkGlobalId ?? -239, 89 | context: args.walletId?.context ?? { 90 | workchain: 0, 91 | walletVersion: 'v5r1', 92 | subwalletNumber: 0 93 | } 94 | }); 95 | } 96 | 97 | readonly address: Address; 98 | readonly init: { data: Cell, code: Cell }; 99 | 100 | private constructor( 101 | workchain: number, 102 | readonly publicKey: Buffer, 103 | readonly walletId: WalletIdV5R1, 104 | ) { 105 | this.walletId = walletId; 106 | 107 | // https://github.com/ton-blockchain/wallet-contract-v5/blob/4fab977f4fae3a37c1aac216ed2b7e611a9bc2af/build/wallet_v5.compiled.json 108 | let code = Cell.fromBoc(Buffer.from('b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e', 'hex'))[0]; 109 | let data = beginCell() 110 | .storeUint(1, 1) // is signature auth allowed 111 | .storeUint(0, 32) // Seqno 112 | .store(storeWalletIdV5R1(this.walletId)) 113 | .storeBuffer(this.publicKey, 32) 114 | .storeBit(0) // Empty plugins dict 115 | .endCell(); 116 | this.init = { code, data }; 117 | this.address = contractAddress(workchain, { code, data }); 118 | } 119 | 120 | /** 121 | * Get Wallet Balance 122 | */ 123 | async getBalance(provider: ContractProvider) { 124 | let state = await provider.getState(); 125 | return state.balance; 126 | } 127 | 128 | /** 129 | * Get Wallet Seqno 130 | */ 131 | async getSeqno(provider: ContractProvider) { 132 | let state = await provider.getState(); 133 | if (state.state.type === 'active') { 134 | let res = await provider.get('seqno', []); 135 | return res.stack.readNumber(); 136 | } else { 137 | return 0; 138 | } 139 | } 140 | 141 | /** 142 | * Get Wallet Extensions 143 | */ 144 | async getExtensions(provider: ContractProvider) { 145 | let state = await provider.getState(); 146 | if (state.state.type === 'active') { 147 | const result = await provider.get('get_extensions', []); 148 | return result.stack.readCellOpt(); 149 | } else { 150 | return null; 151 | } 152 | } 153 | 154 | /** 155 | * Get Wallet Extensions 156 | */ 157 | async getExtensionsArray(provider: ContractProvider) { 158 | const extensions = await this.getExtensions(provider); 159 | if (!extensions) { 160 | return []; 161 | } 162 | 163 | const dict: Dictionary = Dictionary.loadDirect( 164 | Dictionary.Keys.BigUint(256), 165 | Dictionary.Values.BigInt(1), 166 | extensions 167 | ); 168 | 169 | return dict.keys().map(addressHex => { 170 | const wc = this.address.workChain; 171 | return Address.parseRaw(`${wc}:${addressHex.toString(16).padStart(64, '0')}`); 172 | }) 173 | } 174 | 175 | /** 176 | * Get is secret-key authentication enabled 177 | */ 178 | async getIsSecretKeyAuthEnabled(provider: ContractProvider) { 179 | let res = await provider.get('is_signature_allowed', []); 180 | return res.stack.readBoolean(); 181 | } 182 | 183 | /** 184 | * Send signed transfer 185 | */ 186 | async send(provider: ContractProvider, message: Cell) { 187 | await provider.external(message); 188 | } 189 | 190 | /** 191 | * Sign and send transfer 192 | */ 193 | async sendTransfer(provider: ContractProvider, args: WalletV5R1SendArgs & { messages: MessageRelaxed[]; sendMode: SendMode }) { 194 | const transfer = await this.createTransfer(args); 195 | await this.send(provider, transfer); 196 | } 197 | 198 | /** 199 | * Sign and send add extension request 200 | */ 201 | async sendAddExtension(provider: ContractProvider, args: WalletV5R1SendArgs & { extensionAddress: Address }) { 202 | const request = await this.createAddExtension(args); 203 | await this.send(provider, request); 204 | } 205 | 206 | /** 207 | * Sign and send remove extension request 208 | */ 209 | async sendRemoveExtension(provider: ContractProvider, args: WalletV5R1SendArgs & { extensionAddress: Address, }) { 210 | const request = await this.createRemoveExtension(args); 211 | await this.send(provider, request); 212 | } 213 | 214 | private createActions( args: { messages: MessageRelaxed[], sendMode: SendMode }) { 215 | const actions: OutActionSendMsg[] = args.messages.map(message => ({ type: 'sendMsg', mode: args.sendMode, outMsg: message})); 216 | return actions; 217 | } 218 | 219 | /** 220 | * Create signed transfer 221 | */ 222 | createTransfer(args: T & { messages: MessageRelaxed[]; sendMode: SendMode }): WalletV5R1PackedCell { 223 | return this.createRequest({ 224 | actions: this.createActions({ messages: args.messages, sendMode: args.sendMode }), 225 | ...args 226 | }) 227 | } 228 | 229 | /** 230 | * Create signed add extension request 231 | */ 232 | createAddExtension(args: T & { extensionAddress: Address }): WalletV5R1PackedCell { 233 | return this.createRequest({ 234 | actions: [{ 235 | type: 'addExtension', 236 | address: args.extensionAddress 237 | }], 238 | ...args 239 | }) 240 | } 241 | 242 | /** 243 | * Create signed remove extension request 244 | */ 245 | createRemoveExtension(args: T & { extensionAddress: Address }): WalletV5R1PackedCell { 246 | return this.createRequest({ 247 | actions: [{ 248 | type: 'removeExtension', 249 | address: args.extensionAddress 250 | }], 251 | ...args 252 | }) 253 | } 254 | 255 | /** 256 | * Create signed request or extension auth request 257 | */ 258 | createRequest(args: T & { actions: OutActionWalletV5[] }): WalletV5R1PackedCell { 259 | if (args.authType === 'extension') { 260 | return createWalletTransferV5R1(args as Wallet5VR1SendArgsExtensionAuth & { actions: OutActionWalletV5[] }) as WalletV5R1PackedCell; 261 | } 262 | 263 | return createWalletTransferV5R1({ 264 | ...(args as (WalletV5R1SendArgsSinged | WalletV5R1SendArgsSignable) & { actions: OutActionWalletV5[] }), 265 | walletId: storeWalletIdV5R1(this.walletId) 266 | }) as WalletV5R1PackedCell; 267 | } 268 | 269 | /** 270 | * Create sender 271 | */ 272 | sender(provider: ContractProvider, secretKey: Buffer): Sender { 273 | return { 274 | send: async (args) => { 275 | let seqno = await this.getSeqno(provider); 276 | let transfer = this.createTransfer({ 277 | seqno, 278 | secretKey, 279 | sendMode: args.sendMode ?? SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, 280 | messages: [internal({ 281 | to: args.to, 282 | value: args.value, 283 | extracurrency: args.extracurrency, 284 | init: args.init, 285 | body: args.body, 286 | bounce: args.bounce 287 | })] 288 | }); 289 | await this.send(provider, transfer); 290 | } 291 | }; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/wallets/v5r1/WalletV5R1Actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | beginCell, 3 | Builder, Cell, 4 | loadOutList, 5 | OutActionSendMsg, SendMode, 6 | Slice, 7 | storeOutList 8 | } from '@ton/core'; 9 | import { 10 | isOutActionBasic, 11 | isOutActionExtended, 12 | OutActionAddExtension, 13 | OutActionExtended, 14 | OutActionRemoveExtension, 15 | OutActionSetIsPublicKeyEnabled, OutActionWalletV5 16 | } from "../v5beta/WalletV5OutActions"; 17 | import {WalletV5R1SendArgs} from "./WalletContractV5R1"; 18 | 19 | 20 | const outActionSetIsPublicKeyEnabledTag = 0x04; 21 | function storeOutActionSetIsPublicKeyEnabled(action: OutActionSetIsPublicKeyEnabled) { 22 | return (builder: Builder) => { 23 | builder.storeUint(outActionSetIsPublicKeyEnabledTag, 8).storeUint(action.isEnabled ? 1 : 0, 1) 24 | } 25 | } 26 | 27 | const outActionAddExtensionTag = 0x02; 28 | function storeOutActionAddExtension(action: OutActionAddExtension) { 29 | return (builder: Builder) => { 30 | builder.storeUint(outActionAddExtensionTag, 8).storeAddress(action.address) 31 | } 32 | } 33 | 34 | const outActionRemoveExtensionTag = 0x03; 35 | function storeOutActionRemoveExtension(action: OutActionRemoveExtension) { 36 | return (builder: Builder) => { 37 | builder.storeUint(outActionRemoveExtensionTag, 8).storeAddress(action.address) 38 | } 39 | } 40 | 41 | export function storeOutActionExtendedV5R1(action: OutActionExtended) { 42 | switch (action.type) { 43 | case 'setIsPublicKeyEnabled': 44 | return storeOutActionSetIsPublicKeyEnabled(action); 45 | case 'addExtension': 46 | return storeOutActionAddExtension(action); 47 | case 'removeExtension': 48 | return storeOutActionRemoveExtension(action); 49 | default: 50 | throw new Error('Unknown action type' + (action as OutActionExtended)?.type); 51 | } 52 | } 53 | 54 | export function loadOutActionExtendedV5R1(slice: Slice): OutActionExtended { 55 | const tag = slice.loadUint(8); 56 | 57 | switch (tag) { 58 | case outActionSetIsPublicKeyEnabledTag: 59 | return { 60 | type: 'setIsPublicKeyEnabled', 61 | isEnabled: !!slice.loadUint(1) 62 | } 63 | case outActionAddExtensionTag: 64 | return { 65 | type: 'addExtension', 66 | address: slice.loadAddress() 67 | } 68 | case outActionRemoveExtensionTag: 69 | return { 70 | type: 'removeExtension', 71 | address: slice.loadAddress() 72 | } 73 | default: 74 | throw new Error(`Unknown extended out action tag 0x${tag.toString(16)}`); 75 | } 76 | } 77 | 78 | export function storeOutListExtendedV5R1(actions: (OutActionExtended | OutActionSendMsg)[]) { 79 | const extendedActions = actions.filter(isOutActionExtended); 80 | const basicActions = actions.filter(isOutActionBasic); 81 | 82 | return (builder: Builder) => { 83 | const outListPacked = basicActions.length ? beginCell().store(storeOutList(basicActions.slice().reverse())) : null; 84 | builder.storeMaybeRef(outListPacked); 85 | 86 | if (extendedActions.length === 0) { 87 | builder.storeUint(0, 1); 88 | } else { 89 | const [first, ...rest] = extendedActions; 90 | builder 91 | .storeUint(1, 1) 92 | .store(storeOutActionExtendedV5R1(first)); 93 | if (rest.length > 0) { 94 | builder.storeRef(packExtendedActionsRec(rest)); 95 | } 96 | } 97 | } 98 | } 99 | 100 | function packExtendedActionsRec(extendedActions: OutActionExtended[]): Cell { 101 | const [first, ...rest] = extendedActions; 102 | let builder = beginCell() 103 | .store(storeOutActionExtendedV5R1(first)); 104 | if (rest.length > 0) { 105 | builder = builder.storeRef(packExtendedActionsRec(rest)); 106 | } 107 | return builder.endCell(); 108 | } 109 | 110 | export function loadOutListExtendedV5R1(slice: Slice): (OutActionExtended | OutActionSendMsg)[] { 111 | const actions: (OutActionExtended | OutActionSendMsg)[] = []; 112 | const outListPacked = slice.loadMaybeRef(); 113 | if (outListPacked) { 114 | const loadedActions =loadOutList(outListPacked.beginParse()); 115 | if (loadedActions.some(a => a.type !== 'sendMsg')) { 116 | throw new Error("Can't deserialize actions list: only sendMsg actions are allowed for wallet v5r1"); 117 | } 118 | 119 | actions.push(...loadedActions as OutActionSendMsg[]); 120 | } 121 | 122 | if (slice.loadBoolean()) { 123 | const action = loadOutActionExtendedV5R1(slice); 124 | actions.push(action); 125 | } 126 | 127 | while (slice.remainingRefs > 0) { 128 | slice = slice.loadRef().beginParse(); 129 | const action = loadOutActionExtendedV5R1(slice); 130 | actions.push(action); 131 | } 132 | 133 | return actions; 134 | } 135 | 136 | /** 137 | * Safety rules -- actions of external messages must have +2 in the SendMode. Internal messages actions may have arbitrary SendMode. 138 | */ 139 | export function toSafeV5R1SendMode(sendMode: SendMode, authType: WalletV5R1SendArgs['authType']) { 140 | if (authType === 'internal' || authType === 'extension') { 141 | return sendMode; 142 | } 143 | 144 | return sendMode | SendMode.IGNORE_ERRORS 145 | } 146 | 147 | export function patchV5R1ActionsSendMode(actions: OutActionWalletV5[], authType: WalletV5R1SendArgs['authType']): OutActionWalletV5[] { 148 | return actions.map(action => action.type === 'sendMsg' ? ({ 149 | ...action, 150 | mode: toSafeV5R1SendMode(action.mode, authType) 151 | }) : action) 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/wallets/v5r1/WalletV5R1WalletId.spec.ts: -------------------------------------------------------------------------------- 1 | import {beginCell} from "@ton/core"; 2 | import { 3 | loadWalletIdV5R1, 4 | storeWalletIdV5R1, 5 | WalletIdV5R1, 6 | WalletIdV5R1ClientContext, 7 | WalletIdV5R1CustomContext 8 | } from "./WalletV5R1WalletId"; 9 | 10 | describe('Wallet V5R1 wallet id', () => { 11 | it('Should serialise wallet id', () => { 12 | const walletId: WalletIdV5R1 = { 13 | networkGlobalId: -239, 14 | context: { 15 | walletVersion: 'v5r1', 16 | workchain: 0, 17 | subwalletNumber: 0 18 | } 19 | } 20 | 21 | const actual = beginCell().store(storeWalletIdV5R1(walletId)).endCell(); 22 | 23 | const context = beginCell() 24 | .storeUint(1, 1) 25 | .storeInt(walletId.context.workchain, 8) 26 | .storeUint(0, 8) 27 | .storeUint(walletId.context.subwalletNumber, 15) 28 | .endCell().beginParse().loadInt(32); 29 | 30 | const expected = beginCell().storeInt(BigInt(context) ^ BigInt(walletId.networkGlobalId), 32).endCell(); 31 | 32 | expect(expected.equals(actual)).toBeTruthy(); 33 | }); 34 | 35 | it('Should deserialise wallet id', () => { 36 | const expected: WalletIdV5R1 = { 37 | networkGlobalId: -239, 38 | context: { 39 | walletVersion: 'v5r1', 40 | workchain: 0, 41 | subwalletNumber: 0 42 | } 43 | } 44 | 45 | const context = beginCell() 46 | .storeUint(1, 1) 47 | .storeInt(expected.context.workchain, 8) 48 | .storeUint(0, 8) 49 | .storeUint(expected.context.subwalletNumber, 15) 50 | .endCell().beginParse().loadInt(32); 51 | 52 | const actual = loadWalletIdV5R1(beginCell().storeInt(BigInt(context) ^ BigInt(expected.networkGlobalId), 32).endCell().beginParse(), expected.networkGlobalId); 53 | 54 | 55 | expect(expected).toEqual(actual); 56 | }); 57 | 58 | it('Should serialise wallet id', () => { 59 | const walletId: WalletIdV5R1 = { 60 | networkGlobalId: -3, 61 | context: 239239239 62 | } 63 | 64 | const context = beginCell() 65 | .storeUint(0, 1) 66 | .storeUint(walletId.context, 31) 67 | .endCell().beginParse().loadInt(32); 68 | 69 | 70 | const actual = beginCell().store(storeWalletIdV5R1(walletId)).endCell(); 71 | 72 | const expected = beginCell() 73 | .storeInt(BigInt(context) ^ BigInt(walletId.networkGlobalId), 32) 74 | .endCell(); 75 | 76 | expect(expected.equals(actual)).toBeTruthy(); 77 | }); 78 | 79 | it('Should deserialise wallet id', () => { 80 | const expected: WalletIdV5R1 = { 81 | networkGlobalId: -3, 82 | context: 239239239 83 | } 84 | 85 | const context = beginCell() 86 | .storeUint(0, 1) 87 | .storeUint(expected.context, 31) 88 | .endCell().beginParse().loadInt(32); 89 | 90 | const actual = loadWalletIdV5R1(beginCell() 91 | .storeInt(BigInt(context) ^ BigInt(expected.networkGlobalId), 32) 92 | .endCell().beginParse(), expected.networkGlobalId); 93 | 94 | 95 | expect(expected).toEqual(actual); 96 | }); 97 | }) 98 | -------------------------------------------------------------------------------- /src/wallets/v5r1/WalletV5R1WalletId.ts: -------------------------------------------------------------------------------- 1 | import { 2 | beginCell, 3 | BitReader, 4 | BitString, 5 | Builder, 6 | Slice 7 | } from '@ton/core'; 8 | 9 | 10 | /** 11 | * schema: 12 | * wallet_id -- int32 13 | * wallet_id = global_id ^ context_id 14 | * context_id_client$1 = wc:int8 wallet_version:uint8 counter:uint15 15 | * context_id_backoffice$0 = counter:uint31 16 | * 17 | * 18 | * calculated default values serialisation: 19 | * 20 | * global_id = -239, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context) 21 | * gives wallet_id = 2147483409 22 | * 23 | * global_id = -239, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context) 24 | * gives wallet_id = 8388369 25 | * 26 | * global_id = -3, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context) 27 | * gives wallet_id = 2147483645 28 | * 29 | * global_id = -3, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context) 30 | * gives wallet_id = 8388605 31 | */ 32 | export interface WalletIdV5R1 { 33 | /** 34 | * -239 is mainnet, -3 is testnet 35 | */ 36 | readonly networkGlobalId: number; 37 | 38 | readonly context: C; 39 | } 40 | 41 | export interface WalletIdV5R1ClientContext { 42 | readonly walletVersion: 'v5r1'; 43 | 44 | readonly workchain: number; 45 | 46 | readonly subwalletNumber: number; 47 | } 48 | 49 | /** 50 | * 31-bit unsigned integer 51 | */ 52 | export type WalletIdV5R1CustomContext = number; 53 | 54 | export function isWalletIdV5R1ClientContext(context: WalletIdV5R1ClientContext | WalletIdV5R1CustomContext): context is WalletIdV5R1ClientContext { 55 | return typeof context !== 'number'; 56 | } 57 | 58 | const walletV5R1VersionsSerialisation: Record = { 59 | v5r1: 0 60 | }; 61 | 62 | /** 63 | * @param value serialized wallet id 64 | * @param networkGlobalId -239 is mainnet, -3 is testnet 65 | */ 66 | export function loadWalletIdV5R1(value: bigint | Buffer | Slice, networkGlobalId: number): WalletIdV5R1 { 67 | const val = new BitReader( 68 | new BitString( 69 | typeof value === 'bigint' ? 70 | Buffer.from(value.toString(16), 'hex') : 71 | value instanceof Slice ? value.loadBuffer(4) : value, 72 | 0, 73 | 32 74 | ) 75 | ).loadInt(32); 76 | 77 | const context = BigInt(val) ^ BigInt(networkGlobalId); 78 | 79 | const bitReader = beginCell().storeInt(context, 32).endCell().beginParse(); 80 | 81 | const isClientContext = bitReader.loadUint(1); 82 | if (isClientContext) { 83 | const workchain = bitReader.loadInt(8); 84 | const walletVersionRaw = bitReader.loadUint(8); 85 | const subwalletNumber = bitReader.loadUint(15); 86 | 87 | const walletVersion = Object.entries(walletV5R1VersionsSerialisation).find( 88 | ([_, value]) => value === walletVersionRaw 89 | )?.[0] as WalletIdV5R1ClientContext['walletVersion'] | undefined; 90 | 91 | if (walletVersion === undefined) { 92 | throw new Error( 93 | `Can't deserialize walletId: unknown wallet version ${walletVersionRaw}` 94 | ); 95 | } 96 | 97 | return { 98 | networkGlobalId, 99 | context: { 100 | walletVersion, 101 | workchain, 102 | subwalletNumber 103 | } 104 | } 105 | } else { 106 | const context = bitReader.loadUint(31); 107 | return { 108 | networkGlobalId, 109 | context 110 | } 111 | } 112 | } 113 | 114 | export function storeWalletIdV5R1(walletId: WalletIdV5R1) { 115 | return (builder: Builder) => { 116 | let context; 117 | if (isWalletIdV5R1ClientContext(walletId.context)) { 118 | context = beginCell() 119 | .storeUint(1, 1) 120 | .storeInt(walletId.context.workchain, 8) 121 | .storeUint(walletV5R1VersionsSerialisation[walletId.context.walletVersion], 8) 122 | .storeUint(walletId.context.subwalletNumber, 15) 123 | .endCell().beginParse().loadInt(32); 124 | } else { 125 | context = beginCell() 126 | .storeUint(0, 1) 127 | .storeUint(walletId.context, 31) 128 | .endCell().beginParse().loadInt(32); 129 | } 130 | 131 | return builder.storeInt(BigInt(walletId.networkGlobalId) ^ BigInt(context), 32); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": false, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | }, 72 | "include": ["src/**/*"] 73 | } 74 | --------------------------------------------------------------------------------