From b0c686252e84f572784844c88a81605a5b778fb2 Mon Sep 17 00:00:00 2001 From: Mohsen <56779182+mrtnetwork@users.noreply.github.com> Date: Fri, 13 Sep 2024 18:43:16 +0330 Subject: [PATCH] V0.1.0 - Breaking Changes: Updated APIs for NFT, Jetton, and versioned contracts. All related methods have been modified. - Added: Support for WalletV5R1. - Added: Support for MultiOwnerV2R1 contracts. - Added: Support for Stable Jetton contracts. --- .gitignore | 1 + CHANGELOG.md | 7 + README.md | 214 ++-- example/lib/examples/batch_mint_nfts.dart | 33 - example/lib/examples/burn_jetton.dart | 31 - example/lib/examples/change_nft_owner.dart | 25 - example/lib/examples/collection_less_nft.dart | 31 - ...jetton_with_on_chain_metadata_example.dart | 30 - .../lib/examples/create_nft_collection.dart | 45 - example/lib/examples/deploy_minter.dart | 19 - .../editable_contract_change_content.dart | 45 - .../jetton/minter/change_content.dart | 22 + .../examples/jetton/minter/change_owner.dart | 22 + example/lib/examples/jetton/minter/mint.dart | 31 + .../examples/jetton/stable_minter/deploy.dart | 18 + .../examples/jetton/stable_minter/mint.dart | 26 + .../jetton/stable_minter/set_status.dart | 25 + .../jetton/stable_minter/transfer.dart | 29 + example/lib/examples/mint.dart | 33 - example/lib/examples/mint_nft.dart | 29 - example/lib/examples/mnemonic.dart | 3 +- example/lib/examples/nft/batch_mint_nft.dart | 56 + example/lib/examples/nft/change_owner.dart | 24 + .../lib/examples/nft/collection_less_nft.dart | 18 + example/lib/examples/nft/mint.dart | 49 + example/lib/examples/nft/transfer_nft.dart | 27 + example/lib/examples/tranfer_nft.dart | 28 - example/lib/examples/transfer.dart | 34 - example/lib/examples/transfer_jetton.dart | 49 - .../versioned_wallet/highload/v3.dart | 30 + .../highload/v3_mutiple_destination.dart | 60 ++ .../versioned_wallet/multi_owner/r2.dart | 60 ++ .../versioned_wallet/test_wallet.dart | 284 +++++ .../lib/examples/versioned_wallet/v1/r1.dart | 25 + .../lib/examples/versioned_wallet/v1/r2.dart | 25 + .../lib/examples/versioned_wallet/v1/r3.dart | 23 + .../lib/examples/versioned_wallet/v2/r1.dart | 26 + .../lib/examples/versioned_wallet/v2/r2.dart | 34 + .../lib/examples/versioned_wallet/v3/r1.dart | 34 + .../lib/examples/versioned_wallet/v3/r2.dart | 29 + .../lib/examples/versioned_wallet/v4/v4.dart | 25 + .../lib/examples/versioned_wallet/v5/r1.dart | 67 ++ .../versioned_wallet/v5/r1_add_extention.dart | 13 + .../v5/r1_remove_extension.dart | 13 + example/macos/Runner/AppDelegate.swift | 2 +- example/pubspec.lock | 34 +- example/test/test_test.dart | 1 + example/test/widget_test.dart | 21 - lib/src/address/address/address.dart | 53 + lib/src/address/address/external_address.dart | 7 + lib/src/boc/bit/bit_builder.dart | 16 +- lib/src/boc/bit/bit_reader.dart | 41 + lib/src/boc/bit/bit_string.dart | 32 +- lib/src/boc/bit/builder.dart | 63 ++ lib/src/boc/cell/cell.dart | 105 +- lib/src/boc/cell/cell_type.dart | 21 + lib/src/boc/cell/slice.dart | 226 +++- lib/src/boc/exception/exception.dart | 8 + .../boc/serialization/models/level_mask.dart | 2 +- .../serialization/serialization.dart | 2 +- lib/src/boc/serialization/utils/utils.dart | 2 +- lib/src/boc/utils/utils.dart | 2 +- lib/src/contracts/contracts.dart | 6 +- lib/src/contracts/core/contract.dart | 24 - lib/src/contracts/core/core.dart | 5 + lib/src/contracts/core/core/chain.dart | 28 + lib/src/contracts/core/core/contract.dart | 26 + .../contracts/core/{ => core}/provider.dart | 31 +- lib/src/contracts/core/core/state.dart | 24 + .../contracts/core/core/transfer_params.dart | 10 + lib/src/contracts/exception/exception.dart | 24 +- lib/src/contracts/highload/contracts/v3.dart | 151 --- lib/src/contracts/highload/core/core.dart | 7 - lib/src/contracts/highload/highload.dart | 2 - .../highload/models/v3_account_params.dart | 9 - lib/src/contracts/highload/utils/utils.dart | 158 --- .../token/ft/constants/constant.dart | 2 + .../token/ft/constants/constant/minter.dart | 42 + .../token/ft/constants/constant/wallet.dart | 34 + .../token/ft/contract/contracts.dart | 4 + .../token/ft/contract/minter/minter.dart | 185 ++++ .../token/ft/contract/minter/stable.dart | 195 ++++ .../ft/contract/wallet/jetton_wallet.dart | 119 +++ .../contract/wallet/stable_jetton_wallet.dart | 107 ++ lib/src/contracts/token/ft/ft.dart | 5 +- .../token/ft/minter/constant/constant.dart | 14 - .../token/ft/minter/contract/contract.dart | 361 ------- lib/src/contracts/token/ft/minter/minter.dart | 3 - .../minter/models/jetton_data_response.dart | 16 - .../minter/models/minter_wallet_params.dart | 12 - .../token/ft/minter/utils/utils.dart | 40 - .../ft/types/models/stable_minter_data.dart | 55 + .../token/ft/types/operations/jetton.dart | 759 ++++++++++++++ .../ft/types/operations/stable_jetton.dart | 975 ++++++++++++++++++ .../token/ft/types/state/minter.dart | 98 ++ .../token/ft/types/state/stable_minter.dart | 99 ++ .../token/ft/types/state/stable_wallet.dart | 129 +++ .../token/ft/types/state/wallet.dart | 52 + lib/src/contracts/token/ft/types/types.dart | 7 + .../token/ft/wallet/constants/contants.dart | 18 - .../token/ft/wallet/contract/contract.dart | 310 ------ .../wallet/models/jetton_transfer_params.dart | 17 - .../token/ft/wallet/utils/utils.dart | 10 - lib/src/contracts/token/ft/wallet/wallet.dart | 2 - .../token/metadata/core/metadata.dart | 34 +- .../contracts/token/metadata/metadata.dart | 5 +- .../token/metadata/models/nft_collection.dart | 44 +- .../metadata/models/nft_item_metadata.dart | 28 +- .../metadata/models/nft_raw_metadata.dart | 5 + .../of_chain_stable_token_metadata.dart | 25 + .../{token_metadata.dart => off_chain.dart} | 8 +- .../{ft_token_metadata.dart => on_chain.dart} | 4 +- .../token/metadata/utils/metadata.dart | 35 +- .../token/nft/constant/constant.dart | 2 +- .../token/nft/contracts/contracts.dart | 6 +- .../token/nft/contracts/nft/collection.dart | 209 ++++ .../token/nft/contracts/nft/editable.dart | 99 ++ .../token/nft/contracts/nft/item.dart | 133 +++ .../nft_contracts/nft_collection.dart | 278 ----- .../nft_collection_editable.dart | 102 -- .../nft/contracts/nft_contracts/nft_item.dart | 197 ---- .../contracts/token/nft/models/models.dart | 8 - .../nft_collection_editable_params.dart | 47 - .../nft_editable_update_content.dart | 23 - .../models/nft_models/nft_item_params.dart | 34 - .../models/nft_models/nft_mint_params.dart | 95 -- .../nft_models/transfer_nft_params.dart | 45 - lib/src/contracts/token/nft/nft.dart | 3 +- .../models/collection_data.dart} | 1 - .../models/item_data.dart} | 1 - .../token/nft/types/models/mint_params.dart | 72 ++ .../models}/royalty_params.dart | 23 +- .../nft/types/operations/operations.dart | 536 ++++++++++ .../token/nft/types/state/collection.dart | 117 +++ .../contracts/token/nft/types/state/item.dart | 102 ++ lib/src/contracts/token/nft/types/types.dart | 9 + lib/src/contracts/token/nft/utils/utils.dart | 37 - lib/src/contracts/utils/parser.dart | 27 + .../contracts/utils/serialization_utils.dart | 165 +++ .../contracts/utils/transaction_utils.dart | 110 +- lib/src/contracts/wallet/contracts/v1r1.dart | 51 - lib/src/contracts/wallet/contracts/v1r2.dart | 54 - lib/src/contracts/wallet/contracts/v1r3.dart | 50 - lib/src/contracts/wallet/contracts/v2r1.dart | 48 - lib/src/contracts/wallet/contracts/v2r2.dart | 48 - lib/src/contracts/wallet/contracts/v3r1.dart | 63 -- lib/src/contracts/wallet/contracts/v3r2.dart | 64 -- lib/src/contracts/wallet/contracts/v4.dart | 66 -- .../wallet/core/versioned_wallet.dart | 43 - .../versioned_wallet_account_params.dart | 7 - .../wallet/provider/provider_impl.dart | 100 -- .../wallet/transaction/transaction_impl.dart | 89 -- lib/src/contracts/wallet/utils/utils.dart | 111 -- lib/src/contracts/wallet/wallet.dart | 10 - .../wallet_contracts/constant/constant.dart | 3 + .../constant/constants/highload.dart} | 7 +- .../constant/constants/mutli_owner.dart | 18 + .../constant/constants/versioned.dart} | 3 +- .../wallet_contracts/contracts/contracts.dart | 12 + .../contracts/highload/v3.dart | 213 ++++ .../contracts/multi_owner/multisig.dart | 347 +++++++ .../contracts/multi_owner/order.dart | 174 ++++ .../contracts/versioned/v1r1.dart | 57 + .../contracts/versioned/v1r2.dart | 60 ++ .../contracts/versioned/v1r3.dart | 56 + .../contracts/versioned/v2r1.dart | 54 + .../contracts/versioned/v2r2.dart | 54 + .../contracts/versioned/v3r1.dart | 64 ++ .../contracts/versioned/v3r2.dart | 65 ++ .../contracts/versioned/v4.dart | 67 ++ .../contracts/versioned/v5r1.dart | 213 ++++ .../contracts/wallet_contracts/core/core.dart | 3 + .../wallet_contracts/core/core/core.dart | 26 + .../core/highload/highload_wallet.dart | 15 + .../core/versioned/versioned_wallet.dart} | 37 +- .../provider/highload.dart} | 19 +- .../provider}/models/account_state.dart | 13 +- .../provider}/models/run_method_response.dart | 4 +- .../wallet_contracts/provider/versioned.dart | 115 +++ .../types/models/v5_client_id.dart | 79 ++ .../types/state/highload.dart | 38 + .../types/state/multisig.dart | 90 ++ .../wallet_contracts/types/state/order.dart | 86 ++ .../types/state/versioned.dart | 97 ++ .../types/transfer_params/highload.dart | 16 + .../types/transfer_params/multi_owner.dart | 19 + .../types/transfer_params/versioned.dart | 11 + .../types/transfer_params/versioned_v5.dart | 42 + .../wallet_contracts/types/types.dart | 9 + .../utils/highload_query_id.dart} | 4 +- .../wallet_contracts/utils/multi_owner.dart | 22 + .../wallet_contracts/utils/versioned.dart | 131 +++ .../wallet_contracts/wallet_contracts.dart | 9 + lib/src/dict/codecs/codecs.dart | 103 +- lib/src/dict/dictionary/dictionary.dart | 37 +- lib/src/dict/dictionary/key.dart | 23 + lib/src/dict/dictionary/value.dart | 36 +- lib/src/dict/serialization/serialization.dart | 17 +- lib/src/dict/utils/utils.dart | 5 +- lib/src/helper/ton_helper.dart | 32 +- .../models/models/common_message_info.dart | 6 +- .../models/common_message_info_relaxed.dart | 15 +- .../models/models/currency_collection.dart | 4 +- .../models/master_chain_state_extra.dart | 2 +- lib/src/models/models/message.dart | 4 +- lib/src/models/models/message_relaxed.dart | 4 +- lib/src/models/models/out_action.dart | 453 +++++++- lib/src/models/models/send_mode.dart | 28 +- lib/src/models/models/shard_account.dart | 6 +- lib/src/models/models/shard_accounts.dart | 2 +- .../models/models/shard_state_unsplit.dart | 9 +- lib/src/models/models/state_init.dart | 18 +- lib/src/models/models/transaction.dart | 6 +- .../models/transaction_credit_phase.dart | 2 +- .../models/transaction_description.dart | 16 +- .../models/transaction_storage_phase.dart | 2 +- .../provider/core/ton_center_v3_methods.dart | 2 + .../ton_center_v3/account/account.dart | 14 + .../methods/ton_center_v3/models.dart | 1 + .../models/response/account_address.dart | 1 - .../models/response/account_status.dart | 2 +- .../models/response/gas_limit_prices.dart | 1 - .../models/response/msg_forward_prices.dart | 1 - lib/src/provider/models/response/trace.dart | 1 - lib/src/tuple/exception/exception.dart | 5 + lib/src/tuple/tuple/builder.dart | 28 + lib/src/tuple/tuple/tuple.dart | 9 + lib/src/tuple/tuple/tuple_reader.dart | 49 + lib/src/utils/utils.dart | 31 +- lib/src/utils/utils/base64.dart | 26 + lib/src/utils/{ => utils}/crypto.dart | 0 lib/src/utils/{ => utils}/extentions.dart | 4 +- lib/src/utils/{ => utils}/fee.dart | 0 lib/src/utils/{ => utils}/math.dart | 0 lib/ton_dart.dart | 3 +- test/tests/contract_v5/auction_test.dart | 145 +++ .../contract_v5/wallet_context_test.dart | 102 ++ test/tests/dict/dict_test.dart | 13 +- test/tests/metadata/metadata_test.dart | 4 +- .../tests/models/jetton_minter_data_test.dart | 56 + test/tests/models/jetton_wallet_test.dart | 18 + test/tests/models/nft_data_test.dart | 74 ++ test/tests/operations/jetton_test.dart | 83 ++ test/tests/operations/nft_test.dart | 93 ++ test/tests/operations/stable_token_test.dart | 63 ++ 245 files changed, 10357 insertions(+), 3692 deletions(-) delete mode 100644 example/lib/examples/batch_mint_nfts.dart delete mode 100644 example/lib/examples/burn_jetton.dart delete mode 100644 example/lib/examples/change_nft_owner.dart delete mode 100644 example/lib/examples/collection_less_nft.dart delete mode 100644 example/lib/examples/create_jetton_with_on_chain_metadata_example.dart delete mode 100644 example/lib/examples/create_nft_collection.dart delete mode 100644 example/lib/examples/deploy_minter.dart delete mode 100644 example/lib/examples/editable_contract_change_content.dart create mode 100644 example/lib/examples/jetton/minter/change_content.dart create mode 100644 example/lib/examples/jetton/minter/change_owner.dart create mode 100644 example/lib/examples/jetton/minter/mint.dart create mode 100644 example/lib/examples/jetton/stable_minter/deploy.dart create mode 100644 example/lib/examples/jetton/stable_minter/mint.dart create mode 100644 example/lib/examples/jetton/stable_minter/set_status.dart create mode 100644 example/lib/examples/jetton/stable_minter/transfer.dart delete mode 100644 example/lib/examples/mint.dart delete mode 100644 example/lib/examples/mint_nft.dart create mode 100644 example/lib/examples/nft/batch_mint_nft.dart create mode 100644 example/lib/examples/nft/change_owner.dart create mode 100644 example/lib/examples/nft/collection_less_nft.dart create mode 100644 example/lib/examples/nft/mint.dart create mode 100644 example/lib/examples/nft/transfer_nft.dart delete mode 100644 example/lib/examples/tranfer_nft.dart delete mode 100644 example/lib/examples/transfer.dart delete mode 100644 example/lib/examples/transfer_jetton.dart create mode 100644 example/lib/examples/versioned_wallet/highload/v3.dart create mode 100644 example/lib/examples/versioned_wallet/highload/v3_mutiple_destination.dart create mode 100644 example/lib/examples/versioned_wallet/multi_owner/r2.dart create mode 100644 example/lib/examples/versioned_wallet/test_wallet.dart create mode 100644 example/lib/examples/versioned_wallet/v1/r1.dart create mode 100644 example/lib/examples/versioned_wallet/v1/r2.dart create mode 100644 example/lib/examples/versioned_wallet/v1/r3.dart create mode 100644 example/lib/examples/versioned_wallet/v2/r1.dart create mode 100644 example/lib/examples/versioned_wallet/v2/r2.dart create mode 100644 example/lib/examples/versioned_wallet/v3/r1.dart create mode 100644 example/lib/examples/versioned_wallet/v3/r2.dart create mode 100644 example/lib/examples/versioned_wallet/v4/v4.dart create mode 100644 example/lib/examples/versioned_wallet/v5/r1.dart create mode 100644 example/lib/examples/versioned_wallet/v5/r1_add_extention.dart create mode 100644 example/lib/examples/versioned_wallet/v5/r1_remove_extension.dart create mode 100644 example/test/test_test.dart delete mode 100644 example/test/widget_test.dart delete mode 100644 lib/src/contracts/core/contract.dart create mode 100644 lib/src/contracts/core/core.dart create mode 100644 lib/src/contracts/core/core/chain.dart create mode 100644 lib/src/contracts/core/core/contract.dart rename lib/src/contracts/core/{ => core}/provider.dart (76%) create mode 100644 lib/src/contracts/core/core/state.dart create mode 100644 lib/src/contracts/core/core/transfer_params.dart delete mode 100644 lib/src/contracts/highload/contracts/v3.dart delete mode 100644 lib/src/contracts/highload/core/core.dart delete mode 100644 lib/src/contracts/highload/highload.dart delete mode 100644 lib/src/contracts/highload/models/v3_account_params.dart delete mode 100644 lib/src/contracts/highload/utils/utils.dart create mode 100644 lib/src/contracts/token/ft/constants/constant.dart create mode 100644 lib/src/contracts/token/ft/constants/constant/minter.dart create mode 100644 lib/src/contracts/token/ft/constants/constant/wallet.dart create mode 100644 lib/src/contracts/token/ft/contract/contracts.dart create mode 100644 lib/src/contracts/token/ft/contract/minter/minter.dart create mode 100644 lib/src/contracts/token/ft/contract/minter/stable.dart create mode 100644 lib/src/contracts/token/ft/contract/wallet/jetton_wallet.dart create mode 100644 lib/src/contracts/token/ft/contract/wallet/stable_jetton_wallet.dart delete mode 100644 lib/src/contracts/token/ft/minter/constant/constant.dart delete mode 100644 lib/src/contracts/token/ft/minter/contract/contract.dart delete mode 100644 lib/src/contracts/token/ft/minter/minter.dart delete mode 100644 lib/src/contracts/token/ft/minter/models/jetton_data_response.dart delete mode 100644 lib/src/contracts/token/ft/minter/models/minter_wallet_params.dart delete mode 100644 lib/src/contracts/token/ft/minter/utils/utils.dart create mode 100644 lib/src/contracts/token/ft/types/models/stable_minter_data.dart create mode 100644 lib/src/contracts/token/ft/types/operations/jetton.dart create mode 100644 lib/src/contracts/token/ft/types/operations/stable_jetton.dart create mode 100644 lib/src/contracts/token/ft/types/state/minter.dart create mode 100644 lib/src/contracts/token/ft/types/state/stable_minter.dart create mode 100644 lib/src/contracts/token/ft/types/state/stable_wallet.dart create mode 100644 lib/src/contracts/token/ft/types/state/wallet.dart create mode 100644 lib/src/contracts/token/ft/types/types.dart delete mode 100644 lib/src/contracts/token/ft/wallet/constants/contants.dart delete mode 100644 lib/src/contracts/token/ft/wallet/contract/contract.dart delete mode 100644 lib/src/contracts/token/ft/wallet/models/jetton_transfer_params.dart delete mode 100644 lib/src/contracts/token/ft/wallet/utils/utils.dart delete mode 100644 lib/src/contracts/token/ft/wallet/wallet.dart create mode 100644 lib/src/contracts/token/metadata/models/of_chain_stable_token_metadata.dart rename lib/src/contracts/token/metadata/models/{token_metadata.dart => off_chain.dart} (84%) rename lib/src/contracts/token/metadata/models/{ft_token_metadata.dart => on_chain.dart} (98%) create mode 100644 lib/src/contracts/token/nft/contracts/nft/collection.dart create mode 100644 lib/src/contracts/token/nft/contracts/nft/editable.dart create mode 100644 lib/src/contracts/token/nft/contracts/nft/item.dart delete mode 100644 lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection.dart delete mode 100644 lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection_editable.dart delete mode 100644 lib/src/contracts/token/nft/contracts/nft_contracts/nft_item.dart delete mode 100644 lib/src/contracts/token/nft/models/models.dart delete mode 100644 lib/src/contracts/token/nft/models/nft_models/nft_collection_editable_params.dart delete mode 100644 lib/src/contracts/token/nft/models/nft_models/nft_editable_update_content.dart delete mode 100644 lib/src/contracts/token/nft/models/nft_models/nft_item_params.dart delete mode 100644 lib/src/contracts/token/nft/models/nft_models/nft_mint_params.dart delete mode 100644 lib/src/contracts/token/nft/models/nft_models/transfer_nft_params.dart rename lib/src/contracts/token/nft/{models/nft_models/nft_collection_data.dart => types/models/collection_data.dart} (88%) rename lib/src/contracts/token/nft/{models/nft_models/nft_item_data.dart => types/models/item_data.dart} (95%) create mode 100644 lib/src/contracts/token/nft/types/models/mint_params.dart rename lib/src/contracts/token/nft/{models/nft_models => types/models}/royalty_params.dart (51%) create mode 100644 lib/src/contracts/token/nft/types/operations/operations.dart create mode 100644 lib/src/contracts/token/nft/types/state/collection.dart create mode 100644 lib/src/contracts/token/nft/types/state/item.dart create mode 100644 lib/src/contracts/token/nft/types/types.dart delete mode 100644 lib/src/contracts/token/nft/utils/utils.dart create mode 100644 lib/src/contracts/utils/parser.dart create mode 100644 lib/src/contracts/utils/serialization_utils.dart delete mode 100644 lib/src/contracts/wallet/contracts/v1r1.dart delete mode 100644 lib/src/contracts/wallet/contracts/v1r2.dart delete mode 100644 lib/src/contracts/wallet/contracts/v1r3.dart delete mode 100644 lib/src/contracts/wallet/contracts/v2r1.dart delete mode 100644 lib/src/contracts/wallet/contracts/v2r2.dart delete mode 100644 lib/src/contracts/wallet/contracts/v3r1.dart delete mode 100644 lib/src/contracts/wallet/contracts/v3r2.dart delete mode 100644 lib/src/contracts/wallet/contracts/v4.dart delete mode 100644 lib/src/contracts/wallet/core/versioned_wallet.dart delete mode 100644 lib/src/contracts/wallet/models/versioned_wallet_account_params.dart delete mode 100644 lib/src/contracts/wallet/provider/provider_impl.dart delete mode 100644 lib/src/contracts/wallet/transaction/transaction_impl.dart delete mode 100644 lib/src/contracts/wallet/utils/utils.dart delete mode 100644 lib/src/contracts/wallet/wallet.dart create mode 100644 lib/src/contracts/wallet_contracts/constant/constant.dart rename lib/src/contracts/{highload/constant/constant.dart => wallet_contracts/constant/constants/highload.dart} (85%) create mode 100644 lib/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart rename lib/src/contracts/{wallet/constant/constant.dart => wallet_contracts/constant/constants/versioned.dart} (72%) create mode 100644 lib/src/contracts/wallet_contracts/contracts/contracts.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/highload/v3.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/multi_owner/multisig.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/multi_owner/order.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v1r1.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v1r2.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v1r3.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v2r1.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v2r2.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v3r1.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v3r2.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v4.dart create mode 100644 lib/src/contracts/wallet_contracts/contracts/versioned/v5r1.dart create mode 100644 lib/src/contracts/wallet_contracts/core/core.dart create mode 100644 lib/src/contracts/wallet_contracts/core/core/core.dart create mode 100644 lib/src/contracts/wallet_contracts/core/highload/highload_wallet.dart rename lib/src/contracts/{wallet/core/version.dart => wallet_contracts/core/versioned/versioned_wallet.dart} (61%) rename lib/src/contracts/{highload/provider/v3.dart => wallet_contracts/provider/highload.dart} (67%) rename lib/src/contracts/{ => wallet_contracts/provider}/models/account_state.dart (75%) rename lib/src/contracts/{ => wallet_contracts/provider}/models/run_method_response.dart (77%) create mode 100644 lib/src/contracts/wallet_contracts/provider/versioned.dart create mode 100644 lib/src/contracts/wallet_contracts/types/models/v5_client_id.dart create mode 100644 lib/src/contracts/wallet_contracts/types/state/highload.dart create mode 100644 lib/src/contracts/wallet_contracts/types/state/multisig.dart create mode 100644 lib/src/contracts/wallet_contracts/types/state/order.dart create mode 100644 lib/src/contracts/wallet_contracts/types/state/versioned.dart create mode 100644 lib/src/contracts/wallet_contracts/types/transfer_params/highload.dart create mode 100644 lib/src/contracts/wallet_contracts/types/transfer_params/multi_owner.dart create mode 100644 lib/src/contracts/wallet_contracts/types/transfer_params/versioned.dart create mode 100644 lib/src/contracts/wallet_contracts/types/transfer_params/versioned_v5.dart create mode 100644 lib/src/contracts/wallet_contracts/types/types.dart rename lib/src/contracts/{highload/utils/query_id.dart => wallet_contracts/utils/highload_query_id.dart} (95%) create mode 100644 lib/src/contracts/wallet_contracts/utils/multi_owner.dart create mode 100644 lib/src/contracts/wallet_contracts/utils/versioned.dart create mode 100644 lib/src/contracts/wallet_contracts/wallet_contracts.dart create mode 100644 lib/src/provider/methods/ton_center_v3/account/account.dart create mode 100644 lib/src/utils/utils/base64.dart rename lib/src/utils/{ => utils}/crypto.dart (100%) rename lib/src/utils/{ => utils}/extentions.dart (88%) rename lib/src/utils/{ => utils}/fee.dart (100%) rename lib/src/utils/{ => utils}/math.dart (100%) create mode 100644 test/tests/contract_v5/auction_test.dart create mode 100644 test/tests/contract_v5/wallet_context_test.dart create mode 100644 test/tests/models/jetton_minter_data_test.dart create mode 100644 test/tests/models/jetton_wallet_test.dart create mode 100644 test/tests/models/nft_data_test.dart create mode 100644 test/tests/operations/jetton_test.dart create mode 100644 test/tests/operations/nft_test.dart create mode 100644 test/tests/operations/stable_token_test.dart diff --git a/.gitignore b/.gitignore index 69b2a38..90cc600 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ migrate_working_dir/ .dart_tool/ build/ *.zip +example/a.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 487ff25..001ac07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.0 + +- Breaking Changes: Updated APIs for NFT, Jetton, and versioned contracts. All related methods have been modified. +- Added: Support for WalletV5R1. +- Added: Support for MultiOwnerV2R1 contracts. +- Added: Support for Stable Jetton contracts. + ## 0.0.4 - Update dependencies. diff --git a/README.md b/README.md index cbaa4fd..8c6369d 100644 --- a/README.md +++ b/README.md @@ -20,50 +20,77 @@ To leverage Ton Dart's capabilities optimally, familiarity with TonApi and TonCe - TON BOC serialization is a method for encoding and decoding data structures into a binary format within the Telegram Open Network. - **Contract** - - Provides support for Basic wallets 1 through 4. - - Offers support for deploying tokens and transferring jettos with Minter and Jetton wallets. - - Highload Wallet v3 + - Provides support for Basic wallets 1 through 5. + - Offers support for deploying tokens and transferring Jettons with Minter and Jetton wallets (Jetton, StableJetton). + - Highload Wallet v3. + - Support for MultiOwner contracts (MultiOwner, Order). + - Support for NFTs contracts (NFTCollection, EditableCollection, NFTItem). ### Examples - Check [examples](https://github.com/mrtnetwork/ton_dart/tree/main/example/lib/examples) folder +- #### Transfer TON - transfer ```dart - - /// Initialize TonProvider with HTTPProvider for Testnet - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC", - )); - - /// Define private key - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 39)); - - /// Create WalletV4 instance - final wallet = WalletV4( - workChain: -1, - publicKey: privateKey.toPublicKey().toBytes(), - ); - - /// Define destination address - final destination = - TonAddress("Ef_GHcGwnw-bASoxTGQRMNwMQ6w9iCQnTqrv1REDfJ5fCYD2"); - - /// Construct transfer message and send to the network - await wallet.sendTransfer( - messages: [ - wallet.createMessageInfo( - amount: TonHelper.toNano("0.1"), - destination: destination, - ) - ], - privateKey: privateKey, - rpc: rpc, - ); + /// TestWallet is a utility for quickly generating and testing wallet contracts from a specified version. + /// The code for this is available in the example folder. + final TestWallet wallet = TestWallet(version: WalletVersion.v5R1); + + final destination = TestWallet(version: WalletVersion.v1R1); + final destination2 = TestWallet(version: WalletVersion.v1R2); + final destination3 = TestWallet(version: WalletVersion.v1R3); + final destination4 = TestWallet(version: WalletVersion.v2R1); + final destination5 = TestWallet(version: WalletVersion.v2R2); + final destination6 = TestWallet(version: WalletVersion.v3R1); + final destination7 = TestWallet(version: WalletVersion.v3R2); + final destination8 = TestWallet(version: WalletVersion.v4); + final destination9 = TestWallet(version: WalletVersion.v5R1, index: 1); + + await wallet.wallet.sendTransfer( + params: + VersionedV5TransferParams.external(signer: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination3.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination4.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination5.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination6.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination7.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination8.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination9.address, + amount: TonHelper.toNano("0.01"))), + ]), + rpc: wallet.rpc); ``` @@ -71,89 +98,46 @@ To leverage Ton Dart's capabilities optimally, familiarity with TonApi and TonCe - Deploy Jetton minter and mint token ```dart - - /// Define owner wallet with WalletV4 - final ownerWallet = WalletV4( - workChain: -1, - publicKey: privateKey.toPublicKey().toBytes(), - ); - - /// Create JettonMinter with owner and content - final minter = JettonMinter.create( - owner: ownerWallet, - content: "https://github.com/mrtnetwork", - ); - - /// Deploy JettonMinter contract (TOKEN) - await minter.deploy( - ownerPrivateKey: privateKey, - rpc: rpc, - amount: TonHelper.toNano("0.5"), - ); - - await Future.delayed(const Duration(seconds: 5)); - - /// Define the address to which tokens will be minted - final addressToMint = - TonAddress("Ef__48F3wya3lEgIHRtpK8jPzYpQCIrfwZfFSEFmjaPQfC56"); - - /// Define amounts - final amount = TonHelper.toNano("0.5"); - final forwardAmount = TonHelper.toNano("0.3"); - final totalAmount = TonHelper.toNano("0.4"); - final jettonAmountForMint = BigInt.parse("1${"0" * 15}"); - - /// Mint tokens - await minter.mint( - privateKey: privateKey, - rpc: rpc, - jettonAmout: jettonAmountForMint, - forwardTonAmount: forwardAmount, - totalTonAmount: totalAmount, - amount: amount + totalAmount, - to: addressToMint, - ); + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + + final metadata = JettonOnChainMetadata.snakeFormat( + name: "MRT NETWORK", + image: "https://avatars.githubusercontent.com/u/56779182?s=96&v=4", + symbol: "MRT", + decimals: 9, + description: "https://github.com/mrtnetwork/ton_dart"); + + final jetton = JettonMinter.create( + owner: wallet.wallet, + state: MinterWalletState( + owner: wallet.address, + chain: TonChain.testnet, + metadata: metadata, + )); + + await jetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.9"), + operation: JettonMinterMint( + totalTonAmount: TonHelper.toNano("0.5"), + to: wallet.address, + transfer: JettonMinterInternalTransfer( + jettonAmount: TonHelper.toNano("100000"), + forwardTonAmount: TonHelper.toNano("0.01")), + jettonAmount: TonHelper.toNano("100000"))); ``` -- Transfer jetton - -```dart - /// Create JettonMinter to query jetton wallet address - final minter = JettonMinter( - owner: ownerWallet, - address: TonAddress("Ef8ns7A4eSwJC1mJf72JDE9byKY9n_xxb1hhloly9heQi_rY")); - - /// Get the address of the Jetton Wallet - final jettonWalletAddress = await minter.getWalletAddress( - rpc: rpc, - owner: ownerWallet.address, - ); - - /// Create JettonWallet instance from the address - final jettonWallet = JettonWallet.fromAddress( - jettonWalletAddress: jettonWalletAddress, - owner: ownerWallet, - ); - - /// Get the balance of the Jetton Wallet - final balance = await jettonWallet.getBalance(rpc); - - /// Define amounts - final forwardTonAmount = TonHelper.toNano("0.1"); - final transferAmount = BigInt.from(1000000000); - final BigInt amount = TonHelper.toNano("0.3"); - - /// Transfer tokens from Jetton Wallet - await jettonWallet.transfer( - privateKey: privateKey, - rpc: rpc, - destination: destination, - forwardTonAmount: forwardTonAmount, - jettonAmount: transferAmount, - amount: amount + forwardTonAmount, - ); -``` +- Other Example: + [JettonMinter]() + [JettonWallet]() + [StableJettonMinter]() + [StableJettonWallet]() + [NFTs]() + [MultiOwner]() + [Highload]() + [VersionedWallets]() #### JSON-RPC @@ -244,7 +228,7 @@ class HTTPProvider implements TonServiceProvider { final publicKey = privateKey.toPublicKey(); /// Create WalletV4 instance with derived public key - final wallet = WalletV4(workChain: -1, publicKey: publicKey.toBytes()); + final wallet = WalletV4(chain: TonChain.testnet, , publicKey: publicKey.toBytes()); /// Get address from wallet final address = wallet.address; diff --git a/example/lib/examples/batch_mint_nfts.dart b/example/lib/examples/batch_mint_nfts.dart deleted file mode 100644 index 7011e70..0000000 --- a/example/lib/examples/batch_mint_nfts.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final gnWallet = getTestWallet(index: 312); - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - - final nftCollection = NFTCollectionContract( - address: TonAddress("Ef9eHhGCu4PS8M7MIoTcIbFFlja5j5u__jwW3BHLUEqlk2Wx"), - ownerWallet: wallet); - - await nftCollection.batchMintNfts( - privateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("1"), - params: BatchNFTsMintParams(List.generate( - 5, - (index) => NFTMintParams( - ownerAddress: wallet.address, - //// you should select the each file in your base collection or set full path if you dont set collection base in your nft collection - metadata: const NFTItemMetadata("?filename=ipf.json"), - initAmount: TonHelper.toNano("0.1"), - itemIndex: BigInt.from(10) + BigInt.from(index)), - ))); - - /// https://testnet.explorer.tonnft.tools/collection/Ef9eHhGCu4PS8M7MIoTcIbFFlja5j5u__jwW3BHLUEqlk2Wx - /// https://testnet.tonscan.org/tx/by-msg-hash/hKcVkiPxKFFnURHhWKCJn9bbRjGYH/K0f8Op2715B2k= -} diff --git a/example/lib/examples/burn_jetton.dart b/example/lib/examples/burn_jetton.dart deleted file mode 100644 index 71d3948..0000000 --- a/example/lib/examples/burn_jetton.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:ton_dart/ton_dart.dart'; -import 'http.dart'; -import 'transfer.dart'; - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final walletDetails = getTestWallet(index: 12); - final privateKey = walletDetails.item2; - final WalletV4 wallet = walletDetails.item1; - - /// owner its not important in this case. we just want to get jetton wallet address - final minter = JettonMinter( - owner: wallet, - address: TonAddress("Ef9PKHV5y7ynx7ITXZqowOkQeY4WynREXDoIn6XomBfIePqS")); - - final jettonWalletAddress = - await minter.getWalletAddress(rpc: rpc, owner: wallet.address); - final jettonWallet = JettonWallet.fromAddress( - jettonWalletAddress: jettonWalletAddress, owner: wallet); - final burnAmount = BigInt.from(1000000000000); - final BigInt amount = TonHelper.toNano("0.2"); - await jettonWallet.burn( - privateKey: privateKey, - rpc: rpc, - amount: amount, - burnJettonAmount: burnAmount); - - /// https://testnet.tonscan.org/tx/by-msg-hash/LGoiWetC0IH_cCCft6QtnaqmputeqvprNyL9pkL6v2I= -} diff --git a/example/lib/examples/change_nft_owner.dart b/example/lib/examples/change_nft_owner.dart deleted file mode 100644 index f515e12..0000000 --- a/example/lib/examples/change_nft_owner.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC", - api: TonApiType.tonCenter)); - - final gnWallet = getTestWallet(index: 1001); - final newOwner = getTestWallet(index: 417).item1.address; - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - final nftCollection = NFTCollectionEditableContract( - address: TonAddress("Ef9J9PRMOwvli37T5CKBrL5wTk2AV-6A_YVL34wRaIHx2QYE"), - ownerWallet: wallet); - - await nftCollection.changeOwner( - privateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.1"), - newOwner: newOwner, - bounce: false); -} diff --git a/example/lib/examples/collection_less_nft.dart b/example/lib/examples/collection_less_nft.dart deleted file mode 100644 index 713082c..0000000 --- a/example/lib/examples/collection_less_nft.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC", - api: TonApiType.tonCenter)); - - final gnWallet = getTestWallet(index: 312); - - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - - final nftAddr = NFTItemContract.create( - ownerWallet: wallet, - params: NFTItemParams( - index: BigInt.zero, - ownerAddress: wallet.address, - content: const NFTItemMetadata( - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json"))); - - await nftAddr.deploy( - ownerPrivateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.5")); - - /// https://testnet.explorer.tonnft.tools/nft/Ef8e-ohQoQjyil_Zkf_NgwpoxtazjH8mqadZV5V3uRB0bg4u - /// https://testnet.tonscan.org/tx/by-msg-hash/573JC0C/q3R0DVjFwjEqJiwQ7t03CKDsvf7d2sdTVM0= -} diff --git a/example/lib/examples/create_jetton_with_on_chain_metadata_example.dart b/example/lib/examples/create_jetton_with_on_chain_metadata_example.dart deleted file mode 100644 index 9529a16..0000000 --- a/example/lib/examples/create_jetton_with_on_chain_metadata_example.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 132)); - final ownerWallet = WalletV4.create( - workChain: -1, publicKey: privateKey.toPublicKey().toBytes()); - final balance = await ownerWallet.getBalance(rpc); - if (balance == BigInt.zero) { - return; - } - final minter = JettonMinter.create( - owner: ownerWallet, - metadata: JettonOnChainMetadata.snakeFormat( - name: "MRT NETWORK", - image: "https://avatars.githubusercontent.com/u/56779182?s=96&v=4", - symbol: "MRT", - decimals: 9, - description: "https://github.com/mrtnetwork/ton_dart", - ), - ); - await minter.deploy( - ownerPrivateKey: privateKey, rpc: rpc, amount: TonHelper.toNano("0.5")); - - /// https://testnet.tonscan.org/jetton/Ef_S5e9R5RZbWgf7Ob5kfU9JadIYDd-Eb17rYkjG691DmHTl - /// https://testnet.tonscan.org/tx/fN3-ih_54AKBu_G4SNZEkz5n_JREanECRT9KEQ5SiBs= -} diff --git a/example/lib/examples/create_nft_collection.dart b/example/lib/examples/create_nft_collection.dart deleted file mode 100644 index 2d09a17..0000000 --- a/example/lib/examples/create_nft_collection.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final gnWallet = getTestWallet(index: 312); - final royaltyAddress = getTestWallet(index: 314).item1.address; - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - - final royaltyParams = RoyaltyParams( - - /// 1% - royaltyFactor: (0.01 * 1000).ceil(), - royaltyBase: 1000, - address: royaltyAddress); - - final nftCollection = NFTCollectionContract.create( - ownerWallet: wallet, - royaltyParams: royaltyParams, - nextItemIndex: BigInt.from(100), - content: const NFTCollectionMetadata( - - /// collection metadata - collectionMetadataUri: - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json", - - /// collection base. use your nfts collection folders or set null for empty string - /// when you want to mint nfts if its not empty you must juast add your nft file name - /// you can see the example in mint example file - collectionBase: - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN")); - - await nftCollection.deploy( - ownerPrivateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.1"), - ); - - /// https://testnet.explorer.tonnft.tools/collection/Ef9eHhGCu4PS8M7MIoTcIbFFlja5j5u__jwW3BHLUEqlk2Wx - /// https://testnet.tonscan.org/tx/by-msg-hash/KWMjah7g4afXIikoyujO/qK8/vYfWT5cpr1+fpLjm48= -} diff --git a/example/lib/examples/deploy_minter.dart b/example/lib/examples/deploy_minter.dart deleted file mode 100644 index ef061bc..0000000 --- a/example/lib/examples/deploy_minter.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:ton_dart/ton_dart.dart'; -import 'http.dart'; - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 56)); - final ownerWallet = WalletV4.create( - workChain: -1, publicKey: privateKey.toPublicKey().toBytes()); - - final minter = JettonMinter.create( - owner: ownerWallet, - metadata: const JettonOffChainMetadata("https://github.com/mrtnetwork")); - await minter.deploy( - ownerPrivateKey: privateKey, rpc: rpc, amount: TonHelper.toNano("0.5")); - - /// https://testnet.tonscan.org/tx/by-msg-hash/nVALmJB2NmwVOXZu1v2S3e9GtRKjATMEAtjCaIf3go4= -} diff --git a/example/lib/examples/editable_contract_change_content.dart b/example/lib/examples/editable_contract_change_content.dart deleted file mode 100644 index 6d73002..0000000 --- a/example/lib/examples/editable_contract_change_content.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC", - api: TonApiType.tonCenter)); - final gnWallet = getTestWallet(index: 1001); - final royaltyAddress = getTestWallet(index: 314).item1.address; - - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - - final nftCollection = NFTCollectionEditableContract( - ownerWallet: wallet, - address: TonAddress("Ef9J9PRMOwvli37T5CKBrL5wTk2AV-6A_YVL34wRaIHx2QYE")); - - await nftCollection.changeContent( - privateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.1"), - newContent: UpdateEditableNFTContent( - content: const NFTCollectionMetadata( - - /// collection metadata - collectionMetadataUri: - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json", - - /// collection base. use your nfts collection folders or set null for empty string - /// when you want to mint nfts if its not empty you must juast add your nft file name - /// you can see the example in mint example file - collectionBase: - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN"), - royaltyParams: RoyaltyParams( - - /// change to 10% - royaltyFactor: (0.1 * 1000).ceil(), - royaltyBase: 1000, - address: royaltyAddress))); - - /// https://testnet.explorer.tonnft.tools/collection/Ef9J9PRMOwvli37T5CKBrL5wTk2AV-6A_YVL34wRaIHx2QYE - /// https://testnet.tonscan.org/tx/by-msg-hash/ts9kWXmNIadDOkTTrBdSxvE70BOu6FxZvNBsYETMgas= -} diff --git a/example/lib/examples/jetton/minter/change_content.dart b/example/lib/examples/jetton/minter/change_content.dart new file mode 100644 index 0000000..47439e8 --- /dev/null +++ b/example/lib/examples/jetton/minter/change_content.dart @@ -0,0 +1,22 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + + final metadata = JettonOnChainMetadata.snakeFormat( + name: "MRT JETTON", + image: "https://avatars.githubusercontent.com/u/56779182?s=96&v=4", + symbol: "MRT", + decimals: 12, + description: "https://github.com/mrtnetwork/ton_dart"); + final jetton = await JettonMinter.fromAddress( + owner: wallet.wallet, + address: TonAddress("Ef9sa8u1xuh6V5-ueNUEEiOYIjxxAQlqRAZ__7GGc_yJ5Lqf"), + rpc: wallet.rpc); + await jetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.2"), + operation: JettonMinterChangeContent(content: metadata.toContent())); +} diff --git a/example/lib/examples/jetton/minter/change_owner.dart b/example/lib/examples/jetton/minter/change_owner.dart new file mode 100644 index 0000000..af44163 --- /dev/null +++ b/example/lib/examples/jetton/minter/change_owner.dart @@ -0,0 +1,22 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + final newOwner = + TestWallet(version: WalletVersion.v5R1, index: 96); + + final jetton = await JettonMinter.fromAddress( + owner: wallet.wallet, + address: TonAddress("Ef9sa8u1xuh6V5-ueNUEEiOYIjxxAQlqRAZ__7GGc_yJ5Lqf"), + rpc: wallet.rpc); + await jetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.1"), + operation: JettonMinterChangeAdmin(newOwner: newOwner.address), + bounce: false); + await Future.delayed(const Duration(seconds: 20)); + final state = await jetton.getJettonData(wallet.rpc); + assert(state.owner == newOwner.address); +} diff --git a/example/lib/examples/jetton/minter/mint.dart b/example/lib/examples/jetton/minter/mint.dart new file mode 100644 index 0000000..2d3e9f4 --- /dev/null +++ b/example/lib/examples/jetton/minter/mint.dart @@ -0,0 +1,31 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + + final metadata = JettonOnChainMetadata.snakeFormat( + name: "MRT NETWORK", + image: "https://avatars.githubusercontent.com/u/56779182?s=96&v=4", + symbol: "MRT", + decimals: 9, + description: "https://github.com/mrtnetwork/ton_dart"); + final jetton = JettonMinter.create( + owner: wallet.wallet, + state: MinterWalletState( + owner: wallet.address, + chain: TonChain.testnet, + metadata: metadata, + )); + await jetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.9"), + operation: JettonMinterMint( + totalTonAmount: TonHelper.toNano("0.5"), + to: wallet.address, + transfer: JettonMinterInternalTransfer( + jettonAmount: TonHelper.toNano("100000"), + forwardTonAmount: TonHelper.toNano("0.01")), + jettonAmount: TonHelper.toNano("100000"))); +} diff --git a/example/lib/examples/jetton/stable_minter/deploy.dart b/example/lib/examples/jetton/stable_minter/deploy.dart new file mode 100644 index 0000000..b0eb8af --- /dev/null +++ b/example/lib/examples/jetton/stable_minter/deploy.dart @@ -0,0 +1,18 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet( + version: WalletVersion.v5R1, chain: TonChain.mainnet); + final stableJetton = StableJettonMinter.create( + owner: wallet.wallet, + state: StableTokenMinterState( + adminAddress: wallet.address, + metadata: const StableJettonOffChainMetadata( + "https://raw.githubusercontent.com/mrtnetwork/ton_dart/d033738c5aad3877ccdd4fa7f570cad5fe46de7b/example/metadata.json"), + )); + await stableJetton.deploy( + params: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.3")); +} diff --git a/example/lib/examples/jetton/stable_minter/mint.dart b/example/lib/examples/jetton/stable_minter/mint.dart new file mode 100644 index 0000000..e58fddc --- /dev/null +++ b/example/lib/examples/jetton/stable_minter/mint.dart @@ -0,0 +1,26 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +/// index: 96, v5 +void main() async { + final wallet = TestWallet( + version: WalletVersion.v5R1, chain: TonChain.mainnet); + final stableJetton = StableJettonMinter.create( + owner: wallet.wallet, + state: StableTokenMinterState( + adminAddress: wallet.address, + metadata: const StableJettonOffChainMetadata( + "https://raw.githubusercontent.com/mrtnetwork/ton_dart/d033738c5aad3877ccdd4fa7f570cad5fe46de7b/example/metadata.json"), + )); + await stableJetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + operation: StableJettonMinterMint( + totalTonAmount: TonHelper.toNano("0.4"), + to: wallet.address, + transfer: StableJettonMinterInternalTransfer( + jettonAmount: TonHelper.toNano("11111"), + forwardTonAmount: TonHelper.toNano("0.1"))), + amount: TonHelper.toNano("0.5"), + bounce: false); +} diff --git a/example/lib/examples/jetton/stable_minter/set_status.dart b/example/lib/examples/jetton/stable_minter/set_status.dart new file mode 100644 index 0000000..2142946 --- /dev/null +++ b/example/lib/examples/jetton/stable_minter/set_status.dart @@ -0,0 +1,25 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +/// index: 96, v5 +void main() async { + final wallet = TestWallet( + version: WalletVersion.v5R1, chain: TonChain.mainnet); + final stableJetton = StableJettonMinter.create( + owner: wallet.wallet, + state: StableTokenMinterState( + adminAddress: wallet.address, + metadata: const StableJettonOffChainMetadata( + "https://raw.githubusercontent.com/mrtnetwork/ton_dart/d033738c5aad3877ccdd4fa7f570cad5fe46de7b/example/metadata.json"), + )); + + await stableJetton.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + operation: StableJettonMinterCallTo( + address: wallet.address, + amount: TonHelper.toNano("0.1"), + operation: StableJettonWalletSetStatus( + status: StableTokenWalletStatus.statusFull)), + amount: TonHelper.toNano("0.2")); +} diff --git a/example/lib/examples/jetton/stable_minter/transfer.dart b/example/lib/examples/jetton/stable_minter/transfer.dart new file mode 100644 index 0000000..1b1a41a --- /dev/null +++ b/example/lib/examples/jetton/stable_minter/transfer.dart @@ -0,0 +1,29 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +/// index: 96, v5 +void main() async { + final wallet = TestWallet( + version: WalletVersion.v5R1, chain: TonChain.mainnet); + final receiver = TestWallet( + version: WalletVersion.v5R1, chain: TonChain.mainnet, index: 2); + final stableJetton = StableJettonMinter.create( + owner: wallet.wallet, + state: StableTokenMinterState( + adminAddress: wallet.address, + metadata: const StableJettonOffChainMetadata( + "https://raw.githubusercontent.com/mrtnetwork/ton_dart/d033738c5aad3877ccdd4fa7f570cad5fe46de7b/example/metadata.json"), + )); + final contract = await stableJetton.getJettonWalletContract( + rpc: wallet.rpc, owner: wallet.wallet); + + await contract.sendOperation( + signerParams: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + operation: StableJettonWalletTransfer( + jettonAmount: TonHelper.toNano("100"), + to: receiver.address, + forwardTonAmount: TonHelper.toNano("0.1")), + amount: TonHelper.toNano("0.4"), + bounce: false); +} diff --git a/example/lib/examples/mint.dart b/example/lib/examples/mint.dart deleted file mode 100644 index d443c2f..0000000 --- a/example/lib/examples/mint.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:ton_dart/ton_dart.dart'; -import 'http.dart'; -import 'transfer.dart'; - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 56)); - final ownerWallet = WalletV4.create( - workChain: -1, publicKey: privateKey.toPublicKey().toBytes()); - - final TonAddress destination = getTestWallet(index: 12).item1.address; - - final minter = JettonMinter.create( - owner: ownerWallet, - metadata: const JettonOffChainMetadata("https://github.com/mrtnetwork")); - - final amount = TonHelper.toNano("0.5"); - final forwardAmount = TonHelper.toNano("0.3"); - final totalAmount = TonHelper.toNano("0.4"); - final jettonAmountForMint = BigInt.parse("1${"0" * 15}"); - await minter.mint( - privateKey: privateKey, - rpc: rpc, - jettonAmout: jettonAmountForMint, - forwardTonAmount: forwardAmount, - totalTonAmount: totalAmount, - amount: totalAmount + amount, - to: destination); - - /// https://testnet.tonscan.org/tx/by-msg-hash/HUWTXbjj/dGymCB3a1reOGHFzDvtFp/XygUE6fH9YHWgUiHuJf6Q= -} diff --git a/example/lib/examples/mint_nft.dart b/example/lib/examples/mint_nft.dart deleted file mode 100644 index 1a3412b..0000000 --- a/example/lib/examples/mint_nft.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - final gnWallet = getTestWallet(index: 312); - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - final nftCollection = NFTCollectionContract( - address: TonAddress("Ef9eHhGCu4PS8M7MIoTcIbFFlja5j5u__jwW3BHLUEqlk2Wx"), - ownerWallet: wallet); - - await nftCollection.mintNft( - privateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.5"), - params: NFTMintParams( - ownerAddress: wallet.address, - //// you should select the each file in your base collection or set full path if you dont set collection base in your nft collection - metadata: const NFTItemMetadata("?filename=ipf.json"), - initAmount: TonHelper.toNano("0.1"), - itemIndex: BigInt.from(0))); - - /// https://testnet.explorer.tonnft.tools/nft/Ef-A8nDJudBo-3qx-BbHkFBZho9hJ52dRITuhzckB68dojzB - /// https://testnet.tonscan.org/tx/by-msg-hash/qHJ2eWleDjmKj576cV7DeHLIvmZFitFI/l7i34r8f9E= -} diff --git a/example/lib/examples/mnemonic.dart b/example/lib/examples/mnemonic.dart index c5432ab..a343e7a 100644 --- a/example/lib/examples/mnemonic.dart +++ b/example/lib/examples/mnemonic.dart @@ -22,7 +22,8 @@ void main() async { final publicKey = privateKey.toPublicKey(); /// Create WalletV4 instance with derived public key - final wallet = WalletV4.create(workChain: -1, publicKey: publicKey.toBytes()); + final wallet = + WalletV4.create(chain: TonChain.testnet, publicKey: publicKey.toBytes()); /// Get address from wallet final address = wallet.address; diff --git a/example/lib/examples/nft/batch_mint_nft.dart b/example/lib/examples/nft/batch_mint_nft.dart new file mode 100644 index 0000000..4883540 --- /dev/null +++ b/example/lib/examples/nft/batch_mint_nft.dart @@ -0,0 +1,56 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + final royalityParamsWallet = + TestWallet(version: WalletVersion.v5R1, index: 97); + final RoyaltyParams royaltyParams = RoyaltyParams( + + /// 10% (1000~/100)% + royaltyFactor: 100, + royaltyBase: 1000, + address: royalityParamsWallet.address); + final state = NftEditableCollectionState( + royaltyParams: royaltyParams, + ownerAddress: wallet.address, + metadata: const NFTCollectionMetadata( + + /// collection metadata + collectionMetadataUri: + "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json", + + /// collection base. use your nfts collection folders or set null for empty string + /// when you want to mint nfts if its not empty you must juast add your nft file name + /// you can see the example in mint example file + collectionBase: + "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN")); + final nftCollection = + NFTCollectionContract.create(owner: wallet.wallet, state: state); + + await nftCollection.sendOperation( + params: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("1"), + operation: NFTCollectionBatchMint( + nfts: List.generate(3, (index) { + final itemIndex = BigInt.two + BigInt.from(index); + return NFTMintParams( + ownerAddress: wallet.address.copyWith(bounceable: false), + content: NFTItemMetadata("/?filename=nft$itemIndex.json") + .toContent(collectionless: false), + initAmount: TonHelper.toNano("0.2"), + itemIndex: itemIndex); + })), + ); + await Future.delayed(const Duration(seconds: 10)); + for (int i = 0; i < 3; i++) { + final itemIndex = BigInt.two + BigInt.from(i); + final collectionData = await nftCollection.getNFTItemContractByIndex( + rpc: wallet.rpc, index: itemIndex, owner: wallet.wallet); + assert(collectionData.state?.ownerAddress == wallet.address); + assert( + collectionData.state?.metadata.uri == "/?filename=nft$itemIndex.json"); + assert(collectionData.state?.index == itemIndex); + } +} diff --git a/example/lib/examples/nft/change_owner.dart b/example/lib/examples/nft/change_owner.dart new file mode 100644 index 0000000..725c226 --- /dev/null +++ b/example/lib/examples/nft/change_owner.dart @@ -0,0 +1,24 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + final royalityParamsWallet = + TestWallet(version: WalletVersion.v5R1, index: 97); + + final nftCollection = await NFTCollectionContract.fromAddress( + owner: wallet.wallet, + address: TonAddress("Ef-CHcyJannW7zf8pNeqV_6egmdu5nLOZfsxHSAqAOd9tQc7"), + rpc: wallet.rpc); + + await nftCollection.sendOperation( + params: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.1"), + operation: + NFTCollectionChangeOwner(newOwnerAddress: royalityParamsWallet.address), + ); + await Future.delayed(const Duration(seconds: 10)); + final data = await nftCollection.getCollectionData(wallet.rpc); + assert(data.ownerAddress == royalityParamsWallet.address); +} diff --git a/example/lib/examples/nft/collection_less_nft.dart b/example/lib/examples/nft/collection_less_nft.dart new file mode 100644 index 0000000..b3e89ee --- /dev/null +++ b/example/lib/examples/nft/collection_less_nft.dart @@ -0,0 +1,18 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + + final nftItem = NFTItemContract.create( + owner: wallet.wallet, + state: NFTItemState( + index: BigInt.zero, + metadata: const NFTItemMetadata( + "https://avatars.githubusercontent.com/u/56779182?s=96&v=4"), + ownerAddress: wallet.address)); + await nftItem.deploy( + params: VersionedV5TransferParams.external(signer: wallet.signer), + amount: TonHelper.toNano("0.3"), + rpc: wallet.rpc); +} diff --git a/example/lib/examples/nft/mint.dart b/example/lib/examples/nft/mint.dart new file mode 100644 index 0000000..9883034 --- /dev/null +++ b/example/lib/examples/nft/mint.dart @@ -0,0 +1,49 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + final royalityParamsWallet = + TestWallet(version: WalletVersion.v5R1, index: 97); + final RoyaltyParams royaltyParams = RoyaltyParams( + + /// 10% (1000~/100)% + royaltyFactor: 100, + royaltyBase: 1000, + address: royalityParamsWallet.address); + final state = NftEditableCollectionState( + royaltyParams: royaltyParams, + ownerAddress: wallet.address, + metadata: const NFTCollectionMetadata( + + /// collection metadata + collectionMetadataUri: + "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json", + + /// collection base. use your nfts collection folders or set null for empty string + /// when you want to mint nfts if its not empty you must juast add your nft file name + /// you can see the example in mint example file + collectionBase: + "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN")); + final nftCollection = + NFTCollectionContract.create(owner: wallet.wallet, state: state); + + await nftCollection.sendOperation( + params: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.4"), + operation: NFTCollectionMint( + mint: NFTMintParams( + ownerAddress: wallet.address.copyWith(bounceable: false), + content: const NFTItemMetadata("/?filename=nft2.json") + .toContent(collectionless: false), + initAmount: TonHelper.toNano("0.2"), + itemIndex: BigInt.one)), + ); + await Future.delayed(const Duration(seconds: 30)); + final collectionData = await nftCollection.getNFTItemContractByIndex( + rpc: wallet.rpc, index: BigInt.one, owner: wallet.wallet); + assert(collectionData.state?.ownerAddress == wallet.address); + assert(collectionData.state?.metadata.uri == "/?filename=nft2.json"); + assert(collectionData.state?.index == BigInt.one); +} diff --git a/example/lib/examples/nft/transfer_nft.dart b/example/lib/examples/nft/transfer_nft.dart new file mode 100644 index 0000000..10ca8b0 --- /dev/null +++ b/example/lib/examples/nft/transfer_nft.dart @@ -0,0 +1,27 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWallet(version: WalletVersion.v5R1, index: 96); + final royalityParamsWallet = + TestWallet(version: WalletVersion.v5R1, index: 97); + final nftCollection = await NFTCollectionContract.fromAddress( + owner: wallet.wallet, + address: TonAddress("Ef-CHcyJannW7zf8pNeqV_6egmdu5nLOZfsxHSAqAOd9tQc7"), + rpc: wallet.rpc); + + final item = await nftCollection.getNFTItemContractByIndex( + index: BigInt.two, rpc: wallet.rpc, owner: wallet.wallet); + await item.sendOperation( + params: VersionedV5TransferParams.external(signer: wallet.signer), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.2"), + operation: NFTItemTransfer( + newOwnerAddress: + royalityParamsWallet.address.copyWith(bounceable: false), + // responseDestination: responseDestination, + forwardAmount: BigInt.zero)); + await Future.delayed(const Duration(seconds: 20)); + final state = await item.getNftData(wallet.rpc); + assert(state.ownerAddress == royalityParamsWallet.address); +} diff --git a/example/lib/examples/tranfer_nft.dart b/example/lib/examples/tranfer_nft.dart deleted file mode 100644 index b8d70f6..0000000 --- a/example/lib/examples/tranfer_nft.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:example/examples/transfer.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final testnetRpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC", - api: TonApiType.tonCenter)); - final gnWallet = getTestWallet(index: 312); - final newOwner = getTestWallet(index: 319).item1.address; - final wallet = gnWallet.item1; - final privateKey = gnWallet.item2; - final nftCollection = NFTCollectionContract( - address: TonAddress("Ef9eHhGCu4PS8M7MIoTcIbFFlja5j5u__jwW3BHLUEqlk2Wx"), - ownerWallet: wallet); - final nftAddress = await nftCollection.getNftAddressByIndex( - index: BigInt.zero, rpc: testnetRpc); - final nftAddr = NFTItemContract(address: nftAddress, ownerWallet: wallet); - - await nftAddr.transfer( - ownerPrivateKey: privateKey, - rpc: testnetRpc, - amount: TonHelper.toNano("0.3"), - bounce: false, - params: TransferNFTParams( - newOwnerAddress: newOwner, forwardAmount: TonHelper.toNano("0.1"))); -} diff --git a/example/lib/examples/transfer.dart b/example/lib/examples/transfer.dart deleted file mode 100644 index a2418fb..0000000 --- a/example/lib/examples/transfer.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:ton_dart/ton_dart.dart'; - -import 'http.dart'; - -Tuple getTestWallet( - {int index = 0, int wokchain = -1}) { - final privateKey = Bip32Slip10Ed25519.fromSeed(List.filled(32, 56)) - .childKey(Bip32KeyIndex.hardenIndex(index)); - final pr = TonPrivateKey.fromBytes(privateKey.privateKey.raw); - final WalletV4 w = WalletV4.create( - workChain: wokchain, publicKey: pr.toPublicKey().toBytes()); - return Tuple(w, pr); -} - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 39)); - final wallet = WalletV4.create( - workChain: -1, publicKey: privateKey.toPublicKey().toBytes()); - - final destination = - TonAddress("Ef_GHcGwnw-bASoxTGQRMNwMQ6w9iCQnTqrv1REDfJ5fCYD2"); - - await wallet.sendTransfer(messages: [ - wallet.createMessageInfo( - amount: TonHelper.toNano("0.1"), destination: destination) - ], privateKey: privateKey, rpc: rpc); - - /// https://testnet.tonscan.org/tx/by-msg-hash/ds948UY3_gyU8lXDwgFWMb7ddxcGiqSV7v0gz6kkGu8= -} diff --git a/example/lib/examples/transfer_jetton.dart b/example/lib/examples/transfer_jetton.dart deleted file mode 100644 index faa3426..0000000 --- a/example/lib/examples/transfer_jetton.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:ton_dart/ton_dart.dart'; -import 'http.dart'; - -Tuple getTestWallet({int index = 0}) { - final privateKey = Bip32Slip10Ed25519.fromSeed(List.filled(32, 56)) - .childKey(Bip32KeyIndex.hardenIndex(index)); - final pr = TonPrivateKey.fromBytes(privateKey.privateKey.raw); - final WalletV4 w = - WalletV4.create(workChain: -1, publicKey: pr.toPublicKey().toBytes()); - return Tuple(w, pr); -} - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com/api/v2/jsonRPC")); - - final walletDetails = getTestWallet(index: 12); - - final privateKey = walletDetails.item2; - final WalletV4 wallet = walletDetails.item1; - - /// owner its not important in this case. we just want to get jetton wallet address - final minter = JettonMinter( - owner: wallet, - address: TonAddress("Ef9PKHV5y7ynx7ITXZqowOkQeY4WynREXDoIn6XomBfIePqS")); - - final transferAddress = getTestWallet(index: 13).item1.address; - - final jettonWalletAddress = - await minter.getWalletAddress(rpc: rpc, owner: wallet.address); - final jettonWallet = JettonWallet.fromAddress( - jettonWalletAddress: jettonWalletAddress, owner: wallet); - - final forwardTonAmount = TonHelper.toNano("0.1"); - final transferAmount = BigInt.from(1000000000); - final BigInt amount = TonHelper.toNano("0.3"); - - await jettonWallet.transfer( - privateKey: privateKey, - rpc: rpc, - destination: transferAddress, - forwardTonAmount: forwardTonAmount, - jettonAmount: transferAmount, - amount: amount + forwardTonAmount); - - /// https://testnet.tonscan.org/jetton/Ef8gF-KVanZWtQEFVyARJURROJKOCoBX6FRdso6DW9Nw3uu0 -} diff --git a/example/lib/examples/versioned_wallet/highload/v3.dart b/example/lib/examples/versioned_wallet/highload/v3.dart new file mode 100644 index 0000000..7c1232a --- /dev/null +++ b/example/lib/examples/versioned_wallet/highload/v3.dart @@ -0,0 +1,30 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWalletHighLoadWallet wallet = TestWalletHighLoadWallet(index: 12); + final TestWallet destination = + TestWallet(version: WalletVersion.v5R1, index: 10); + final query = HighloadQueryId.fromQueryId(BigInt.one); + final nextQueryId = query.getQueryId(); + await wallet.wallet.sendTransfer( + params: HighloadTransferParams( + queryId: nextQueryId, + signer: wallet.signer, + messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("0.1"))), + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + assert(state.subWalletId == wallet.wallet.state!.subWalletId); + await HighloadWalletV3.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/highload/v3_mutiple_destination.dart b/example/lib/examples/versioned_wallet/highload/v3_mutiple_destination.dart new file mode 100644 index 0000000..53c55a1 --- /dev/null +++ b/example/lib/examples/versioned_wallet/highload/v3_mutiple_destination.dart @@ -0,0 +1,60 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWalletHighLoadWallet wallet = TestWalletHighLoadWallet(index: 12); + final destination = TestWallet(version: WalletVersion.v1R1); + final destination2 = TestWallet(version: WalletVersion.v1R2); + final destination3 = TestWallet(version: WalletVersion.v1R3); + final destination4 = TestWallet(version: WalletVersion.v2R1); + final destination5 = TestWallet(version: WalletVersion.v2R2); + final destination6 = TestWallet(version: WalletVersion.v3R1); + final destination7 = TestWallet(version: WalletVersion.v3R2); + final destination8 = TestWallet(version: WalletVersion.v4); + final destination9 = TestWallet(version: WalletVersion.v5R1, index: 1); + final query = HighloadQueryId.fromQueryId(BigInt.one); + final nextQueryId = query.getNext().getQueryId(); + await wallet.wallet.sendTransfer( + params: HighloadTransferParams( + queryId: nextQueryId, + signer: wallet.signer, + messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination3.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination4.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination5.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination6.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination7.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination8.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination9.address, + amount: TonHelper.toNano("0.01"))), + ]), + rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/multi_owner/r2.dart b/example/lib/examples/versioned_wallet/multi_owner/r2.dart new file mode 100644 index 0000000..ef9b79e --- /dev/null +++ b/example/lib/examples/versioned_wallet/multi_owner/r2.dart @@ -0,0 +1,60 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final wallet = TestWalletMultiOwner( + startIndex: 0, proposIndex: 144, threshHold: 2, name: "Multisig owner"); + final destination1 = + TestWallet(version: WalletVersion.v4, name: "destination 1"); + final destination2 = + TestWallet(version: WalletVersion.v5R1, index: 3, name: "destination 2"); + final int expire = (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 3600; + final proposer = wallet.wallet.changeOwnerWallet(wallet.proposer); + + /// create new order for transfer Ton from multi owner account + await proposer.sendTransfer( + params: MultiOwnerTransferParams( + params: VersionedTransferParams(privateKey: wallet.proposerKey), + expirationDate: BigInt.from(expire), + amount: TonHelper.toNano("0.5"), + messages: [ + OutActionMultiSigSendMsg( + outMessage: TransactioUtils.internal( + destination: + destination1.address.copyWith(bounceable: false), + amount: TonHelper.toNano("0.01"))), + OutActionMultiSigSendMsg( + outMessage: TransactioUtils.internal( + destination: + destination2.address.copyWith(bounceable: false), + amount: TonHelper.toNano("0.01"))), + ]), + rpc: wallet.rpc); + + /// wait transaction confirmed + await Future.delayed(const Duration(seconds: 30)); + + /// get wallet state + final state = await wallet.wallet.getStateData(wallet.rpc); + + /// get previous seqno + final seqno = state.nextOrderSeqno - BigInt.one; + for (int i = 0; i < wallet.thereshHold; i++) { + final signerWalletContract = wallet.signerWalletes[i]; + + /// get order contract + final orderContract = await wallet.wallet.getOrderContract( + rpc: wallet.rpc, seqno: seqno, signerWallet: signerWalletContract); + + /// approve order + await orderContract.sendApprove( + params: VersionedV5TransferParams.external(signer: wallet.signers[i]), + rpc: wallet.rpc, + amount: TonHelper.toNano("0.3")); + if (i + 1 == wallet.thereshHold) { + await Future.delayed(const Duration(seconds: 30)); + final state = await orderContract.getOrderData(wallet.rpc); + assert(state.executed == true); + } + } +} diff --git a/example/lib/examples/versioned_wallet/test_wallet.dart b/example/lib/examples/versioned_wallet/test_wallet.dart new file mode 100644 index 0000000..e989008 --- /dev/null +++ b/example/lib/examples/versioned_wallet/test_wallet.dart @@ -0,0 +1,284 @@ +// ignore_for_file: avoid_print + +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:example/examples/http.dart'; +import 'package:ton_dart/ton_dart.dart'; + +class TestWallet { + final T wallet; + final TonPrivateKey signer; + final TonProvider rpc; + final String? name; + const TestWallet._( + {required this.wallet, + required this.signer, + required this.rpc, + this.name}); + TonAddress get address => wallet.address; + TonAddress getAddress({bool bounceableAddress = true}) { + return TonAddress( + wallet.address.toFriendlyAddress(bounceable: bounceableAddress)); + } + + @override + String toString() { + final toJson = { + "address": address.toFriendlyAddress( + testOnly: wallet.chain == TonChain.testnet ? true : false, + bounceable: true), + "privateKey": signer.toHex(), + "publickKey": signer.toPublicKey().toHex(), + "version": wallet.type.name, + }; + if (name != null) { + return "$name$toJson"; + } + return "$toJson"; + } + + factory TestWallet( + {required WalletVersion version, + TonChain chain = TonChain.testnet, + int index = 0, + bool bounceableAddress = false, + String tonApiUrl = "https://testnet.tonapi.io", + String tonCenterUrl = "https://testnet.toncenter.com", + TonApiType rpcApiUse = TonApiType.tonApi, + String? name, + bool log = true}) { + final rpc = TonProvider(HTTPProvider( + tonApiUrl: tonApiUrl, tonCenterUrl: tonCenterUrl, api: rpcApiUse)); + final hdWallet = Bip32Slip10Ed25519.fromSeed(List.filled(32, 25)) + .childKey(Bip32KeyIndex.hardenIndex(index)); + // final hdWallet = Bip32Slip10Ed25519.fromSeed(List.filled(32, 56)) + // .childKey(Bip32KeyIndex.hardenIndex(index)); + final privateKey = TonPrivateKey.fromBytes(hdWallet.privateKey.raw); + final List publicKey = privateKey.toPublicKey().toBytes(); + VersionedWalletContract contract; + switch (version) { + case WalletVersion.v1R1: + contract = WalletV1R1.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v1R2: + contract = WalletV1R2.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v1R3: + contract = WalletV1R3.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v2R1: + contract = WalletV2R1.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v2R2: + contract = WalletV2R2.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v3R1: + contract = WalletV3R1.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v3R2: + contract = WalletV3R2.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v4: + contract = WalletV4.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress); + break; + case WalletVersion.v5R1: + contract = WalletV5R1.create( + chain: chain, + publicKey: publicKey, + bounceableAddress: bounceableAddress, + context: V5R1ClientContext(subwalletNumber: 0, chain: chain)); + break; + default: + throw UnimplementedError("Unknow wallet contract"); + } + if (contract is! T) { + throw TypeError(); + } + final wallet = TestWallet._( + signer: privateKey, wallet: contract, rpc: rpc, name: name); + if (log) { + print(wallet); + } + return wallet; + } +} + +class TestWalletHighLoadWallet { + final HighloadWalletV3 wallet; + final TonPrivateKey signer; + final TonProvider rpc; + final String? name; + const TestWalletHighLoadWallet._( + {required this.wallet, + required this.signer, + required this.rpc, + this.name}); + TonAddress get address => wallet.address; + TonAddress getAddress({bool bounceableAddress = true}) { + return TonAddress( + wallet.address.toFriendlyAddress(bounceable: bounceableAddress)); + } + + @override + String toString() { + final toJson = { + "address": address.toFriendlyAddress( + testOnly: wallet.chain == TonChain.testnet ? true : false, + bounceable: true), + "rawAddress": address.toRawAddress(), + "privateKey": signer.toHex(), + "publickKey": signer.toPublicKey().toHex(), + }; + if (name != null) { + return "$name$toJson"; + } + return "$toJson"; + } + + factory TestWalletHighLoadWallet( + {TonChain chain = TonChain.testnet, + int index = 0, + bool bounceableAddress = false, + String tonApiUrl = "https://testnet.tonapi.io", + String tonCenterUrl = "https://testnet.toncenter.com", + TonApiType rpcApiUse = TonApiType.tonApi, + String? name, + bool log = true}) { + final rpc = TonProvider(HTTPProvider( + tonApiUrl: tonApiUrl, tonCenterUrl: tonCenterUrl, api: rpcApiUse)); + final hdWallet = Bip32Slip10Ed25519.fromSeed(List.filled(32, 25)) + .childKey(Bip32KeyIndex.hardenIndex(index)); + final privateKey = TonPrivateKey.fromBytes(hdWallet.privateKey.raw); + final List publicKey = privateKey.toPublicKey().toBytes(); + final wallet = TestWalletHighLoadWallet._( + signer: privateKey, + wallet: HighloadWalletV3.create(chain: chain, publicKey: publicKey), + rpc: rpc, + name: name); + if (log) { + print(wallet); + } + return wallet; + } +} + +class TestWalletMultiOwner { + final MultiOwnerContract wallet; + final List signerWalletes; + final WalletV4 proposer; + final TonPrivateKey proposerKey; + final List signers; + TonPrivateKey get ownerKey => signers[0]; + final int thereshHold; + final TonProvider rpc; + final String? name; + const TestWalletMultiOwner._( + {required this.wallet, + required this.proposerKey, + required this.signerWalletes, + required this.signers, + required this.proposer, + required this.rpc, + required this.thereshHold, + this.name}); + TonAddress get address => wallet.address; + TonAddress getAddress({bool bounceableAddress = true}) { + return TonAddress( + wallet.address.toFriendlyAddress(bounceable: bounceableAddress)); + } + + @override + String toString() { + final toJson = { + "address": address.toFriendlyAddress( + testOnly: wallet.chain == TonChain.testnet ? true : false, + bounceable: true), + "rawAddress": address.toRawAddress(), + "signerWallets": signerWalletes.map((e) => e.address).toList(), + "proposer": proposer.address + }; + if (name != null) { + return "$name$toJson"; + } + return "$toJson"; + } + + factory TestWalletMultiOwner( + {TonChain chain = TonChain.testnet, + int startIndex = 0, + int proposIndex = 144, + int threshHold = 2, + bool bounceableAddress = false, + String tonApiUrl = "https://testnet.tonapi.io", + String tonCenterUrl = "https://testnet.toncenter.com", + TonApiType rpcApiUse = TonApiType.tonApi, + String? name, + bool log = true}) { + final rpc = TonProvider(HTTPProvider( + tonApiUrl: tonApiUrl, tonCenterUrl: tonCenterUrl, api: rpcApiUse)); + final masterWallet = Bip32Slip10Ed25519.fromSeed(List.filled(32, 25)); + final proposerHdWallet = + Bip32Slip10Ed25519.fromSeed(List.filled(32, 25)) + .childKey(Bip32KeyIndex.hardenIndex(proposIndex)); + final proposerPrivateKey = + TonPrivateKey.fromBytes(proposerHdWallet.privateKey.raw); + final proposer = WalletV4.create( + publicKey: proposerPrivateKey.toPublicKey().toBytes(), chain: chain); + List signersWallet = []; + final List signers = []; + for (int i = 0; i < threshHold; i++) { + final hdWallet = masterWallet.childKey(Bip32KeyIndex.hardenIndex(i)); + final privateKey = TonPrivateKey.fromBytes(hdWallet.privateKey.raw); + + final List publicKey = privateKey.toPublicKey().toBytes(); + final walletV5 = WalletV5R1.create( + context: V5R1CustomContext(context: startIndex, chain: chain), + publicKey: publicKey); + signersWallet.add(walletV5); + signers.add(privateKey); + } + final multiOwner = MultiOwnerContract.create( + chain: chain, + owner: signersWallet[0], + threshold: threshHold, + signers: signersWallet.map((e) => e.address).toList(), + proposers: [proposer.address], + allowArbitrarySeqno: false); + final wallet = TestWalletMultiOwner._( + wallet: multiOwner, + proposerKey: proposerPrivateKey, + signerWalletes: signersWallet, + signers: signers, + proposer: proposer, + rpc: rpc, + thereshHold: threshHold, + name: name); + if (log) { + print(wallet); + } + return wallet; + } +} diff --git a/example/lib/examples/versioned_wallet/v1/r1.dart b/example/lib/examples/versioned_wallet/v1/r1.dart new file mode 100644 index 0000000..3ceb2e7 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v1/r1.dart @@ -0,0 +1,25 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v1R1); + final destination = TestWallet(version: WalletVersion.v1R2); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("4.5"))) + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + await WalletV1R1.fromAddress(address: wallet.address, rpc: wallet.rpc); + + /// https://testnet.tonscan.org/address/kf9serWJThPWFoZqR5ad3GSjWsGmY8I_8d3IQuKOyIlODJlh +} diff --git a/example/lib/examples/versioned_wallet/v1/r2.dart b/example/lib/examples/versioned_wallet/v1/r2.dart new file mode 100644 index 0000000..3183226 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v1/r2.dart @@ -0,0 +1,25 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v1R2); + final destination = TestWallet(version: WalletVersion.v1R3); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("4.3"))) + ]), + rpc: wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + await WalletV1R2.fromAddress(address: wallet.address, rpc: wallet.rpc); + + /// https://testnet.tonscan.org/address/kf9PppquOLEHxw89z20d_aRKhRuVN3po1FnNmxoTwpI6TjNo + /// te6cckECBAEAAR4AA9GJ/p9NNVxxYg+OHnue2jv7SJUKNypu9NGos5s2NCeFJHScEZ4+wlW9RyWOJ//yi4pbFZIAzrES/1KBtv4QAHdlissp5Bt9XlkrImuNzj0wnIW1JjYx0JHl5t8EEK6vdTe/LcCgAAAAADABAgMAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVABIAAAAAKtgoEJ1ZN5Mh+qkE6uI0oqj1O+J3xvj4fEVxGNbJdrrAGpCf+T222vdI+gGe/iYGArf7PUYvXHt1PIr1bG3SupogDWdKAgCZlgAAAAAAAAAAAAAAAAAAIDYq7Q= +} diff --git a/example/lib/examples/versioned_wallet/v1/r3.dart b/example/lib/examples/versioned_wallet/v1/r3.dart new file mode 100644 index 0000000..caaf5f1 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v1/r3.dart @@ -0,0 +1,23 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v1R3); + final destination = TestWallet(version: WalletVersion.v2R1); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("4.1"))) + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + await WalletV1R3.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v2/r1.dart b/example/lib/examples/versioned_wallet/v2/r1.dart new file mode 100644 index 0000000..8579220 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v2/r1.dart @@ -0,0 +1,26 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v2R1); + final destination = TestWallet(version: WalletVersion.v2R2); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("3.9"))) + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + await WalletV2R1.fromAddress(address: wallet.address, rpc: wallet.rpc); + + /// https://testnet.tonscan.org/address/kf9PppquOLEHxw89z20d_aRKhRuVN3po1FnNmxoTwpI6TjNo + /// te6cckECBAEAASUAA9mJ/6r/sDgx0V89qt3W0WmEgyt6WuQwDtrnAUfkSnIvNqReEZtEP5LQx+sw0R4cKV5s+H4O6bUfgXwohyTiCS7x6Ypv/IC1e4NuEVDD3WwX0b8MY8jUTt5V8HbWzfMiReRFDMHgAAAAH////+AwAQIDAKr/ACDdIIIBTJe6lzDtRNDXCx/gpPJggwjXGCDTH9MfAfgju/Jj7UTQ0x/T/9FRMbryoQP5AVQQQvkQ8qL4AAKTINdKltMH1AL7AOjRpMjLH8v/ye1UAEgAAAAAq2CgQnVk3kyH6qQTq4jSiqPU74nfG+Ph8RXEY1sl2usAaEJ/wNzpXSCNWkjyB6KI7uAJadnuP+GA3Y0Nj84gaklvgHCnQ6o4AAAAAAAAAAAAAAAAAAAKyyGV +} diff --git a/example/lib/examples/versioned_wallet/v2/r2.dart b/example/lib/examples/versioned_wallet/v2/r2.dart new file mode 100644 index 0000000..f211f0f --- /dev/null +++ b/example/lib/examples/versioned_wallet/v2/r2.dart @@ -0,0 +1,34 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v2R2); + final destination = TestWallet(version: WalletVersion.v3R1); + final destination1 = TestWallet(version: WalletVersion.v4); + final destination2 = TestWallet(version: WalletVersion.v5R1); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("3.6"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination1.address, + amount: TonHelper.toNano("0.001"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.001"))) + ]), + rpc: wallet.rpc); + + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + await WalletV2R2.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v3/r1.dart b/example/lib/examples/versioned_wallet/v3/r1.dart new file mode 100644 index 0000000..2cd147a --- /dev/null +++ b/example/lib/examples/versioned_wallet/v3/r1.dart @@ -0,0 +1,34 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v3R1); + final destination = TestWallet(version: WalletVersion.v3R2); + final destination1 = TestWallet(version: WalletVersion.v4); + final destination2 = TestWallet(version: WalletVersion.v5R1); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("3.3"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination1.address, + amount: TonHelper.toNano("0.001"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.001"))) + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + assert(state.subwallet == wallet.wallet.state!.subwallet); + await WalletV3R1.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v3/r2.dart b/example/lib/examples/versioned_wallet/v3/r2.dart new file mode 100644 index 0000000..a8606ea --- /dev/null +++ b/example/lib/examples/versioned_wallet/v3/r2.dart @@ -0,0 +1,29 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v3R2); + final destination = TestWallet(version: WalletVersion.v4); + final destination2 = TestWallet(version: WalletVersion.v5R1); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("0.5"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.4"))) + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + assert(state.subwallet == wallet.wallet.state!.subwallet); + await WalletV3R2.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v4/v4.dart b/example/lib/examples/versioned_wallet/v4/v4.dart new file mode 100644 index 0000000..a9319ca --- /dev/null +++ b/example/lib/examples/versioned_wallet/v4/v4.dart @@ -0,0 +1,25 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v4); + final destination2 = TestWallet(version: WalletVersion.v5R1); + await wallet.wallet.sendTransfer( + params: VersionedTransferParams(privateKey: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.03"))) + ]), + rpc: wallet.rpc); + + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + assert(state.subwallet == wallet.wallet.state!.subwallet); + await WalletV4.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v5/r1.dart b/example/lib/examples/versioned_wallet/v5/r1.dart new file mode 100644 index 0000000..f3cd1a5 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v5/r1.dart @@ -0,0 +1,67 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v5R1); + + final destination = TestWallet(version: WalletVersion.v1R1); + final destination2 = TestWallet(version: WalletVersion.v1R2); + final destination3 = TestWallet(version: WalletVersion.v1R3); + final destination4 = TestWallet(version: WalletVersion.v2R1); + final destination5 = TestWallet(version: WalletVersion.v2R2); + final destination6 = TestWallet(version: WalletVersion.v3R1); + final destination7 = TestWallet(version: WalletVersion.v3R2); + final destination8 = TestWallet(version: WalletVersion.v4); + final destination9 = TestWallet(version: WalletVersion.v5R1, index: 1); + + await wallet.wallet.sendTransfer( + params: + VersionedV5TransferParams.external(signer: wallet.signer, messages: [ + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination2.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination3.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination4.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination5.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination6.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination7.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination8.address, + amount: TonHelper.toNano("0.01"))), + OutActionSendMsg( + outMessage: TransactioUtils.internal( + destination: destination9.address, + amount: TonHelper.toNano("0.01"))), + ]), + rpc: wallet.rpc); + await wallet.wallet.getBalance(wallet.rpc); + final publicKey = await wallet.wallet.getPublicKey(wallet.rpc); + assert(publicKey == wallet.signer.toPublicKey().toHex()); + final state = await wallet.wallet.readState(wallet.rpc); + assert(BytesUtils.bytesEqual( + state.publicKey, wallet.signer.toPublicKey().toBytes())); + assert(state.context == wallet.wallet.state!.context); + await WalletV5R1.fromAddress(address: wallet.address, rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v5/r1_add_extention.dart b/example/lib/examples/versioned_wallet/v5/r1_add_extention.dart new file mode 100644 index 0000000..24457fb --- /dev/null +++ b/example/lib/examples/versioned_wallet/v5/r1_add_extention.dart @@ -0,0 +1,13 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v5R1); + final TestWallet newExtention = + TestWallet(version: WalletVersion.v5R1, index: 12); + await wallet.wallet.sendTransfer( + params: VersionedV5TransferParams.external( + signer: wallet.signer, + messages: [OutActionAddExtension(newExtention.address)]), + rpc: wallet.rpc); +} diff --git a/example/lib/examples/versioned_wallet/v5/r1_remove_extension.dart b/example/lib/examples/versioned_wallet/v5/r1_remove_extension.dart new file mode 100644 index 0000000..499f419 --- /dev/null +++ b/example/lib/examples/versioned_wallet/v5/r1_remove_extension.dart @@ -0,0 +1,13 @@ +import 'package:example/examples/versioned_wallet/test_wallet.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() async { + final TestWallet wallet = TestWallet(version: WalletVersion.v5R1); + final TestWallet newExtention = + TestWallet(version: WalletVersion.v5R1, index: 12); + await wallet.wallet.sendTransfer( + params: VersionedV5TransferParams.external( + signer: wallet.signer, + messages: [OutActionRemoveExtension(newExtention.address)]), + rpc: wallet.rpc); +} diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.lock b/example/pubspec.lock index 7185502..eed16c1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -87,10 +87,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -103,18 +103,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -143,18 +143,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" ton_dart: dependency: "direct main" description: @@ -243,18 +243,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" sdks: - dart: ">=3.3.3 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/test/test_test.dart b/example/test/test_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/example/test/test_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 5a18c00..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:example/examples/http.dart'; -import 'package:ton_dart/ton_dart.dart'; - -void main() async { - final rpc = TonProvider(HTTPProvider( - tonApiUrl: "https://testnet.tonapi.io", - tonCenterUrl: "https://testnet.toncenter.com")); - final privateKey = TonPrivateKey.fromBytes(List.filled(32, 56)); - final ownerWallet = WalletV4.create( - workChain: -1, publicKey: privateKey.toPublicKey().toBytes()); - - final minter = JettonMinter.create( - owner: ownerWallet, - metadata: const JettonOffChainMetadata( - "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf.json")); - final hash = await minter.deploy( - ownerPrivateKey: privateKey, rpc: rpc, amount: TonHelper.toNano("0.5")); - print("hash $hash"); - - /// https://testnet.tonscan.org/jetton/Ef86P5RpRpBOfm76uCPqU78Y4TdmFAiqJVnx1lozUx0w1gHZ -} diff --git a/lib/src/address/address/address.dart b/lib/src/address/address/address.dart index e067d2e..d1bb436 100644 --- a/lib/src/address/address/address.dart +++ b/lib/src/address/address/address.dart @@ -3,18 +3,44 @@ import 'package:ton_dart/src/address/core/ton_address.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/models/models/state_init.dart'; +/// A class representing the different types of TON (The Open Network) addresses. +/// +/// This class uses a private named constructor to create constant instances representing +/// different types of TON addresses, such as bounceable, non-bounceable, raw, and test variants. class TonAddressType { + /// The name of the TON address type. final String name; + + /// Private constructor for defining specific instances of [TonAddressType]. const TonAddressType._(this.name); + + /// A bounceable address, typically used in cases where the address can receive bounced messages. static const TonAddressType bounceable = TonAddressType._("Bounceable"); + + /// A non-bounceable address, used when bounced messages are not allowed. static const TonAddressType nonBounceable = TonAddressType._("Non-Bounceable"); + + /// A raw address type, typically used in low-level interactions without the friendly representation. static const TonAddressType raw = TonAddressType._("Raw"); + + /// A bounceable address used in test networks. static const TonAddressType testBounceable = TonAddressType._("Test(Bounceable)"); + + /// A non-bounceable address used in test networks. static const TonAddressType testNonBounceable = TonAddressType._("Test(Non-Bounceable)"); + + /// Returns `true` if the address type is bounceable. bool get isBounceable => this == bounceable; + + /// Factory constructor that creates a [TonAddressType] from a [TonAddress]. + /// + /// If the address is not in a friendly format, it will return [TonAddressType.raw]. + /// If the address is bounceable, it returns [TonAddressType.bounceable] or [TonAddressType.testBounceable] + /// based on whether the address is test-only. + /// If not bounceable, it returns [TonAddressType.nonBounceable] or [TonAddressType.testNonBounceable]. factory TonAddressType.fromAddress(TonAddress address) { if (!address.isFriendly) { return TonAddressType.raw; @@ -28,18 +54,31 @@ class TonAddressType { } } +/// Represents a TON address and provides methods for handling its type. class TonAddress implements TonBaseAddress { static final _decoder = TonAddrDecoder(); final int workChain; final List hash; final List defaultFlags; + + /// Determines the address type based on flags. late final TonAddressType type = TonAddressType.fromAddress(this); + + /// Returns true if the address is in a friendly format. bool get isFriendly => defaultFlags.isNotEmpty; + + /// Returns true if the address is test-only. bool get isTestOnly => defaultFlags.contains(FriendlyAddressFlags.test); + + /// Returns true if the address is bounceable. bool get isBounceable => defaultFlags.contains(FriendlyAddressFlags.bounceable); + + /// Private constructor initializing workChain, hash, and flags. TonAddress._(this.workChain, this.hash, List flags) : defaultFlags = List.unmodifiable(flags); + + /// Factory to create a TON address from raw bytes. factory TonAddress.fromBytes(int workChain, List hash, {bool bounceable = true, bool testNet = false}) { final flags = [ @@ -52,6 +91,7 @@ class TonAddress implements TonBaseAddress { return TonAddress._(workChain, hash, flags); } + /// Factory to create a TON address from a state initialization. factory TonAddress.fromState( {required StateInit state, required int workChain, @@ -62,6 +102,7 @@ class TonAddress implements TonBaseAddress { bounceable: bounceable, testNet: testNet); } + /// Factory to create a TON address from a string. factory TonAddress(String address, {int? forceWorkchain, bool? bounceable}) { final decode = _decoder.decodeWithResult(address, {"workchain": forceWorkchain}); @@ -79,15 +120,24 @@ class TonAddress implements TonBaseAddress { return TonAddress._(decode.workchain, decode.hash, flags); } + /// Creates a copy of the address with optional bounceable and test-only flags. + TonAddress copyWith({bool? bounceable, bool? testOnly}) { + return TonAddress( + toFriendlyAddress(bounceable: bounceable, testOnly: testOnly)); + } + + /// Converts the address to a raw format. String toRawAddress() { return BytesUtils.toHexString(hash, prefix: "$workChain:"); } + /// Converts the address to a byte array. List toBytes() { final int chain = workChain & mask8; return [...hash, ...List.generate(4, (index) => chain)]; } + /// Converts the address to a friendly format. String toFriendlyAddress( {bool? bounceable, bool? testOnly, bool urlSafe = true}) { return TonAddressUtils.encodeAddress( @@ -98,12 +148,14 @@ class TonAddress implements TonBaseAddress { testOnly: testOnly ?? isTestOnly); } + /// Returns the string representation of the address. @override String toString() { if (!isFriendly) return toRawAddress(); return toFriendlyAddress(); } + /// Compares two addresses for equality. @override operator ==(other) { if (other is! TonAddress) return false; @@ -111,6 +163,7 @@ class TonAddress implements TonBaseAddress { other.workChain == workChain; } + /// Returns the hash code of the address. @override int get hashCode => Object.hash(hash, workChain); } diff --git a/lib/src/address/address/external_address.dart b/lib/src/address/address/external_address.dart index bcc0dca..47f1b09 100644 --- a/lib/src/address/address/external_address.dart +++ b/lib/src/address/address/external_address.dart @@ -2,19 +2,26 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/address/core/ton_address.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; +/// Represents an external address with a value and bit length. class ExternalAddress with JsonSerialization implements TonBaseAddress { final BigInt value; final int bits; + + /// Constructs an [ExternalAddress] with the given value and bit length. const ExternalAddress(this.value, this.bits); + + /// Creates an [ExternalAddress] from a JSON object. factory ExternalAddress.fromJson(Map json) { return ExternalAddress(BigintUtils.parse(json["value"]), json["bits"]); } + /// Returns a string representation of the external address. @override String toString() { return 'External<$bits:$value>'; } + /// Converts the external address to a JSON object. @override Map toJson() { return {"value": value.toString(), "bits": bits}; diff --git a/lib/src/boc/bit/bit_builder.dart b/lib/src/boc/bit/bit_builder.dart index 8d14d88..c47cb72 100644 --- a/lib/src/boc/bit/bit_builder.dart +++ b/lib/src/boc/bit/bit_builder.dart @@ -34,16 +34,20 @@ class _BitBuilderUtils { } } +/// A class for building and manipulating bits in a buffer. class BitBuilder { final List _bytes; int _length; + /// Initializes a bit builder with a byte buffer of specified size. BitBuilder({int size = 1023}) : _bytes = List.filled((size / 8).ceil(), 0), _length = 0; + /// Returns the current length of the bit stream. int get length => _length; + /// Writes a single bit to the buffer. void writeBit(bool value) { if (_length > _bytes.length * 8) { throw BocException("Overflow bytes", @@ -56,12 +60,14 @@ class BitBuilder { _length++; } + /// Writes a sequence of bits from a [BitString]. void writeBits(BitString src) { for (int i = 0; i < src.length; i++) { writeBit(src.at(i)); } } + /// Writes a buffer of bytes to the bit stream. void writeBuffer(List src) { BytesUtils.validateBytes(src); if (_length % 8 == 0) { @@ -80,6 +86,7 @@ class BitBuilder { } } + /// Writes an unsigned integer value using a specified number of bits. void writeUint(dynamic value, int bits) { _BitBuilderUtils.validateBits(bits); final BigInt v = _BitBuilderUtils.parseBigint(value); @@ -116,7 +123,6 @@ class BitBuilder { } } bits -= tillByte; - // final mask8Big = BigInt.from(0xff); while (bits > 0) { if (bits >= 8) { _bytes[_length ~/ 8] = @@ -132,6 +138,7 @@ class BitBuilder { } } + /// Writes a signed integer value using a specified number of bits. void writeInt(dynamic value, int bits) { _BitBuilderUtils.validateBits(bits); BigInt v = _BitBuilderUtils.parseBigint(value, sign: true); @@ -171,6 +178,7 @@ class BitBuilder { writeUint(v, bits - 1); } + /// Writes a variable-length unsigned integer value. void writeVarUint(dynamic value, int bits) { _BitBuilderUtils.validateBits(bits); final BigInt v = _BitBuilderUtils.parseBigint(value); @@ -188,6 +196,7 @@ class BitBuilder { writeUint(v, sizeBits); } + /// Writes a variable-length signed integer value. void writeVarInt(dynamic value, int bits) { _BitBuilderUtils.validateBits(bits); final BigInt v = _BitBuilderUtils.parseBigint(value, sign: true); @@ -202,10 +211,12 @@ class BitBuilder { writeInt(v, sizeBits); } + /// Writes the specified amount as coins. void writeCoins(dynamic amount) { writeVarUint(amount, 4); } + /// Writes a TON base address or an external address. void writeAddress(TonBaseAddress? address) { if (address == null) { writeUint(0, 2); @@ -222,10 +233,12 @@ class BitBuilder { } } + /// Builds a [BitString] from the current buffer. BitString build() { return BitString(_bytes, 0, _length); } + /// Returns the current buffer if it's byte-aligned. List buffer() { if (_length % 8 != 0) { throw BocException("Buffer is not byte aligned"); @@ -233,5 +246,6 @@ class BitBuilder { return List.unmodifiable(_bytes.sublist(0, _length ~/ 8)); } + /// Returns the byte buffer. List toBytes() => List.from(_bytes); } diff --git a/lib/src/boc/bit/bit_reader.dart b/lib/src/boc/bit/bit_reader.dart index 8c49152..0d33d5c 100644 --- a/lib/src/boc/bit/bit_reader.dart +++ b/lib/src/boc/bit/bit_reader.dart @@ -2,15 +2,23 @@ import 'package:ton_dart/src/address/address.dart'; import 'package:ton_dart/src/boc/exception/exception.dart'; import 'bit_string.dart'; +/// A reader for handling bit-level operations on a `BitString`. +/// It allows loading, preloading, and skipping of bits or byte-level data. class BitReader { final List _checkpoints = []; final BitString _bits; int _offset; + + /// Returns the current bit offset. int get offset => _offset; + + /// Returns the number of remaining bits in the `BitString`. int get remaining => _bits.length - _offset; + /// Constructs a `BitReader` with the given `BitString` and optional offset. BitReader(this._bits, {int offset = 0}) : _offset = offset; + /// Skips the specified number of bits, advancing the offset. void skip(int bits) { if (bits < 0 || _offset + bits > _bits.length) { throw BocException('Index out of bounds', details: { @@ -22,6 +30,7 @@ class BitReader { _offset += bits; } + /// Resets the offset to the last saved checkpoint, or to 0 if none exists. void reset() { if (_checkpoints.isNotEmpty) { _offset = _checkpoints.removeLast(); @@ -30,125 +39,152 @@ class BitReader { } } + /// Saves the current offset as a checkpoint. void save() { _checkpoints.add(_offset); } + /// Loads a single bit and increments the offset. bool loadBit() { bool r = _bits.at(_offset); _offset++; return r; } + /// Preloads a single bit without changing the offset. bool preloadBit() { return _bits.at(_offset); } + /// Loads the specified number of bits and increments the offset. BitString loadBits(int bits) { BitString r = _bits.substring(_offset, bits); _offset += bits; return r; } + /// Preloads the specified number of bits without changing the offset. BitString preloadBits(int bits) { return _bits.substring(_offset, bits); } + /// Loads a buffer of the specified number of bytes and increments the offset. List loadBuffer(int bytes) { List buf = _preloadBuffer(bytes, _offset); _offset += bytes * 8; return buf; } + /// Preloads a buffer of the specified number of bytes without changing the offset. List preloadBuffer(int bytes) { return _preloadBuffer(bytes, _offset); } + /// Loads an unsigned integer of the specified number of bits. int loadUint(int bits) { return loadUintBig(bits).toInt(); } + /// Loads a large unsigned integer (BigInt) of the specified number of bits. BigInt loadUintBig(int bits) { BigInt loaded = _preloadUint(bits, _offset); _offset += bits; return loaded; } + /// Preloads an unsigned integer of the specified number of bits. int preloadUint(int bits) { return _preloadUint(bits, _offset).toInt(); } + /// Preloads a large unsigned integer (BigInt) of the specified number of bits. BigInt preloadUintBig(int bits) { return _preloadUint(bits, _offset); } + /// Loads a signed integer of the specified number of bits. int loadInt(int bits) { return loadIntBig(bits).toInt(); } + /// Loads a large signed integer (BigInt) of the specified number of bits. BigInt loadIntBig(int bits) { BigInt res = _preloadInt(bits, _offset); _offset += bits; return res; } + /// Preloads a signed integer of the specified number of bits. int preloadInt(int bits) { return _preloadInt(bits, _offset).toInt(); } + /// Preloads a large signed integer (BigInt) of the specified number of bits. BigInt preloadIntBig(int bits) { return _preloadInt(bits, _offset); } + /// Loads a variable-length unsigned integer, with a specified number of size bits. int loadVarUint(int bits) { int size = loadUint(bits); return loadUintBig(size * 8).toInt(); } + /// Loads a variable-length unsigned integer (BigInt). BigInt loadVarUintBig(int bits) { int size = loadUint(bits); return loadUintBig(size * 8); } + /// Preloads a variable-length unsigned integer. int preloadVarUint(int bits) { return preloadVarUintBig(bits).toInt(); } + /// Preloads a variable-length unsigned integer (BigInt). BigInt preloadVarUintBig(int bits) { int size = _preloadUint(bits, offset).toInt(); return _preloadUint(size * 8, offset + bits); } + /// Loads a variable-length signed integer. int loadVarInt(int bits) { return loadVarIntBig(bits).toInt(); } + /// Loads a variable-length signed integer (BigInt). BigInt loadVarIntBig(int bits) { int size = loadUint(bits); return loadIntBig(size * 8); } + /// Preloads a variable-length signed integer. int preloadVarInt(int bits) { return preloadVarIntBig(bits).toInt(); } + /// Preloads a variable-length signed integer (BigInt). BigInt preloadVarIntBig(int bits) { int size = _preloadInt(bits, offset).toInt(); return _preloadInt(size * 8, offset + bits); } + /// Loads a value in coins (as BigInt). BigInt loadCoins() { return loadVarUintBig(4); } + /// Preloads a value in coins. BigInt preloadCoins() { return preloadVarUintBig(4); } + /// Loads a TON address from the bitstream. TonAddress loadAddress() { return _loadInternalAddress(); } + /// Loads a TON address if it exists, otherwise returns null. TonAddress? loadMaybeAddress() { int type = preloadUint(2); if (type == 0) { @@ -158,10 +194,12 @@ class BitReader { return _loadInternalAddress(); } + /// Loads an external address from the bitstream. ExternalAddress loadExternalAddress() { return _loadExternalAddress(); } + /// Loads an external address if it exists, otherwise returns null. ExternalAddress? loadMaybeExternalAddress() { int type = preloadUint(2); if (type == 0) { @@ -171,6 +209,7 @@ class BitReader { return _loadExternalAddress(); } + /// Loads any type of address (TON or external), or returns null if none exists. TonBaseAddress? loadAddressAny() { int type = preloadUint(2); if (type == 0) { @@ -187,6 +226,7 @@ class BitReader { } } + /// Loads bits, taking into account padding (ensures byte-aligned). BitString loadPaddedBits(int bits) { if (bits % 8 != 0) { throw BocException("Invalid number of bits", details: {"bits": bits}); @@ -207,6 +247,7 @@ class BitReader { return r; } + /// Creates a clone of the current `BitReader`, preserving the bitstream and offset. BitReader clone() { return BitReader(_bits.clone(), offset: _offset); } diff --git a/lib/src/boc/bit/bit_string.dart b/lib/src/boc/bit/bit_string.dart index 2404b71..07ca2fe 100644 --- a/lib/src/boc/bit/bit_string.dart +++ b/lib/src/boc/bit/bit_string.dart @@ -16,15 +16,24 @@ class _BitStringUtils { } } +/// A utility class to handle sequences of bits. +/// Provides methods to manipulate, retrieve, and clone bit-level data. class BitString { + /// Represents an empty `BitString`. static const BitString empty = BitString._([], 0, 0); final int _offset; final int _length; final List _data; + + /// Returns the length of the bitstring. int get length => _length; + /// Private constructor for `BitString`. const BitString._(this._data, this._offset, this._length); + + /// Constructs a `BitString` from raw data, with an offset and length. + /// Throws a [BocException] if the length is out of bounds. factory BitString(List data, int offset, int length) { if (length < 0) { throw BocException("Length is out of bounds", @@ -34,10 +43,13 @@ class BitString { BytesUtils.toBytes(data, unmodifiable: true), offset, length); } + /// Returns a copy (clone) of this `BitString`. BitString clone() { return BitString(List.from(_data), _offset, _length); } + /// Returns the value of the bit at the specified index. + /// Throws a [BocException] if the index is out of bounds. bool at(int index) { _BitStringUtils.validateOffset(index, _length); if (index >= _length) { @@ -51,6 +63,17 @@ class BitString { return (_data[byteIndex] & (1 << bitIndex)) != 0; } + /// Checks if the specified index is within the bitstring's length. + /// Returns `true` if the bit exists, `false` otherwise. + bool hasBit(int index) { + if (index >= _length) { + return false; + } + return true; + } + + /// Returns a substring of the bitstring starting at the given offset and of the specified length. + /// If the length is 0, returns the empty `BitString`. BitString substring(int offset, int length) { _BitStringUtils.validateOffset(offset, _length, at: length); if (length == 0) { @@ -59,6 +82,9 @@ class BitString { return BitString(_data, _offset + offset, length); } + /// Returns a sub-buffer of the bitstring at the specified offset and length. + /// The length must be a multiple of 8, and the offset must be byte-aligned. + /// Returns `null` if these conditions are not met. List? subbuffer(int offset, int length) { _BitStringUtils.validateOffset(offset, _length, at: length); if (length % 8 != 0) { @@ -73,6 +99,7 @@ class BitString { return _data.sublist(start, end); } + /// Converts the `BitString` to a hex string representation. @override String toString() { final padded = BocUtils.bitsToPaddedBuffer(this).buffer(); @@ -96,6 +123,9 @@ class BitString { } } + /// Returns a copy of the internal data buffer as a list of bytes. + List get buffer => List.from(_data); + @override operator ==(other) { if (other is! BitString) return false; @@ -108,6 +138,4 @@ class BitString { @override int get hashCode => Object.hash(_data, _length); - - List get buffer => List.from(_data); } diff --git a/lib/src/boc/bit/builder.dart b/lib/src/boc/bit/builder.dart index f175bfc..852f4d4 100644 --- a/lib/src/boc/bit/builder.dart +++ b/lib/src/boc/bit/builder.dart @@ -10,6 +10,7 @@ import '../cell/cell.dart'; import '../cell/slice.dart'; abstract class BocSerializableObject { + const BocSerializableObject(); void store(Builder builder); } @@ -17,34 +18,49 @@ Builder beginCell() { return Builder(); } +/// A `Builder` class that facilitates storing bits, integers, addresses, coins, +/// and other complex data structures into a cell structure for serialization. class Builder { + /// Internal `BitBuilder` used to store bits. final BitBuilder _bits = BitBuilder(); + + /// List of references (cells) stored within the builder. final List _refs = []; + + /// Returns the number of references (cells) stored in the builder. int get refs => _refs.length; + /// Returns the number of bits currently stored in the builder. int get bits => _bits.length; + + /// Returns the number of available bits left for storage. int get availableBits => 1023 - bits; + /// Stores a single bit. If [value] is greater than 0, stores a `true` bit. Builder storeBit(int value) { _bits.writeBit(value > 0 ? true : false); return this; } + /// Stores a boolean value as a bit. Builder storeBitBolean(bool value) { _bits.writeBit(value); return this; } + /// Stores a `BitString` object. Builder storeBits(BitString src) { _bits.writeBits(src); return this; } + /// Stores a buffer (list of integers) into the builder. Builder storeBuffer(List src) { _bits.writeBuffer(src); return this; } + /// Conditionally stores a buffer, depending on whether [src] is `null`. Builder storeMaybeBuffer(List? src) { if (src != null) { storeBit(1); @@ -55,31 +71,49 @@ class Builder { return this; } + /// Stores an unsigned integer with a specified number of bits. Builder storeUint(dynamic value, int bits) { _bits.writeUint(value, bits); return this; } + /// Stores an 8-bit unsigned integer. Builder storeUint8(dynamic value) { _bits.writeUint(value, 8); return this; } + /// Stores a 4-bit unsigned integer. + Builder storeUint4(dynamic value) { + _bits.writeUint(value, 4); + return this; + } + + /// Stores a 32-bit unsigned integer. Builder storeUint32(dynamic value) { _bits.writeUint(value, 32); return this; } + /// Stores a 64-bit unsigned integer. Builder storeUint64(dynamic value) { _bits.writeUint(value, 64); return this; } + /// Stores a 256-bit unsigned integer. + Builder storeUint256(dynamic value) { + _bits.writeUint(value, 256); + return this; + } + + /// Stores a 16-bit unsigned integer. Builder storeUint16(dynamic value) { _bits.writeUint(value, 16); return this; } + /// Conditionally stores an unsigned integer with the specified number of bits. Builder storeMaybeUint(dynamic value, int bits) { if (value != null) { storeBit(1); @@ -90,11 +124,13 @@ class Builder { return this; } + /// Stores a signed integer with the specified number of bits. Builder storeInt(dynamic value, int bits) { _bits.writeInt(value, bits); return this; } + /// Conditionally stores a signed integer with the specified number of bits. Builder storeMaybeInt(dynamic value, int bits) { if (value != null) { storeBit(1); @@ -105,11 +141,13 @@ class Builder { return this; } + /// Stores a variable-length unsigned integer. Builder storeVarUint(dynamic value, int bits) { _bits.writeVarUint(value, bits); return this; } + /// Conditionally stores a variable-length unsigned integer. Builder storeMaybeVarUint(dynamic value, int bits) { if (value != null) { storeBit(1); @@ -120,11 +158,13 @@ class Builder { return this; } + /// Stores a variable-length signed integer. Builder storeVarInt(dynamic value, int bits) { _bits.writeVarInt(value, bits); return this; } + /// Conditionally stores a variable-length signed integer. Builder storeMaybeVarInt(dynamic value, int bits) { if (value != null) { storeBit(1); @@ -135,11 +175,13 @@ class Builder { return this; } + /// Stores a coin amount. Builder storeCoins(dynamic amount) { _bits.writeCoins(amount); return this; } + /// Conditionally stores a coin amount. Builder storeMaybeCoins(dynamic amount) { if (amount != null) { storeBit(1); @@ -150,11 +192,13 @@ class Builder { return this; } + /// Stores a TON address. Builder storeAddress(TonBaseAddress? address) { _bits.writeAddress(address); return this; } + /// Stores a reference (cell). Throws [BocException] if more than 4 references are added. Builder storeRef(Cell cell) { if (_refs.length >= 4) { throw BocException("Too many references.", @@ -164,6 +208,7 @@ class Builder { return this; } + /// Conditionally stores a reference (cell). Builder storeMaybeRef({Cell? cell}) { if (cell != null) { storeBit(1); @@ -174,6 +219,7 @@ class Builder { return this; } + /// Stores a slice of bits and references from another cell. Builder storeSlice(Slice src) { final c = src.clone(); if (c.remainingBits > 0) { @@ -185,6 +231,7 @@ class Builder { return this; } + /// Conditionally stores a slice. Builder storeMaybeSlice({Slice? src}) { if (src != null) { storeBit(1); @@ -195,10 +242,12 @@ class Builder { return this; } + /// Stores another builder's contents as a slice. Builder storeBuilder(Builder src) { return storeSlice(src.endCell().beginParse()); } + /// Conditionally stores another builder's contents. Builder storeMaybeBuilder({Builder? src}) { if (src != null) { storeBit(1); @@ -209,11 +258,13 @@ class Builder { return this; } + /// Stores a `BocSerializableObject`. Builder storeWritable(BocSerializableObject writer) { writer.store(this); return this; } + /// Conditionally stores a `BocSerializableObject`. Builder storeMaybeWritable({BocSerializableObject? writer}) { if (writer != null) { storeBit(1); @@ -224,20 +275,24 @@ class Builder { return this; } + /// Helper method to store a `BocSerializableObject`. Builder store(BocSerializableObject writer) { return storeWritable(writer); } + /// Stores a string as a tail of bits. Builder storeStringTail(String src) { BocUtils.writeString(src, this); return this; } + /// Stores a buffer (list of bytes) as a tail. Builder storeBytesTail(List src) { BocUtils.writeBuffer(src, this); return this; } + /// Conditionally stores a string tail. Builder storeMaybeStringTail({String? src}) { if (src != null) { storeBit(1); @@ -248,16 +303,19 @@ class Builder { return this; } + /// Stores a string reference tail. Builder storeStringRefTail(String src) { storeRef(beginCell().storeStringTail(src).asCell()); return this; } + /// Stores a bytes reference tail. Builder storeBytesRefTail(List src) { storeRef(beginCell().storeBytesTail(src).asCell()); return this; } + /// Conditionally stores a string reference tail. Builder storeMaybeStringRefTail({String? src}) { if (src != null) { storeBit(1); @@ -268,6 +326,7 @@ class Builder { return this; } + /// Stores a dictionary with keys [K] and values [V]. Builder storeDict( {Dictionary? dict, DictionaryKey? key, @@ -280,20 +339,24 @@ class Builder { return this; } + /// Stores a dictionary directly with keys [K] and values [V]. Builder storeDictDirect(Dictionary dict, {DictionaryKey? key, DictionaryValue? value}) { dict.storeDirect(this, key: key, value: value); return this; } + /// Finalizes the builder, returning a `Cell` with the stored bits and references. Cell endCell({bool? exotic}) { return Cell(bits: _bits.build(), refs: _refs, exotic: exotic ?? false); } + /// Returns the cell built by the builder. Cell asCell() { return endCell(); } + /// Returns a `Slice` from the built cell. Slice asSlice() { return endCell().beginParse(); } diff --git a/lib/src/boc/cell/cell.dart b/lib/src/boc/cell/cell.dart index 5df8294..7432745 100644 --- a/lib/src/boc/cell/cell.dart +++ b/lib/src/boc/cell/cell.dart @@ -7,25 +7,53 @@ import 'package:ton_dart/src/boc/serialization/serialization/serialization.dart' import 'package:ton_dart/src/boc/serialization/utils/utils.dart'; import 'package:ton_dart/src/boc/exception/exception.dart'; import 'package:ton_dart/src/boc/cell/slice.dart'; -import 'package:ton_dart/src/utils/math.dart'; -import 'package:ton_dart/src/utils/utils.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/base64.dart'; import 'cell_type.dart'; +/// The `Cell` class represents a cell in the data structure, including its type, bit data, +/// references to other cells, and various metadata such as hashes and depths. +/// +/// Cells can be ordinary or exotic, with exotic cells having additional characteristics +/// that are resolved using utility methods. class Cell { + /// Represents an empty cell with default values. static final Cell empty = Cell(); + + /// The type of the cell, indicating its kind or usage. final CellType type; + + /// The bit string data contained within the cell. final BitString bits; + + /// List of cells that this cell references. final List refs; + + /// The mask that represents the cell's level in a hierarchy. final LevelMask mask; + + /// Internal list of hashes used for cell identification and verification. final List> _hashes; + + /// Internal list of depths corresponding to each level of the cell. final List _depths; + + /// Returns `true` if the cell is exotic (i.e., not ordinary). bool get isExotic => type != CellType.ordinary; + + /// Returns the level of the cell as defined by its mask. int get level => mask.level; + /// Creates a list of `Cell` instances from a binary object code (BOC). + /// + /// [src] The source BOC data. static List fromBoc(List src) { return BocSerialization.deserialize(src); } + /// Creates a `Cell` instance from a byte array. + /// + /// [src] The byte data representing the cell. factory Cell.fromBytes(List src) { final parsed = Cell.fromBoc(src); if (parsed.length != 1) { @@ -35,14 +63,28 @@ class Cell { return parsed[0]; } + /// Creates a `Cell` instance from a Base64 encoded string. + /// + /// [src] The Base64 encoded string representing the cell. factory Cell.fromBase64(String src) { return Cell.fromBytes(Base64Utils.decodeBase64(src)); } + /// Creates a `Cell` instance from a hexadecimal string. + /// + /// [src] The hexadecimal string representing the cell. factory Cell.fromHex(String src) { return Cell.fromBytes(BytesUtils.fromHexString(src)); } + /// Private constructor to create a `Cell` with specific attributes. + /// + /// [type] The type of the cell. + /// [bits] The bit data of the cell. + /// [refs] The references to other cells. + /// [mask] The level mask of the cell. + /// [hashes] The list of hashes associated with the cell. + /// [depths] The list of depths corresponding to each level. Cell._( {required this.type, required this.bits, @@ -55,6 +97,11 @@ class Cell { _depths = List.unmodifiable(depths), refs = List.unmodifiable(refs); + /// Factory constructor to create a `Cell` instance with optional exotic properties. + /// + /// [exotic] Indicates if the cell is exotic. + /// [bits] The bit data of the cell. + /// [refs] The references to other cells. factory Cell( {bool exotic = false, BitString bits = BitString.empty, @@ -64,12 +111,9 @@ class Cell { LevelMask mask; CellType type = CellType.ordinary; if (exotic) { - // Resolve exotic cell final resolved = CellUtils.resolveExotic(bits, refs); type = resolved.type; - // Perform wonders final wonders = CellUtils.wonderCalculator(type, bits, refs); - // Copy results mask = wonders.mask; depths = wonders.depths; hashes = wonders.hashes; @@ -98,6 +142,11 @@ class Cell { depths: depths); } + /// Begins parsing the cell and returns a `Slice` for reading its contents. + /// + /// [allowExotic] If `false`, parsing will fail for exotic cells. + /// + /// Returns a `Slice` object to read the cell's bit data and references. Slice beginParse({bool allowExotic = false}) { if (isExotic && !allowExotic) { throw BocException("Exotic cells cannot be parsed"); @@ -105,30 +154,61 @@ class Cell { return Slice(BitReader(bits), refs); } + /// Retrieves the hash for the cell at the specified [level]. + /// + /// [level] The level for which the hash is requested (default is 3). + /// + /// Returns a list of integers representing the hash. List hash({int level = 3}) { return _hashes[MathUtils.min(_hashes.length - 1, level)]; } + /// Retrieves the depth for the cell at the specified [level]. + /// + /// [level] The level for which the depth is requested (default is 3). + /// + /// Returns the depth as an integer. int depth({int level = 3}) { return _depths[MathUtils.min(_depths.length - 1, level)]; } + /// Serializes the cell to a binary object code (BOC). + /// + /// [idx] If `true`, includes an index in the serialization. + /// [crc32] If `true`, includes a CRC32 checksum in the serialization. + /// + /// Returns a list of integers representing the serialized BOC. List toBoc({bool idx = false, bool crc32 = true}) { return BocSerialization.serialize(root: this, idx: idx, crc32: crc32); } + /// Serializes the cell to a Base64 encoded string. + /// + /// [idx] If `true`, includes an index in the serialization. + /// [crc32] If `true`, includes a CRC32 checksum in the serialization. + /// [urlsafe] If `true`, uses URL-safe encoding. + /// + /// Returns a Base64 encoded string representing the serialized BOC. String toBase64({bool idx = false, bool crc32 = true, bool urlsafe = false}) { - final encode = - BocSerialization.serialize(root: this, idx: idx, crc32: crc32); - return Base64Utils.encodeBase64(encode, urlSafe: urlsafe); + return Base64Utils.encodeBase64(toBoc(idx: idx, crc32: crc32), + urlSafe: urlsafe); } + /// Serializes the cell to a hexadecimal string. + /// + /// [idx] If `true`, includes an index in the serialization. + /// [crc32] If `true`, includes a CRC32 checksum in the serialization. + /// + /// Returns a hexadecimal string representing the serialized BOC. String toHex({bool idx = false, bool crc32 = true}) { - final encode = - BocSerialization.serialize(root: this, idx: idx, crc32: crc32); - return BytesUtils.toHexString(encode); + return BytesUtils.toHexString(toBoc(idx: idx, crc32: crc32)); } + /// Returns a string representation of the cell, including its type and bit data. + /// + /// [indent] A string used for indentation to format the output. + /// + /// Returns a formatted string representation of the cell and its references. @override String toString({String indent = ""}) { String id = indent; @@ -149,6 +229,9 @@ class Cell { return s; } + /// Converts the cell to a `Slice` object for parsing. + /// + /// Returns a `Slice` object that starts parsing the cell's contents. Slice asSlice() { return beginParse(); } diff --git a/lib/src/boc/cell/cell_type.dart b/lib/src/boc/cell/cell_type.dart index 54dadbf..a2013ca 100644 --- a/lib/src/boc/cell/cell_type.dart +++ b/lib/src/boc/cell/cell_type.dart @@ -1,13 +1,31 @@ +/// The `CellType` class represents different types of cells with a unique tag and name. +/// This is used to define the types of cells in a system such as Merkle proofs, pruned branches, etc. class CellType { + /// The name of the cell type. final String name; + + /// The unique tag that identifies the cell type. final int tag; + /// Private constructor to create a `CellType` with a specific [tag] and [name]. const CellType._(this.tag, this.name); + + /// Represents an ordinary cell type with a tag of `-1`. static const CellType ordinary = CellType._(-1, "Ordinary"); + + /// Represents a pruned branch cell type with a tag of `1`. static const CellType prunedBranch = CellType._(1, "PrunedBranch"); + + /// Represents a library cell type with a tag of `2`. static const CellType library = CellType._(2, "Library"); + + /// Represents a Merkle proof cell type with a tag of `3`. static const CellType merkleProof = CellType._(3, "MerkleProof"); + + /// Represents a Merkle update cell type with a tag of `4`. static const CellType merkleUpdate = CellType._(4, "MerkleUpdate"); + + /// A list containing all available cell types. static const List values = [ ordinary, prunedBranch, @@ -16,6 +34,8 @@ class CellType { merkleUpdate ]; + /// Returns the corresponding `CellType` based on the provided [tag]. + /// If no matching tag is found, returns `null`. static CellType? fromValue(int? tag) { try { return values.firstWhere((element) => element.tag == tag); @@ -24,6 +44,7 @@ class CellType { } } + /// Returns a string representation of the `CellType`. @override String toString() { return "CellType.$name"; diff --git a/lib/src/boc/cell/slice.dart b/lib/src/boc/cell/slice.dart index df7a309..18bab37 100644 --- a/lib/src/boc/cell/slice.dart +++ b/lib/src/boc/cell/slice.dart @@ -9,51 +9,79 @@ import 'package:ton_dart/src/dict/dictionary/key.dart'; import 'package:ton_dart/src/dict/dictionary/value.dart'; import 'cell.dart'; +/// The `Slice` class provides methods to read and manipulate a portion of data from a bit stream. +/// It handles the extraction of various data types and references from a binary object code (BOC) slice. class Slice { final BitReader _reader; final List _refs; int _refsOffset = 0; + /// Creates a new `Slice` with a bit reader and a list of cell references. + /// + /// [reader] The bit reader to read bits from. + /// [refs] The list of cell references. Slice(BitReader reader, List refs) : _reader = reader.clone(), _refs = List.unmodifiable(refs); + /// Returns the number of remaining bits in the slice. int get remainingBits { return _reader.remaining; } + /// Returns the current offset of bits read. int get offsetBits { return _reader.offset; } + /// Returns the number of remaining cell references in the slice. int get remainingRefs { return _refs.length - _refsOffset; } + /// Returns the current offset of cell references read. int get offsetRefs { return _refsOffset; } + /// Alias for the bit reader's offset. int get readerOffset => _reader.offset; + + /// Alias for the bit reader's remaining bits. int get readerremaining => _reader.remaining; + /// Skips a specified number of bits in the slice. + /// + /// [bits] The number of bits to skip. + /// + /// Returns the updated `Slice` instance. Slice skip(int bits) { _reader.skip(bits); return this; } + /// Loads a single bit from the bit stream. + /// + /// Returns `true` if the bit is 1, `false` if it is 0. bool loadBit() { return _reader.loadBit(); } + /// Preloads a single bit from the bit stream without advancing the reader. + /// + /// Returns `true` if the bit is 1, `false` if it is 0. bool preloadBit() { return _reader.preloadBit(); } + /// Loads a boolean value from the bit stream (same as `loadBit`). bool loadBoolean() { return loadBit(); } + /// Loads a boolean value or returns `null` if the bit indicating presence is 0. + /// + /// Returns a boolean value or `null`. bool? loadMaybeBoolean() { if (loadBit()) { return loadBoolean(); @@ -62,46 +90,131 @@ class Slice { } } + /// Loads a specified number of bits from the bit stream. + /// + /// [bits] The number of bits to load. + /// + /// Returns a `BitString` representing the loaded bits. BitString loadBits(int bits) { return _reader.loadBits(bits); } + /// Preloads a specified number of bits from the bit stream without advancing the reader. + /// + /// [bits] The number of bits to preload. + /// + /// Returns a `BitString` representing the preloaded bits. BitString preloadBits(int bits) { return _reader.preloadBits(bits); } + /// Loads an unsigned integer of a specified bit length from the bit stream. + /// + /// [bits] The number of bits for the unsigned integer. + /// + /// Returns the loaded unsigned integer. int loadUint(int bits) { return _reader.loadUint(bits); } + /// Loads an 8-bit unsigned integer from the bit stream. int loadUint8() { return _reader.loadUint(8); } + /// Loads a 4-bit unsigned integer from the bit stream. + int loadUint4() { + return _reader.loadUint(4); + } + + /// Attempts to load an 8-bit unsigned integer and returns `null` if it fails. + int? tryLoadUint8() { + try { + return _reader.loadUint(8); + } on BocException { + return null; + } + } + + /// Loads a big unsigned integer of a specified bit length from the bit stream. + /// + /// [bits] The number of bits for the big unsigned integer. + /// + /// Returns the loaded big unsigned integer. BigInt loadUintBig(int bits) { return _reader.loadUintBig(bits); } + /// Loads a 64-bit big unsigned integer from the bit stream. BigInt loadUint64() { return _reader.loadUintBig(64); } + /// Loads a 256-bit big unsigned integer from the bit stream. + BigInt loadUint256() { + return _reader.loadUintBig(256); + } + + /// Loads a 32-bit unsigned integer from the bit stream. int loadUint32() { return _reader.loadUint(32); } + /// Attempts to load a 32-bit unsigned integer and returns `null` if it fails. + int? tryLoadUint32() { + try { + return _reader.loadUint(32); + } on BocException { + return null; + } + } + + /// Attempts to preload a 8-bit unsigned integer and returns `null` if it fails. + int? tryPreLoadUint8() { + try { + return _reader.preloadUint(8); + } on BocException { + return null; + } + } + + /// Attempts to preload a 32-bit unsigned integer and returns `null` if it fails. + int? tryPreloadUint32() { + try { + return _reader.preloadUint(32); + } on BocException { + return null; + } + } + + /// Loads a 16-bit unsigned integer from the bit stream. int loadUint16() { return _reader.loadUint(16); } + /// Preloads an unsigned integer of a specified bit length from the bit stream without advancing the reader. + /// + /// [bits] The number of bits for the unsigned integer. + /// + /// Returns the preloaded unsigned integer. int preloadUint(int bits) { return _reader.preloadUint(bits); } + /// Preloads a big unsigned integer of a specified bit length from the bit stream without advancing the reader. + /// + /// [bits] The number of bits for the big unsigned integer. + /// + /// Returns the preloaded big unsigned integer. BigInt preloadUintBig(int bits) { return _reader.preloadUintBig(bits); } + /// Loads an unsigned integer of a specified bit length if a preceding bit indicates its presence. + /// + /// [bits] The number of bits for the unsigned integer. + /// + /// Returns the loaded unsigned integer or `null`. int? loadMaybeUint(int bits) { if (loadBit()) { return loadUint(bits); @@ -110,6 +223,11 @@ class Slice { } } + /// Loads a big unsigned integer of a specified bit length if a preceding bit indicates its presence. + /// + /// [bits] The number of bits for the big unsigned integer. + /// + /// Returns the loaded big unsigned integer or `null`. BigInt? loadMaybeUintBig(int bits) { if (loadBit()) { return loadUintBig(bits); @@ -118,22 +236,47 @@ class Slice { } } + /// Loads a signed integer of a specified bit length from the bit stream. + /// + /// [bits] The number of bits for the signed integer. + /// + /// Returns the loaded signed integer. int loadInt(int bits) { return _reader.loadInt(bits); } + /// Loads a big signed integer of a specified bit length from the bit stream. + /// + /// [bits] The number of bits for the big signed integer. + /// + /// Returns the loaded big signed integer. BigInt loadIntBig(int bits) { return _reader.loadIntBig(bits); } + /// Preloads a signed integer of a specified bit length from the bit stream without advancing the reader. + /// + /// [bits] The number of bits for the signed integer. + /// + /// Returns the preloaded signed integer. int preloadInt(int bits) { return _reader.preloadInt(bits); } + /// Preloads a big signed integer of a specified bit length from the bit stream without advancing the reader. + /// + /// [bits] The number of bits for the big signed integer. + /// + /// Returns the preloaded big signed integer. BigInt preloadIntBig(int bits) { return _reader.preloadIntBig(bits); } + /// Loads a signed integer of a specified bit length if a preceding bit indicates its presence. + /// + /// [bits] The number of bits for the signed integer. + /// + /// Returns the loaded signed integer or `null`. int? loadMaybeInt(int bits) { if (loadBit()) { return loadInt(bits); @@ -142,6 +285,11 @@ class Slice { } } + /// Loads a big signed integer of a specified bit length if a preceding bit indicates its presence. + /// + /// [bits] The number of bits for the big signed integer. + /// + /// Returns the loaded big signed integer or `null`. BigInt? loadMaybeIntBig(int bits) { if (loadBit()) { return loadIntBig(bits); @@ -150,46 +298,73 @@ class Slice { } } + /// Loads and returns a variable-length unsigned integer from the slice. + /// + /// The number of bits determines the length of the integer. int loadVarUint(int bits) { return _reader.loadVarUint(bits); } + /// Loads and returns a variable-length unsigned integer as a `BigInt` from the slice. + /// + /// The number of bits determines the length of the integer. BigInt loadVarUintBig(int bits) { return _reader.loadVarUintBig(bits); } + /// Peeks at a variable-length unsigned integer from the slice without advancing the reader. + /// + /// The number of bits determines the length of the integer. int preloadVarUint(int bits) { return _reader.preloadVarUint(bits); } + /// Peeks at a variable-length unsigned integer as a `BigInt` from the slice without advancing the reader. + /// + /// The number of bits determines the length of the integer. BigInt preloadVarUintBig(int bits) { return _reader.preloadVarUintBig(bits); } + /// Loads and returns a variable-length signed integer from the slice. + /// + /// The number of bits determines the length of the integer. int loadVarInt(int bits) { return _reader.loadVarInt(bits); } + /// Loads and returns a variable-length signed integer as a `BigInt` from the slice. + /// + /// The number of bits determines the length of the integer. BigInt loadVarIntBig(int bits) { return _reader.loadVarIntBig(bits); } + /// Peeks at a variable-length signed integer from the slice without advancing the reader. + /// + /// The number of bits determines the length of the integer. int preloadVarInt(int bits) { return _reader.preloadVarInt(bits); } + /// Peeks at a variable-length signed integer as a `BigInt` from the slice without advancing the reader. + /// + /// The number of bits determines the length of the integer. BigInt preloadVarIntBig(int bits) { return _reader.preloadVarIntBig(bits); } + /// Loads and returns the coin value from the slice as a `BigInt`. BigInt loadCoins() { return _reader.loadCoins(); } + /// Peeks at the coin value from the slice as a `BigInt` without advancing the reader. BigInt preloadCoins() { return _reader.preloadCoins(); } + /// Loads the coin value if the next bit is set; otherwise, returns `null`. BigInt? loadMaybeCoins() { if (loadBit()) { return loadCoins(); @@ -198,26 +373,34 @@ class Slice { } } + /// Loads and returns a `TonAddress` from the slice. TonAddress loadAddress() { return _reader.loadAddress(); } + /// Loads a `TonAddress` if present; otherwise, returns `null`. TonAddress? loadMaybeAddress() { return _reader.loadMaybeAddress(); } + /// Loads and returns an `ExternalAddress` from the slice. ExternalAddress loadExternalAddress() { return _reader.loadExternalAddress(); } + /// Loads an `ExternalAddress` if present; otherwise, returns `null`. ExternalAddress? loadMaybeExternalAddress() { return _reader.loadMaybeExternalAddress(); } + /// Loads and returns a `TonBaseAddress` if present; otherwise, returns `null`. TonBaseAddress? loadAddressAny() { return _reader.loadAddressAny(); } + /// Loads and returns a `Cell` reference from the slice. + /// + /// Throws a `BocException` if there are no more references available. Cell loadRef() { if (_refsOffset >= _refs.length) { throw BocException('No more references'); @@ -225,6 +408,9 @@ class Slice { return _refs[_refsOffset++]; } + /// Peeks at and returns a `Cell` reference from the slice without advancing the reader. + /// + /// Throws a `BocException` if there are no more references available. Cell preloadRef() { if (_refsOffset >= _refs.length) { throw BocException('No more references'); @@ -232,6 +418,7 @@ class Slice { return _refs[_refsOffset]; } + /// Loads a `Cell` reference if the next bit is set; otherwise, returns `null`. Cell? loadMaybeRef() { if (loadBit()) { return loadRef(); @@ -240,6 +427,20 @@ class Slice { } } + /// Attempts to load a `Cell` reference if the next bit is set; returns `null` on failure. + Cell? tryLoadRef() { + try { + if (loadBit()) { + return loadRef(); + } else { + return null; + } + } on BocException { + return null; + } + } + + /// Attempts to peek at a `Cell` reference if the next bit is set; returns `null` on failure. Cell? preloadMaybeRef() { if (preloadBit()) { return preloadRef(); @@ -248,18 +449,27 @@ class Slice { } } + /// Loads and returns a buffer of the specified size (in bytes) from the slice. List loadBuffer(int bytes) { return _reader.loadBuffer(bytes); } + /// Peeks at a buffer of the specified size (in bytes) from the slice without advancing the reader. List preloadBuffer(int bytes) { return _reader.preloadBuffer(bytes); } + /// Loads and returns the remaining string from the slice. String loadStringTail() { return BocUtils.readString(this); } + /// Loads and returns the remaining buffer from the slice. + List loadBufferTail() { + return BocUtils.readBuffer(this); + } + + /// Loads and returns the remaining string if the next bit is set; otherwise, returns `null`. String? loadMaybeStringTail() { if (loadBit()) { return BocUtils.readString(this); @@ -268,10 +478,12 @@ class Slice { } } + /// Loads and returns the remaining string from a referenced `Cell`. String loadStringRefTail() { return BocUtils.readString(loadRef().beginParse()); } + /// Loads and returns the remaining string from a referenced `Cell` if present; otherwise, returns `null`. String? loadMaybeStringRefTail() { final ref = loadMaybeRef(); if (ref != null) { @@ -281,30 +493,40 @@ class Slice { } } + /// Loads and returns a dictionary from the slice using the provided key and value serializers. Dictionary loadDict( DictionaryKey key, DictionaryValue value) { - return Dictionary.load(key, value, this); + return Dictionary.load(key, value, this); } + /// Loads and returns a dictionary directly from the slice using the provided key and value serializers. Dictionary loadDictDirect( DictionaryKey key, DictionaryValue value) { - return Dictionary.loadDirect(key, value, this); + return Dictionary.loadDirect(key: key, value: value, slice: this); } + /// Checks if the slice is fully parsed (i.e., all bits and references have been consumed). + /// + /// Throws a `BocException` if there are remaining bits or references. void endParse() { if (remainingBits > 0 || remainingRefs > 0) { throw BocException('Slice is not empty'); } } + /// Converts the slice into a `Cell`. Cell asCell() { return beginCell().storeSlice(this).endCell(); } + /// Converts the slice into a `Builder` for further manipulation. Builder asBuilder() { return beginCell().storeSlice(this); } + /// Creates a new `Slice` instance by cloning the current slice. + /// + /// If `fromStart` is `true`, the reader is reset to the beginning. Slice clone({bool fromStart = false}) { if (fromStart) { final reader = _reader.clone(); diff --git a/lib/src/boc/exception/exception.dart b/lib/src/boc/exception/exception.dart index 91d73ec..e693af5 100644 --- a/lib/src/boc/exception/exception.dart +++ b/lib/src/boc/exception/exception.dart @@ -1,6 +1,14 @@ import 'package:ton_dart/src/exception/exception.dart'; +/// Exception thrown for errors related to the Binary Object Container (BOC) operations. +/// +/// This class extends `TonDartPluginException` to handle exceptions specific to BOC parsing +/// and manipulation. It allows for custom error messages and additional details. class BocException extends TonDartPluginException { + /// Creates a new instance of `BocException`. + /// + /// [message] is the error message describing the exception. + /// [details] is an optional map of additional details to include with the exception. BocException(String message, {Map? details}) : super(message, details: details); } diff --git a/lib/src/boc/serialization/models/level_mask.dart b/lib/src/boc/serialization/models/level_mask.dart index 8887330..a6e8ef5 100644 --- a/lib/src/boc/serialization/models/level_mask.dart +++ b/lib/src/boc/serialization/models/level_mask.dart @@ -1,4 +1,4 @@ -import 'package:ton_dart/src/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; class _LevelMaskUtils { static int countSetBits(int n) { diff --git a/lib/src/boc/serialization/serialization/serialization.dart b/lib/src/boc/serialization/serialization/serialization.dart index f556e93..0dad5da 100644 --- a/lib/src/boc/serialization/serialization/serialization.dart +++ b/lib/src/boc/serialization/serialization/serialization.dart @@ -6,7 +6,7 @@ import 'package:ton_dart/src/boc/cell/cell.dart'; import 'package:ton_dart/src/boc/serialization/utils/utils.dart'; import 'package:ton_dart/src/boc/exception/exception.dart'; import 'package:ton_dart/src/boc/utils/utils.dart'; -import 'package:ton_dart/src/utils/crypto.dart'; +import 'package:ton_dart/src/utils/utils/crypto.dart'; class _ParseBocResult { final int size; diff --git a/lib/src/boc/serialization/utils/utils.dart b/lib/src/boc/serialization/utils/utils.dart index cbf37d3..82b765f 100644 --- a/lib/src/boc/serialization/utils/utils.dart +++ b/lib/src/boc/serialization/utils/utils.dart @@ -12,7 +12,7 @@ import 'package:ton_dart/src/boc/serialization/models/topological_sort.dart'; import 'package:ton_dart/src/boc/cell/cell_type.dart'; import 'package:ton_dart/src/boc/exception/exception.dart'; import 'package:ton_dart/src/boc/utils/utils.dart'; -import 'package:ton_dart/src/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; class CellUtils { static List topologicalSort(Cell src) { diff --git a/lib/src/boc/utils/utils.dart b/lib/src/boc/utils/utils.dart index 8e33c37..21ff59c 100644 --- a/lib/src/boc/utils/utils.dart +++ b/lib/src/boc/utils/utils.dart @@ -5,7 +5,7 @@ import 'package:ton_dart/src/boc/bit/builder.dart'; import 'package:ton_dart/src/boc/cell/cell.dart'; import 'package:ton_dart/src/boc/exception/exception.dart'; import 'package:ton_dart/src/boc/cell/slice.dart'; -import 'package:ton_dart/src/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; class BocUtils { static BitBuilder bitsToPaddedBuffer(BitString bits) { diff --git a/lib/src/contracts/contracts.dart b/lib/src/contracts/contracts.dart index 8331f7c..3e51daf 100644 --- a/lib/src/contracts/contracts.dart +++ b/lib/src/contracts/contracts.dart @@ -1,5 +1,5 @@ -export 'wallet/wallet.dart'; -export 'highload/highload.dart'; export 'token/token.dart'; export 'utils/transaction_utils.dart'; -export 'models/run_method_response.dart'; +export 'wallet_contracts/provider/models/run_method_response.dart'; +export 'core/core.dart'; +export 'wallet_contracts/wallet_contracts.dart'; diff --git a/lib/src/contracts/core/contract.dart b/lib/src/contracts/core/contract.dart deleted file mode 100644 index ff45d0f..0000000 --- a/lib/src/contracts/core/contract.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; - -import 'provider.dart'; - -abstract class TonWallets { - abstract final TonAddress address; - int get workChain => address.workChain; - abstract final int? subWalletId; - const TonWallets(); - - @override - String toString() { - return address.toString(); - } -} - -abstract class TonContract extends TonWallets with ContractProvider { - Cell code(int workchain); - Cell data(T params, int workchain); - abstract final StateInit? state; - const TonContract(); -} diff --git a/lib/src/contracts/core/core.dart b/lib/src/contracts/core/core.dart new file mode 100644 index 0000000..085b190 --- /dev/null +++ b/lib/src/contracts/core/core.dart @@ -0,0 +1,5 @@ +export 'core/chain.dart'; +export 'core/contract.dart'; +export 'core/state.dart'; +export 'core/provider.dart'; +export 'core/transfer_params.dart'; diff --git a/lib/src/contracts/core/core/chain.dart b/lib/src/contracts/core/core/chain.dart new file mode 100644 index 0000000..41db2aa --- /dev/null +++ b/lib/src/contracts/core/core/chain.dart @@ -0,0 +1,28 @@ +import 'package:ton_dart/src/contracts/exception/exception.dart'; + +/// Represents a TON (The Open Network) blockchain network and its associated parameters. +/// +/// This class is used to define and work with different TON chains, such as the mainnet and testnet. +class TonChain { + final int workchain; + final int id; + const TonChain._(this.workchain, this.id); + static const TonChain mainnet = TonChain._(0, -239); + static const TonChain testnet = TonChain._(-1, -3); + static const List values = [mainnet, testnet]; + static TonChain fromWorkchain(int? workchain) { + return values.firstWhere( + (e) => e.workchain == workchain, + orElse: () => throw const TonContractException("Invalid workchain."), + ); + } + + @override + operator ==(other) { + if (other is! TonChain) return false; + return workchain == other.workchain && id == other.id; + } + + @override + int get hashCode => workchain.hashCode ^ id.hashCode; +} diff --git a/lib/src/contracts/core/core/contract.dart b/lib/src/contracts/core/core/contract.dart new file mode 100644 index 0000000..23241a1 --- /dev/null +++ b/lib/src/contracts/core/core/contract.dart @@ -0,0 +1,26 @@ +import 'package:ton_dart/src/address/address/address.dart'; + +import 'provider.dart'; + +/// An abstract base class representing a TON (The Open Network) contract. +/// +/// This class provides a structure for working with TON contracts, including their +/// state and address. Specific contract implementations should extend this class +/// and provide concrete implementations for the contract's state and address. +abstract class TonContract with ContractProvider { + const TonContract(); + + /// The state associated with this contract. + /// + /// This represents the current state of the contract, which may be of type [T]. + /// The state is not guaranteed to be non-null; it can be null depending on the + /// contract's implementation and context. + abstract final T? state; + + /// The address of this contract on the TON blockchain. + /// + /// This is a unique identifier for the contract on the blockchain and is used + /// to interact with the contract, such as sending messages or querying data. + @override + abstract final TonAddress address; +} diff --git a/lib/src/contracts/core/provider.dart b/lib/src/contracts/core/core/provider.dart similarity index 76% rename from lib/src/contracts/core/provider.dart rename to lib/src/contracts/core/core/provider.dart index 74c0cdf..403754d 100644 --- a/lib/src/contracts/core/provider.dart +++ b/lib/src/contracts/core/core/provider.dart @@ -1,14 +1,17 @@ import 'package:blockchain_utils/exception/rpc_error.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/address/address.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/models/account_state.dart'; -import 'package:ton_dart/src/contracts/models/run_method_response.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/provider/models/account_state.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/provider/models/run_method_response.dart'; import 'package:ton_dart/src/helper/ton_helper.dart'; import 'package:ton_dart/src/models/models.dart'; import 'package:ton_dart/src/provider/provider.dart'; -mixin ContractProvider on TonWallets { +mixin ContractProvider { + TonAddress get address; + + /// call contract methods Future getStateStack( {required TonProvider rpc, required String method, @@ -45,6 +48,7 @@ mixin ContractProvider on TonWallets { return response; } + /// static method for get account state (balance, code, data , etc.) static Future getStaticState( {required TonProvider rpc, required TonAddress address}) async { if (rpc.isTonCenter) { @@ -77,25 +81,40 @@ mixin ContractProvider on TonWallets { } } + /// static method for get account state if initialized. otherwise throw exception + static Future getActiveState( + {required TonProvider rpc, required TonAddress address}) async { + final state = await getStaticState(rpc: rpc, address: address); + if (!state.state.isActive) { + throw TonContractExceptionConst.stateIsInactive; + } + return state; + } + + /// get account state (balance, code, data , etc.) Future getState({required TonProvider rpc}) async { - return getStaticState(rpc: rpc, address: address); + final state = await getStaticState(rpc: rpc, address: address); + return state; } Future sendMessage( {required TonProvider rpc, required Message exMessage}) async { final boc = exMessage.serialize(); + if (rpc.isTonCenter) { await rpc.request(TonCenterSendBocReturnHash(boc.toBase64())); } else { await rpc .request(TonApiSendBlockchainMessage(batch: [], boc: boc.toBase64())); } - + // throw Exception("wait please"); return StringUtils.decode(boc.hash(), type: StringEncoding.base64); } + /// check if contract is initialized. Future isActive(TonProvider rpc) async { final state = await getState(rpc: rpc); + return state.state.isActive; } } diff --git a/lib/src/contracts/core/core/state.dart b/lib/src/contracts/core/core/state.dart new file mode 100644 index 0000000..2e98347 --- /dev/null +++ b/lib/src/contracts/core/core/state.dart @@ -0,0 +1,24 @@ +import 'package:ton_dart/src/boc/cell/cell.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +/// An abstract base class representing the state of a contract. +/// +/// This class provides a structure for managing and interacting with the state of a contract. +/// It defines methods for obtaining the initial state and initial data of the contract. +abstract class ContractState { + const ContractState(); + + /// Returns the initial state of the contract. + /// + /// This method provides the initial state configuration required when deploying + /// a contract. The `StateInit` object represents the initialization parameters or + /// settings needed for the contract. + StateInit initialState(); + + /// Returns the initial data associated with the contract. + /// + /// This method provides the initial data that the contract will operate on + /// when it is deployed. The `Cell` object represents a cell of data in the + /// TON blockchain format. + Cell initialData(); +} diff --git a/lib/src/contracts/core/core/transfer_params.dart b/lib/src/contracts/core/core/transfer_params.dart new file mode 100644 index 0000000..bb17bb3 --- /dev/null +++ b/lib/src/contracts/core/core/transfer_params.dart @@ -0,0 +1,10 @@ +import 'package:ton_dart/src/utils/utils/extentions.dart'; +import 'package:ton_dart/ton_dart.dart'; + +/// the interface for wallet contracts for called transfer method from another contract like jetton and nfts +/// [messages] the messages to be transfer from specify wallet +abstract class WalletContractTransferParams { + final List messages; + WalletContractTransferParams({required List messages}) + : messages = messages.immutable; +} diff --git a/lib/src/contracts/exception/exception.dart b/lib/src/contracts/exception/exception.dart index a4b6fef..5063c17 100644 --- a/lib/src/contracts/exception/exception.dart +++ b/lib/src/contracts/exception/exception.dart @@ -1,6 +1,28 @@ import 'package:ton_dart/src/exception/exception.dart'; +class TonContractExceptionConst { + static const TonContractException stateIsInactive = TonContractException( + "Unable to read contract data. The contract is not active."); + static TonContractException unknownBody(String name, + {String? message, String? trace}) => + TonContractException("Unknown $name operation body", + details: {"message": message, "trace": trace} + ..removeWhere((k, v) => v == null)); + static TonContractException invalidJson(String name, + {String? message, String? trace, Map? data}) => + TonContractException("Provided json is not valid for $name", + details: {"message": message, "trace": trace, "data": data} + ..removeWhere((k, v) => v == null)); + static TonContractException invalidOperationId({Object? tag}) => + TonContractException("Unknow or unsupported operation.", + details: {"tag": tag}); + static TonContractException incorrectOperation( + {required String excepted, required String got}) => + TonContractException("Incorrect operation.", + details: {"excepted": excepted, "got": got}); +} + class TonContractException extends TonDartPluginException { - TonContractException(String message, {Map? details}) + const TonContractException(String message, {Map? details}) : super(message, details: details); } diff --git a/lib/src/contracts/highload/contracts/v3.dart b/lib/src/contracts/highload/contracts/v3.dart deleted file mode 100644 index 571354e..0000000 --- a/lib/src/contracts/highload/contracts/v3.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/highload/core/core.dart'; -import 'package:ton_dart/src/contracts/highload/models/v3_account_params.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/highload/constant/constant.dart'; -import 'package:ton_dart/src/contracts/highload/provider/v3.dart'; -import 'package:ton_dart/src/contracts/highload/utils/utils.dart'; - -class HighloadWalletV3 extends HighloadWallets - with HighloadWalletV3ProviderImpl { - @override - final int workChain; - - @override - final StateInit state; - - @override - final TonAddress address; - - @override - final int subWalletId; - - final int timeout; - - const HighloadWalletV3._( - {required this.workChain, - required this.state, - required this.address, - required this.subWalletId, - required this.timeout}); - - factory HighloadWalletV3( - {required int workChain, - required List publicKey, - int? subWalletId, - int timeout = HighloadWalletConst.defaultTimeout}) { - subWalletId ??= HighloadWalletConst.defaultHighLoadSubWallet + workChain; - final state = HighloadWalletUtils.buildV3( - publicKey: publicKey, subWalletId: subWalletId, timeout: timeout); - return HighloadWalletV3._( - state: state, - workChain: workChain, - address: TonAddress.fromState(state: state, workChain: workChain), - subWalletId: subWalletId, - timeout: timeout); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final st2 = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = HighloadWalletUtils.readV3State(st2.data); - final wallet = HighloadWalletV3( - workChain: address.workChain, - publicKey: state.publicKey, - timeout: state.timeout, - subWalletId: state.subWalletId); - if (wallet.address.toRawAddress() != address.toRawAddress()) { - throw TonContractException("Incorrect state address.", details: { - "excepted": wallet.address.toString(), - "address": address.toString(), - "workChain": wallet.address.workChain - }); - } - return wallet; - } - - @override - Cell code(int workchain) { - return HighloadWalletUtils.buildV3Code(); - } - - @override - Cell data(HighloadWalletV3AccountParams params, int workchain) { - return HighloadWalletUtils.buildV3Data( - publicKey: params.publicKey, - subWalletId: params.subWalletId, - timeout: params.timeout); - } - - MessageRelaxed packedAction( - {required List messages, - required BigInt value, - required BigInt queryId, - bool bounce = false}) { - return HighloadWalletUtils.packedAction( - messages: messages, - value: value, - queryId: queryId, - account: address, - bounce: bounce); - } - - static Cell createInternalTransferBody( - {required List acctions, required BigInt queryId}) { - return HighloadWalletUtils.createInternalTransferBody( - acctions: acctions, queryId: queryId); - } - - MessageRelaxed createInternalTransfer( - {required List messages, - required BigInt value, - required BigInt queryId, - bool bounce = false}) { - return HighloadWalletUtils.createInternalTransfer( - messages: messages, value: value, queryId: queryId, account: address); - } - - Message createAndSignExternalMessage( - {required TonPrivateKey signer, - required MessageRelaxed message, - required BigInt queryId, - SendMode mode = SendMode.carryAllRemainingBalance, - int? createAt, - StateInit? initState}) { - return HighloadWalletUtils.createExternalMessage( - signer: signer, - message: message, - queryId: queryId, - subWalletId: subWalletId, - timeout: timeout, - account: address, - createAt: createAt, - mode: mode, - state: initState); - } - - Future sendBatchTransaction( - {required TonPrivateKey signer, - required List messages, - required BigInt queryId, - required TonProvider rpc, - required BigInt value, - SendMode mode = SendMode.carryAllRemainingBalance, - int? createAt}) async { - final active = await isActive(rpc); - final extMessage = createAndSignExternalMessage( - signer: signer, - message: - packedAction(messages: messages, value: value, queryId: queryId), - queryId: queryId, - createAt: createAt, - mode: mode, - initState: active ? null : state); - return sendMessage(rpc: rpc, exMessage: extMessage); - } -} diff --git a/lib/src/contracts/highload/core/core.dart b/lib/src/contracts/highload/core/core.dart deleted file mode 100644 index bf90079..0000000 --- a/lib/src/contracts/highload/core/core.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/highload/models/v3_account_params.dart'; - -abstract class HighloadWallets - extends TonContract { - const HighloadWallets(); -} diff --git a/lib/src/contracts/highload/highload.dart b/lib/src/contracts/highload/highload.dart deleted file mode 100644 index f3cb00a..0000000 --- a/lib/src/contracts/highload/highload.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'constant/constant.dart'; -export 'contracts/v3.dart'; diff --git a/lib/src/contracts/highload/models/v3_account_params.dart b/lib/src/contracts/highload/models/v3_account_params.dart deleted file mode 100644 index 16eea86..0000000 --- a/lib/src/contracts/highload/models/v3_account_params.dart +++ /dev/null @@ -1,9 +0,0 @@ -class HighloadWalletV3AccountParams { - final List publicKey; - final int timeout; - final int subWalletId; - const HighloadWalletV3AccountParams( - {required this.publicKey, - required this.timeout, - required this.subWalletId}); -} diff --git a/lib/src/contracts/highload/utils/utils.dart b/lib/src/contracts/highload/utils/utils.dart deleted file mode 100644 index c2ded32..0000000 --- a/lib/src/contracts/highload/utils/utils.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:blockchain_utils/bip/ecc/bip_ecc.dart'; -import 'package:ton_dart/src/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/contracts/highload/constant/constant.dart'; -import 'package:ton_dart/src/contracts/highload/models/v3_account_params.dart'; -import 'package:ton_dart/src/models/models.dart'; - -class HighloadWalletUtils { - static StateInit buildV3( - {required List publicKey, - required int subWalletId, - required int timeout}) { - final code = buildV3Code(); - final data = buildV3Data( - publicKey: publicKey, subWalletId: subWalletId, timeout: timeout); - return StateInit(code: code, data: data); - } - - static HighloadWalletV3AccountParams readV3State(Cell? state) { - try { - final Slice slice = state!.beginParse(); - return HighloadWalletV3AccountParams( - publicKey: slice.loadBuffer(32), - subWalletId: slice.loadUint(32), - timeout: slice - .skip(1 + 1 + HighloadWalletConst.highLoadTimeStampSize) - .loadUint(HighloadWalletConst.highLoadTimeOutSize)); - } catch (e) { - throw TonContractException("Invalid HighloadWalletV3 account data.", - details: {"state": state}); - } - } - - static Cell buildV3Code() { - return Cell.fromBase64(HighloadWalletConst.hightloadWallet3State); - } - - static Cell buildV3Data({ - required List publicKey, - required int subWalletId, - required int timeout, - }) { - final pubkey = Ed25519PublicKey.fromBytes(publicKey); - return beginCell() - .storeBuffer(pubkey.compressed.sublist(1)) - .storeUint(subWalletId, 32) - .storeUint(0, 1 + 1 + HighloadWalletConst.highLoadTimeStampSize) - .storeUint(timeout, HighloadWalletConst.highLoadTimeOutSize) - .endCell(); - } - - static Cell createInternalTransferBody( - {required List acctions, required BigInt queryId}) { - final Cell actionsCell; - if (acctions.length > 254) { - throw TonContractException( - "Max allowed action count is 254. Use packActions instead."); - } - final actionsBuilder = beginCell(); - final out = OutActionUtils.storeOutList(acctions); - actionsBuilder.storeSlice(out); - actionsCell = actionsBuilder.endCell(); - return beginCell() - .storeUint(HighloadWalletConst.transferOp, 32) - .storeUint(queryId, 64) - .storeRef(actionsCell) - .endCell(); - } - - static MessageRelaxed createInternalTransfer( - {required List messages, - required BigInt value, - required BigInt queryId, - required TonAddress account, - bool? bounce, - bool bounced = false}) { - return MessageRelaxed( - info: CommonMessageInfoRelaxedInternal( - ihrDisabled: true, - bounce: bounce ?? account.isBounceable, - bounced: bounced, - dest: account, - value: CurrencyCollection(coins: value), - ihrFee: BigInt.zero, - forwardFee: BigInt.zero, - createdLt: BigInt.zero, - createdAt: 0, - ), - body: createInternalTransferBody(acctions: messages, queryId: queryId)); - } - - static Message createExternalMessage({ - required TonPrivateKey signer, - required MessageRelaxed message, - required BigInt queryId, - required int subWalletId, - required int timeout, - required TonAddress account, - StateInit? state, - SendMode mode = SendMode.carryAllRemainingBalance, - int? createAt, - }) { - createAt ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) - 10; - final Cell messageCell = beginCell().store(message).endCell(); - final messageInner = beginCell() - .storeUint(subWalletId, 32) - .storeRef(messageCell) - .storeUint(mode.mode, 8) - .storeUint(queryId, 23) - .storeUint(createAt, HighloadWalletConst.highLoadTimeStampSize) - .storeUint(timeout, HighloadWalletConst.highLoadTimeOutSize) - .endCell(); - - final body = beginCell() - .storeBuffer(signer.sign(messageInner.hash())) - .storeRef(messageInner) - .endCell(); - final extMessage = Message( - init: state, - info: - CommonMessageInfoExternalIn(dest: account, importFee: BigInt.zero), - body: body); - return extMessage; - } - - static MessageRelaxed packedAction( - {required List messages, - required BigInt value, - required BigInt queryId, - required TonAddress account, - bool? bounce, - bool bounced = false}) { - List batch = []; - if (messages.length > 254) { - batch = messages.sublist(0, 253); - batch.add(OutActionSendMsg( - mode: value > BigInt.zero - ? SendMode.payGasSeparately - : SendMode.carryAllRemainingBalance, - outMessage: packedAction( - messages: messages.sublist(253), - value: value, - queryId: queryId, - account: account))); - } else { - batch = messages; - } - return createInternalTransfer( - messages: batch, - value: value, - queryId: queryId, - account: account, - bounce: bounce, - bounced: bounced); - } -} diff --git a/lib/src/contracts/token/ft/constants/constant.dart b/lib/src/contracts/token/ft/constants/constant.dart new file mode 100644 index 0000000..3f3ff26 --- /dev/null +++ b/lib/src/contracts/token/ft/constants/constant.dart @@ -0,0 +1,2 @@ +export 'constant/minter.dart'; +export 'constant/wallet.dart'; diff --git a/lib/src/contracts/token/ft/constants/constant/minter.dart b/lib/src/contracts/token/ft/constants/constant/minter.dart new file mode 100644 index 0000000..3451c89 --- /dev/null +++ b/lib/src/contracts/token/ft/constants/constant/minter.dart @@ -0,0 +1,42 @@ +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:ton_dart/src/boc/cell/cell.dart'; + +class JettonMinterConst { + static const String _code = + "b5ee9c7241020e010002a3000114ff00f4a413f4bcf2c80b01020162050202037a600403001faf16f6a2687d007d206a6a183faa9040007dadbcf6a2687d007d206a6a183618fc1400b82a1009aa0a01e428027d012c678b00e78b666491646580897a007a00658064fc80383a6465816503e5ffe4e8400202cc07060093b3f0508806e0a84026a8280790a009f404b19e2c039e2d99924591960225e801e80196019241f200e0e9919605940f97ff93a0ef003191960ab19e2ca009f4042796d625999992e3f60101f5d906380492f81f000e8698180b8d8492f81f07d207d2018fd0018b8eb90fd0018fd001839d4da0001698fe99ff6a2687d007d206a6a18400aa9385d47199a9a9b1b289a6382f97024817d207d006a18106840306b90fd001812881a282178048a502819e428027d012c678b666664f6aa7041083deecbef29385d40804f48ee036373701fa00fa40f82854120670542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007074c8cb02ca07cbffc9d05006c705f2e04aa1034545c85004fa0258cf16ccccc9ed5401fa403020d70b01c300915be30de082102c76b9735270bae30235373723c003e3023502c0040d0b0a0900428e185124c705f2e049d4304300c85004fa0258cf16ccccc9ed54e05f05840ff2f00034335035c705f2e04903fa403059c85004fa0258cf16ccccc9ed5401fe365f03820898968015a015bcf2e04b02fa40d3003095c821cf16c9916de28210d1735400708018c8cb055005cf1624fa0214cb6a13cb1f14cb3f23fa443070ba8e33f828440370542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007074c8cb02ca07cbffc9d0cf16966c227001cb01e2f4000c000ac98040fb00003e8210d53276db708010c8cb055003cf1622fa0212cb6acb1fcb3fc98042fb00e5760164"; + + static const String _testnetWorkChain = + "b5ee9c7241020e010002a3000114ff00f4a413f4bcf2c80b01020162050202037a600403001faf16f6a2687d007d206a6a183faa9040007dadbcf6a2687d007d206a6a183618fc1400b82a1009aa0a01e428027d012c678b00e78b666491646580897a007a00658064fc803fba6465816503e5ffe4e8400202cc07060093b3f0508806e0a84026a8280790a009f404b19e2c039e2d99924591960225e801e80196019241f200fee9919605940f97ff93a0ef003191960ab19e2ca009f4042796d625999992e3f60101f5d906380492f81f000e8698180b8d8492f81f07d207d2018fd0018b8eb90fd0018fd001839d4da0001698fe99ff6a2687d007d206a6a18400aa9385d47199a9a9b1b289a6382f97024817d207d006a18106840306b90fd001812881a282178048a502819e428027d012c678b666664f6aa7041083deecbef29385d40804f48ee036373701fa00fa40f82854120670542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007f74c8cb02ca07cbffc9d05006c705f2e04aa1034545c85004fa0258cf16ccccc9ed5401fa403020d70b01c300915be30de082102c76b9735270bae30235373723c003e3023502c0040d0b0a0900428e185124c705f2e049d4304300c85004fa0258cf16ccccc9ed54e05f05840ff2f00034335035c705f2e04903fa403059c85004fa0258cf16ccccc9ed5401fe365f03820898968015a015bcf2e04b02fa40d3003095c821cf16c9916de28210d1735400708018c8cb055005cf1624fa0214cb6a13cb1f14cb3f23fa44307fba8e33f828440370542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007f74c8cb02ca07cbffc9d0cf16966c227001cb01e2f4000c000ac98040fb00003e8210d53276db708010c8cb055003cf1622fa0212cb6acb1fcb3fc98042fb00fc9823ca"; + + static const String _stableMinter = + "b5ee9c72410218010005bb000114ff00f4a413f4bcf2c80b0102016207020201200603020271050400cfaf16f6a2687d007d207d206a6a68bf99e836c1783872ebdb514d9c97c283b7f0ae5179029e2b6119c39462719e4f46ed8f7413e62c780a417877407e978f01a40711411b1acb773a96bdd93fa83bb5ca8435013c8c4b3ac91f4589b4780a38646583fa0064a180400085adbcf6a2687d007d207d206a6a688a2f827c1400b82a3002098a81e46581ac7d0100e78b00e78b6490e4658089fa00097a00658064fc80383a6465816503e5ffe4e8400025bd9adf6a2687d007d207d206a6a6888122f8240202cb0908001da23864658380e78b64814183fa0bc002f3d0cb434c0c05c6c238ecc200835c874c7c0608405e351466ea44c38601035c87e800c3b51343e803e903e90353534541168504d3214017e809400f3c58073c5b333327b55383e903e900c7e800c7d007e800c7e80004c5c3e0e80b4c7c04074cfc044bb51343e803e903e9035353449a084190adf41eeb8c089a150a03fa82107bdd97deba8ee7363805fa00fa40f82854120a70546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007074c8cb02ca07cbffc9d05008c705f2e04a12a14414506603c85005fa025003cf1601cf16ccccc9ed54fa40d120d70b01c000b3915be30de02682102c76b973bae302352514120b04f882106501f354ba8e223134365145c705f2e04902fa40d1103402c85005fa025003cf1601cf16ccccc9ed54e0258210fb88e119ba8e2132343603d15131c705f2e0498b025512c85005fa025003cf1601cf16ccccc9ed54e034248210235caf52bae30237238210cb862902bae302365b2082102508d66abae3026c310f0e0d0c00188210d372158cbadc840ff2f0001e3002c705f2e049d4d4d101ed54fb040044335142c705f2e049c85003cf16c9134440c85005fa025003cf1601cf16ccccc9ed5402ec3031325033c705f2e049fa40fa00d4d120d0d31f01018040d7212182100f8a7ea5ba8e4d36208210595f07bcba8e2c3004fa0031fa4031f401d120f839206e943081169fde718102f270f8380170f836a0811a7770f836a0bcf2b08e138210eed236d3ba9504d30331d19434f2c048e2e2e30d500370111000c082103b9aca0070fb02f828450470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007074c8cb02ca07cbffc9d0c8801801cb0501cf1658fa02029858775003cb6bcccc9730017158cb6acce2c98011fb0000ce31fa0031fa4031fa4031f401fa0020d70b009ad74bc00101c001b0f2b19130e25442162191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a370f83ca00270f83612a00170f836a07381040982100966018070f837a0bcf2b001fc145f04323401fa40d2000101d195c821cf16c9916de2c8801001cb055004cf1670fa027001cb6a8210d173540001cb1f500401cb3f23fa4430c0008e35f828440470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007074c8cb02ca07cbffc9d012cf1697316c127001cb01e2f400c91300088050fb000044c8801001cb0501cf1670fa027001cb6a8210d53276db01cb1f0101cb3fc98042fb00019635355161c705f2e04904fa4021fa4430c000f2e14dfa00d4d120d0d31f018210178d4519baf2e0488040d721fa00fa4031fa4031fa0020d70b009ad74bc00101c001b0f2b19130e254431b16018e2191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a370f83ca00270f83612a00170f836a07381040982100966018070f837a0bcf2b025597f1700ec82103b9aca0070fb02f828450470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007074c8cb02ca07cbffc9d0c8801801cb0501cf1658fa02029858775003cb6bcccc9730017158cb6acce2c98011fb005005a04314c85005fa025003cf1601cf16ccccc9ed546f6e5bfb"; + + static const String _stableMinterTestnet = + "b5ee9c72410218010005bb000114ff00f4a413f4bcf2c80b0102016207020201200603020271050400cfaf16f6a2687d007d207d206a6a68bf99e836c1783872ebdb514d9c97c283b7f0ae5179029e2b6119c39462719e4f46ed8f7413e62c780a417877407e978f01a40711411b1acb773a96bdd93fa83bb5ca8435013c8c4b3ac91f4589b4780a38646583fa0064a180400085adbcf6a2687d007d207d206a6a688a2f827c1400b82a3002098a81e46581ac7d0100e78b00e78b6490e4658089fa00097a00658064fc803fba6465816503e5ffe4e8400025bd9adf6a2687d007d207d206a6a6888122f8240202cb0908001da23864658380e78b64814183fa0bc002f3d0cb434c0c05c6c238ecc200835c874c7c0608405e351466ea44c38601035c87e800c3b51343e803e903e90353534541168504d3214017e809400f3c58073c5b333327b55383e903e900c7e800c7d007e800c7e80004c5ffe0e80b4c7c04074cfc044bb51343e803e903e9035353449a084190adf41eeb8c089a150a03fa82107bdd97deba8ee7363805fa00fa40f82854120a70546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007f74c8cb02ca07cbffc9d05008c705f2e04a12a14414506603c85005fa025003cf1601cf16ccccc9ed54fa40d120d70b01c000b3915be30de02682102c76b973bae302352514120b04f882106501f354ba8e223134365145c705f2e04902fa40d1103402c85005fa025003cf1601cf16ccccc9ed54e0258210fb88e119ba8e2132343603d15131c705f2e0498b025512c85005fa025003cf1601cf16ccccc9ed54e034248210235caf52bae30237238210cb862902bae302365b2082102508d66abae3026c310f0e0d0c00188210d372158cbadc840ff2f0001e3002c705f2e049d4d4d101ed54fb040044335142c705f2e049c85003cf16c9134440c85005fa025003cf1601cf16ccccc9ed5402ec3031325033c705f2e049fa40fa00d4d120d0d31f01018040d7212182100f8a7ea5ba8e4d36208210595f07bcba8e2c3004fa0031fa4031f401d120f839206e943081169fde718102f27ff838017ff836a0811a777ff836a0bcf2b08e138210eed236d3ba9504d30331d19434f2c048e2e2e30d500370111000c082103b9aca0070fb02f828450470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007f74c8cb02ca07cbffc9d0c8801801cb0501cf1658fa02029858775003cb6bcccc9730017158cb6acce2c98011fb0000ce31fa0031fa4031fa4031f401fa0020d70b009ad74bc00101c001b0f2b19130e25442162191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a37ff83ca0027ff83612a0017ff836a0738104098210096601807ff837a0bcf2b001fc145f04323401fa40d2000101d195c821cf16c9916de2c8801001cb055004cf1670fa027001cb6a8210d173540001cb1f500401cb3f23fa4430c0ff8e35f828440470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007f74c8cb02ca07cbffc9d012cf1697316c127001cb01e2f400c91300088050fb000044c8801001cb0501cf1670fa027001cb6a8210d53276db01cb1f0101cb3fc98042fb00019635355161c705f2e04904fa4021fa4430c0fff2e14dfa00d4d120d0d31f018210178d4519baf2e0488040d721fa00fa4031fa4031fa0020d70b009ad74bc00101c001b0f2b19130e254431b16018e2191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a37ff83ca0027ff83612a0017ff836a0738104098210096601807ff837a0bcf2b025597f1700ec82103b9aca0070fb02f828450470546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007f74c8cb02ca07cbffc9d0c8801801cb0501cf1658fa02029858775003cb6bcccc9730017158cb6acce2c98011fb005005a04314c85005fa025003cf1601cf16ccccc9ed5498da4e52"; + + static const int internalTransferOperation = 0x178d4519; + static const int discoverMessageOperation = 0x2c76b973; + static const int changeAdminOperation = 3; + static const int changeContentOperation = 4; + static const int mintOperation = 21; + + ///stable + static const int deployOperation = 0xd372158c; + static const int stableMintOperation = 0x642b7d07; + static const int stableInternalTranfer = 0x178d4519; + static const int stableProvideWalletAddress = 0x2c76b973; + + static Cell code(int workchain) { + if (workchain < 0) { + return Cell.fromBytes(BytesUtils.fromHexString(_testnetWorkChain)); + } + return Cell.fromBytes(BytesUtils.fromHexString(_code)); + } + + static Cell stableCode(int workchain) { + if (workchain < 0) { + return Cell.fromBytes(BytesUtils.fromHexString(_stableMinterTestnet)); + } + return Cell.fromBytes(BytesUtils.fromHexString(_stableMinter)); + } +} diff --git a/lib/src/contracts/token/ft/constants/constant/wallet.dart b/lib/src/contracts/token/ft/constants/constant/wallet.dart new file mode 100644 index 0000000..eaddbc7 --- /dev/null +++ b/lib/src/contracts/token/ft/constants/constant/wallet.dart @@ -0,0 +1,34 @@ +import 'package:blockchain_utils/utils/utils.dart'; +import 'package:ton_dart/src/boc/boc.dart'; + +class JettonWalletConst { + static const String _code = + "b5ee9c7241021201000334000114ff00f4a413f4bcf2c80b010201620302001ba0f605da89a1f401f481f481a8610202cc0f04020148080502012007060083200835c87b51343e803e903e90350c0134c7e08405e3514654882ea0841ef765f784ee84ac7cb8b174cfcc7e800c04e81408f214013e809633c58073c5b3327b552000db3b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b0a0823938702a8005a805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b55200201200d0903f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401c1d3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a020822625a004ad8228608239387028062849f8c3c975c2c070c008e00c0b0a0076c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54000e10491038375f0400705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb001024102301f100f4cffe803e90087c007b51343e803e903e90350c144da8548ab1c17cb8b04a30bffcb8b0950d109c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c032483e401c1d3232c0b281f2fff274013e903d010c7e800835d270803cb8b11de0063232c1540233c59c3e8085f2dac4f3200e00ae8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08208e4e1c0aa008208989680a0a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed540201d4111000113e910c1c2ebcb8536000c30831c02497c138007434c0c05c6c2544d7c0fc02f83e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0238208405e3514654882ea58c511100fc02780d60841657c1ef2ea4d67c02b817c12103fcbc204479e250"; + + static const String _testnetWorkChain = + "b5ee9c7241021201000334000114ff00f4a413f4bcf2c80b010201620302001ba0f605da89a1f401f481f481a8610202cc0f04020148080502012007060083200835c87b51343e803e903e90350c0134c7e08405e3514654882ea0841ef765f784ee84ac7cb8b174cfcc7e800c04e81408f214013e809633c58073c5b3327b552000db3b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b0a0823938702a8005a805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b55200201200d0903f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401fdd3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a020822625a004ad8228608239387028062849f8c3c975c2c070c008e00c0b0a0076c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54000e10491038375f0400705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb001024102301f100f4cffe803e90087c007b51343e803e903e90350c144da8548ab1c17cb8b04a30bffcb8b0950d109c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c032483e401fdd3232c0b281f2fff274013e903d010c7e800835d270803cb8b11de0063232c1540233c59c3e8085f2dac4f3200e00ae8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08208e4e1c0aa008208989680a0a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed540201d4111000113e910c1feebcb8536000c30831c02497c138007434c0c05c6c2544d7c0fc02f83e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0238208405e3514654882ea58c511100fc02780d60841657c1ef2ea4d67c02b817c12103fcbc207dada3a3"; + + static const String _stableCodeHash = + "b5ee9c7241020f010003d1000114ff00f4a413f4bcf2c80b01020162050202012004030021bc508f6a2686981fd007d207d2068af81c0027bfd8176a2686981fd007d207d206899fc152098402f8d001d0d3030171b08e48135f038020d721ed44d0d303fa00fa40fa40d104d31f01840f218210178d4519ba0282107bdd97deba12b1f2f48040d721fa003012a0401303c8cb0358fa0201cf1601cf16c9ed54e0fa40fa4031fa0031f401fa0031fa00013170f83a02d31f012082100f8a7ea5ba8e85303459db3ce0330c0602d0228210178d4519ba8e84325adb3ce034218210595f07bcba8e843101db3ce032208210eed236d3ba8e2f30018040d721d303d1ed44d0d303fa00fa40fa40d1335142c705f2e04a403303c8cb0358fa0201cf1601cf16c9ed54e06c218210d372158cbadc840ff2f0080701f2ed44d0d303fa00fa40fa40d106d33f0101fa00fa40f401d15141a15288c705f2e04926c2fff2afc882107bdd97de01cb1f5801cb3f01fa0221cf1658cf16c9c8801801cb0526cf1670fa02017158cb6accc903f839206e943081169fde718102f270f8380170f836a0811a7770f836a0bcf2b0028050fb00030903f4ed44d0d303fa00fa40fa40d12372b0c002f26d07d33f0101fa005141a004fa40fa4053bac705f82a5464e070546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007074c8cb02ca07cbffc9d0500cc7051bb1f2e04a09fa0021925f04e30d26d70b01c000b393306c33e30d55020b0a09002003c8cb0358fa0201cf1601cf16c9ed54007a5054a1f82fa07381040982100966018070f837b60972fb02c8801001cb055005cf1670fa027001cb6a8210d53276db01cb1f5801cb3fc9810082fb00590060c882107362d09c01cb1f2501cb3f5004fa0258cf1658cf16c9c8801001cb0524cf1658fa02017158cb6accc98011fb0001f203d33f0101fa00fa4021fa4430c000f2e14ded44d0d303fa00fa40fa40d15309c7052471b0c00021b1f2ad522bc705500ab1f2e0495115a120c2fff2aff82a54259070546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007074c8cb02ca07cbffc9d004fa40f401fa00200d019820d70b009ad74bc00101c001b0f2b19130e2c88210178d451901cb1f500a01cb3f5008fa0223cf1601cf1626fa025007cf16c9c8801801cb055004cf1670fa024063775003cb6bccccc945370e00b42191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a370f83ca00270f83612a00170f836a07381040982100966018070f837a0bcf2b0048050fb005803c8cb0358fa0201cf1601cf16c9ed5401f9319e"; + + static const String _stableTestnetCodeHash = + "b5ee9c7241020f010003d1000114ff00f4a413f4bcf2c80b01020162050202012004030021bc508f6a2686981fd007d207d2068af81c0027bfd8176a2686981fd007d207d206899fc152098402f8d001d0d3030171b08e48135f038020d721ed44d0d303fa00fa40fa40d104d31f01840f218210178d4519ba0282107bdd97deba12b1f2f48040d721fa003012a0401303c8cb0358fa0201cf1601cf16c9ed54e0fa40fa4031fa0031f401fa0031fa0001317ff83a02d31f012082100f8a7ea5ba8e85303459db3ce0330c0602d0228210178d4519ba8e84325adb3ce034218210595f07bcba8e843101db3ce032208210eed236d3ba8e2f30018040d721d303d1ed44d0d303fa00fa40fa40d1335142c705f2e04a403303c8cb0358fa0201cf1601cf16c9ed54e06c218210d372158cbadc840ff2f0080701f2ed44d0d303fa00fa40fa40d106d33f0101fa00fa40f401d15141a15288c705f2e04926c2fff2afc882107bdd97de01cb1f5801cb3f01fa0221cf1658cf16c9c8801801cb0526cf1670fa02017158cb6accc903f839206e943081169fde718102f27ff838017ff836a0811a777ff836a0bcf2b0028050fb00030903f4ed44d0d303fa00fa40fa40d12372b0c002f26d07d33f0101fa005141a004fa40fa4053bac705f82a5464e070546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c9f9007f74c8cb02ca07cbffc9d0500cc7051bb1f2e04a09fa0021925f04e30d26d70b01c000b393306c33e30d55020b0a09002003c8cb0358fa0201cf1601cf16c9ed54007a5054a1f82fa0738104098210096601807ff837b60972fb02c8801001cb055005cf1670fa027001cb6a8210d53276db01cb1f5801cb3fc9810082fb00590060c882107362d09c01cb1f2501cb3f5004fa0258cf1658cf16c9c8801001cb0524cf1658fa02017158cb6accc98011fb0001f203d33f0101fa00fa4021fa4430c0fff2e14ded44d0d303fa00fa40fa40d15309c7052471b0c00021b1f2ad522bc705500ab1f2e0495115a120c2fff2aff82a54259070546004131503c8cb0358fa0201cf1601cf16c921c8cb0113f40012f400cb00c920f9007f74c8cb02ca07cbffc9d004fa40f401fa00200d019820d70b009ad74bc00101c001b0f2b19130e2c88210178d451901cb1f500a01cb3f5008fa0223cf1601cf1626fa025007cf16c9c8801801cb055004cf1670fa024063775003cb6bccccc945370e00b42191729171e2f839206e938124279120e2216e94318128739101e25023a813a0738103a37ff83ca0027ff83612a0017ff836a0738104098210096601807ff837a0bcf2b0048050fb005803c8cb0358fa0201cf1601cf16c9ed547fd62f53"; + static Cell code(int workchain) { + if (workchain < 0) { + return Cell.fromBytes(BytesUtils.fromHexString(_testnetWorkChain)); + } + return Cell.fromBytes(BytesUtils.fromHexString(_code)); + } + + static Cell stableCode(int workchain) { + if (workchain < 0) { + return Cell.fromBytes(BytesUtils.fromHexString(_stableTestnetCodeHash)); + } + return Cell.fromBytes(BytesUtils.fromHexString(_stableCodeHash)); + } + + static const int transfer = 0xf8a7ea5; + static const int burn = 0x595f07bc; + static const int withdrawTon = 0x6d8e5e3c; + static const int withdrawJetton = 0x768a50b2; +} diff --git a/lib/src/contracts/token/ft/contract/contracts.dart b/lib/src/contracts/token/ft/contract/contracts.dart new file mode 100644 index 0000000..7fba2ed --- /dev/null +++ b/lib/src/contracts/token/ft/contract/contracts.dart @@ -0,0 +1,4 @@ +export 'minter/minter.dart'; +export 'minter/stable.dart'; +export 'wallet/jetton_wallet.dart'; +export 'wallet/stable_jetton_wallet.dart'; diff --git a/lib/src/contracts/token/ft/contract/minter/minter.dart b/lib/src/contracts/token/ft/contract/minter/minter.dart new file mode 100644 index 0000000..75bef9e --- /dev/null +++ b/lib/src/contracts/token/ft/contract/minter/minter.dart @@ -0,0 +1,185 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/contract/contracts.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/types.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/provider/provider/provider.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; + +class JettonMinter + extends TonContract with ContractProvider { + final WalletContract owner; + @override + final TonAddress address; + @override + final MinterWalletState? state; + + const JettonMinter({required this.owner, required this.address, this.state}); + factory JettonMinter.create({ + required WalletContract owner, + required MinterWalletState state, + }) { + return JettonMinter( + owner: owner, + address: TonAddress.fromState( + state: state.initialState(), workChain: owner.address.workChain), + state: state); + } + static Future + fromAddress( + {required WalletContract owner, + required TonAddress address, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = MinterWalletState.deserialize(stateData.data!.beginParse()); + return JettonMinter(owner: owner, address: address, state: state); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + OnEstimateFee? onEstimateFee}) async { + final active = await isActive(rpc); + if (!active && state == null) { + throw const TonContractException( + "The account is inactive and requires state initialization."); + } + return await owner.sendTransfer( + params: params, + messages: [ + TransactioUtils.internal( + destination: address, + amount: amount, + initState: active ? null : state!.initialState(), + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ), + ], + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + Future deploy( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + OnEstimateFee? onEstimateFee}) async { + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: body, + bounce: bounce, + bounced: bounced, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + /// Sends a transaction operation. + /// + /// This method facilitates sending a transaction with a specified operation, amount, and other parameters + /// to contract. + /// + /// - `params`: owner wallet specify parameters for transfer. + /// - `rpc`: The RPC provider used to interact with the blockchain. + /// - `amount`: The amount of cryptocurrency to be sent in the transaction. + /// - `operation`: The operation to be executed as part of the transaction, encapsulated in a [JettonMinterOperation] object. + /// - `sendMode`: Specifies how the transaction fees are handled. The default is [SendModeConst.payGasSeparately]. + Future sendOperation({ + required E signerParams, + required TonProvider rpc, + required BigInt amount, + required JettonMinterOperation operation, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + }) { + return _sendTransaction( + params: signerParams, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout); + } + + Future getJettonData(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_jetton_data"); + return MinterWalletState.fromTupple(data.reader()); + } + + Future getWalletAddress( + {required TonProvider rpc, required TonAddress owner}) async { + final data = + await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ + if (rpc.isTonCenter) + ["tvm.Slice", beginCell().storeAddress(owner).endCell().toBase64()] + else + owner.toString() + ]); + return data.reader().readAddress(); + } + + Future> + getJettonWalletContract( + {required TonProvider rpc, + required WalletContract owner}) async { + final data = + await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ + if (rpc.isTonCenter) + [ + "tvm.Slice", + beginCell().storeAddress(owner.address).endCell().toBase64() + ] + else + owner.address.toString() + ]); + return JettonWallet.fromAddress( + address: data.reader().readAddress(), owner: owner, rpc: rpc); + } + + Future totalSupply(TonProvider rpc) async { + final data = await getJettonData(rpc); + return data.totalSupply; + } + + Future adminAddress(TonProvider rpc) async { + final data = await getJettonData(rpc); + return data.owner; + } + + Future getContent(TonProvider rpc) async { + final data = await getJettonData(rpc); + return data.content; + } + + Future getMetadata(TonProvider rpc) async { + final data = await getJettonData(rpc); + return TokneMetadataUtils.loadContent(data.content); + } +} diff --git a/lib/src/contracts/token/ft/contract/minter/stable.dart b/lib/src/contracts/token/ft/contract/minter/stable.dart new file mode 100644 index 0000000..c29fb35 --- /dev/null +++ b/lib/src/contracts/token/ft/contract/minter/stable.dart @@ -0,0 +1,195 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/contract/wallet/stable_jetton_wallet.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/models/stable_minter_data.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/state/stable_minter.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/operations/stable_jetton.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/provider/provider/provider.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant/minter.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; + +class StableJettonMinter + extends TonContract with ContractProvider { + final WalletContract owner; + @override + final TonAddress address; + @override + final StableTokenMinterState? state; + + const StableJettonMinter( + {required this.owner, required this.address, this.state}); + factory StableJettonMinter.create({ + required WalletContract owner, + required StableTokenMinterState state, + }) { + return StableJettonMinter( + owner: owner, + address: TonAddress.fromState( + state: state.initialState(), workChain: owner.address.workChain), + state: state); + } + static Future + fromAddress( + {required WalletContract owner, + required TonAddress address, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + StableTokenMinterState.deserialize(stateData.data!.beginParse()); + + return StableJettonMinter(owner: owner, address: address, state: state); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + StateInit? state, + OnEstimateFee? onEstimateFee}) async { + return await owner.sendTransfer( + params: params, + messages: [ + TransactioUtils.internal( + destination: address, + amount: amount, + initState: state, + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ), + ], + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + Future deploy( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + final active = await isActive(rpc); + if (active) { + throw const TonContractException("Account is already active."); + } + if (state == null) { + throw const TonContractException( + "For deploy minter please use create constructor to build state"); + } + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: beginCell() + .storeUint32(JettonMinterConst.deployOperation) + .storeUint64(BigInt.zero) + .endCell(), + bounce: bounce, + bounced: bounced, + state: state!.initialState(), + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + /// Sends a transaction operation. + /// + /// This method facilitates sending a transaction with a specified operation, amount, and other parameters + /// to contract. + /// + /// - `params`: owner wallet specify parameters for transfer. + /// - `rpc`: The RPC provider used to interact with the blockchain. + /// - `amount`: The amount of cryptocurrency to be sent in the transaction. + /// - `operation`: The operation to be executed as part of the transaction, encapsulated in a [StableJettonMinterOperation] object. + /// - `sendMode`: Specifies how the transaction fees are handled. The default is [SendModeConst.payGasSeparately]. + Future sendOperation( + {required E signerParams, + required TonProvider rpc, + required StableJettonMinterOperation operation, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + return _sendTransaction( + params: signerParams, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future getJettonData(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_jetton_data"); + return StableTokenMinterData.fromTuple(data.reader()); + } + + Future getWalletAddress( + {required TonProvider rpc, required TonAddress owner}) async { + final data = + await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ + if (rpc.isTonCenter) + ["tvm.Slice", beginCell().storeAddress(owner).endCell().toBase64()] + else + owner.toString() + ]); + return data.reader().readAddress(); + } + + Future> + getJettonWalletContract( + {required TonProvider rpc, + required WalletContract owner}) async { + final data = + await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ + if (rpc.isTonCenter) + [ + "tvm.Slice", + beginCell().storeAddress(owner.address).endCell().toBase64() + ] + else + owner.address.toString() + ]); + final address = data.reader().readAddress(); + return StableJettonWallet.fromAddress( + address: address, owner: owner, rpc: rpc); + } + + Future totalSupply(TonProvider rpc) async { + final data = await getJettonData(rpc); + return data.totalSupply; + } + + Future adminAddress(TonProvider rpc) async { + final data = await getJettonData(rpc); + return data.adminAddress; + } + + Future getNextAdminAddress(TonProvider rpc) async { + final data = + await getStateStack(rpc: rpc, method: "get_next_admin_address"); + return data.reader().readAddressOpt(); + } +} diff --git a/lib/src/contracts/token/ft/contract/wallet/jetton_wallet.dart b/lib/src/contracts/token/ft/contract/wallet/jetton_wallet.dart new file mode 100644 index 0000000..da188f1 --- /dev/null +++ b/lib/src/contracts/token/ft/contract/wallet/jetton_wallet.dart @@ -0,0 +1,119 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/operations/jetton.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/state/wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; + +class JettonWallet + extends TonContract { + final WalletContract owner; + @override + final TonAddress address; + @override + final JettonWalletState? state; + + const JettonWallet( + {required this.owner, required this.state, required this.address}); + static Future> + fromAddress( + {required TonAddress address, + required WalletContract owner, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = JettonWalletState.deserialize(stateData.data!.beginParse()); + return JettonWallet(owner: owner, state: state, address: address); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + StateInit? state, + OnEstimateFee? onEstimateFee}) async { + final message = TransactioUtils.internal( + destination: address, + amount: amount, + initState: state, + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable); + return await owner.sendTransfer( + messages: [message], + params: params, + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + /// Sends a transaction operation. + /// + /// This method facilitates sending a transaction with a specified operation, amount, and other parameters + /// to contract. + /// + /// - `params`: owner wallet specify parameters for transfer. + /// - `rpc`: The RPC provider used to interact with the blockchain. + /// - `amount`: The amount of cryptocurrency to be sent in the transaction. + /// - `operation`: The operation to be executed as part of the transaction, encapsulated in a [JettonWalletOperation] object. + /// - `sendMode`: Specifies how the transaction fees are handled. The default is [SendModeConst.payGasSeparately]. + Future sendOperation( + {required E signerParams, + required TonProvider rpc, + required JettonWalletOperation operation, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + return _sendTransaction( + params: signerParams, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future getBalance(TonProvider rpc) async { + final data = await getWalletData(rpc); + return data.balance; + } + + Future getWalletData(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_wallet_data"); + return JettonWalletState.fromTuple(data.reader()); + } + + Future getWalletAddress( + {required TonProvider rpc, required TonAddress owner}) async { + final data = + await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ + if (rpc.isTonCenter) + ["tvm.Slice", beginCell().storeAddress(owner).endCell().toBase64()] + else + owner.toString() + ]); + return data.reader().readAddress(); + } + + Future readState(TonProvider rpc) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + return JettonWalletState.deserialize(stateData.data!.beginParse()); + } +} diff --git a/lib/src/contracts/token/ft/contract/wallet/stable_jetton_wallet.dart b/lib/src/contracts/token/ft/contract/wallet/stable_jetton_wallet.dart new file mode 100644 index 0000000..eaabc80 --- /dev/null +++ b/lib/src/contracts/token/ft/contract/wallet/stable_jetton_wallet.dart @@ -0,0 +1,107 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/operations/stable_jetton.dart'; +import 'package:ton_dart/src/contracts/token/ft/types/state/stable_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; + +class StableJettonWallet + extends TonContract { + final WalletContract owner; + @override + final TonAddress address; + @override + final StableJettonWalletState? state; + + const StableJettonWallet( + {required this.owner, required this.state, required this.address}); + static Future> + fromAddress( + {required TonAddress address, + required WalletContract owner, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + StableJettonWalletState.deserialize(stateData.data!.beginParse()); + return StableJettonWallet(owner: owner, state: state, address: address); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + StateInit? state, + OnEstimateFee? onEstimateFee}) async { + final message = TransactioUtils.internal( + destination: address, + amount: amount, + initState: state, + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable); + return await owner.sendTransfer( + messages: [message], + params: params, + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + /// Sends a transaction operation. + /// + /// This method facilitates sending a transaction with a specified operation, amount, and other parameters + /// to contract. + /// + /// - `params`: owner wallet specify parameters for transfer. + /// - `rpc`: The RPC provider used to interact with the blockchain. + /// - `amount`: The amount of cryptocurrency to be sent in the transaction. + /// - `operation`: The operation to be executed as part of the transaction, encapsulated in a [StableJettonWalletOperation] object. + /// - `sendMode`: Specifies how the transaction fees are handled. The default is [SendModeConst.payGasSeparately]. + Future sendOperation( + {required E signerParams, + required TonProvider rpc, + required StableJettonWalletOperation operation, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + return _sendTransaction( + params: signerParams, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future getBalance(TonProvider rpc) async { + final data = await getWalletData(rpc); + return data.balance; + } + + Future getWalletData(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_wallet_data"); + return StableJettonWalletState.fromTuple(data.reader()); + } + + Future getStatus(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_status"); + return StableTokenWalletStatus.fromTag(data.reader().readNumber()); + } +} diff --git a/lib/src/contracts/token/ft/ft.dart b/lib/src/contracts/token/ft/ft.dart index 76c5c94..0d40462 100644 --- a/lib/src/contracts/token/ft/ft.dart +++ b/lib/src/contracts/token/ft/ft.dart @@ -1,2 +1,3 @@ -export 'minter/minter.dart'; -export 'wallet/wallet.dart'; +export 'constants/constant.dart'; +export 'contract/contracts.dart'; +export 'types/types.dart'; diff --git a/lib/src/contracts/token/ft/minter/constant/constant.dart b/lib/src/contracts/token/ft/minter/constant/constant.dart deleted file mode 100644 index e221d01..0000000 --- a/lib/src/contracts/token/ft/minter/constant/constant.dart +++ /dev/null @@ -1,14 +0,0 @@ -class JettonMinterConst { - /// wc 0 - static const String code = - "b5ee9c7241020e010002a3000114ff00f4a413f4bcf2c80b01020162050202037a600403001faf16f6a2687d007d206a6a183faa9040007dadbcf6a2687d007d206a6a183618fc1400b82a1009aa0a01e428027d012c678b00e78b666491646580897a007a00658064fc80383a6465816503e5ffe4e8400202cc07060093b3f0508806e0a84026a8280790a009f404b19e2c039e2d99924591960225e801e80196019241f200e0e9919605940f97ff93a0ef003191960ab19e2ca009f4042796d625999992e3f60101f5d906380492f81f000e8698180b8d8492f81f07d207d2018fd0018b8eb90fd0018fd001839d4da0001698fe99ff6a2687d007d206a6a18400aa9385d47199a9a9b1b289a6382f97024817d207d006a18106840306b90fd001812881a282178048a502819e428027d012c678b666664f6aa7041083deecbef29385d40804f48ee036373701fa00fa40f82854120670542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007074c8cb02ca07cbffc9d05006c705f2e04aa1034545c85004fa0258cf16ccccc9ed5401fa403020d70b01c300915be30de082102c76b9735270bae30235373723c003e3023502c0040d0b0a0900428e185124c705f2e049d4304300c85004fa0258cf16ccccc9ed54e05f05840ff2f00034335035c705f2e04903fa403059c85004fa0258cf16ccccc9ed5401fe365f03820898968015a015bcf2e04b02fa40d3003095c821cf16c9916de28210d1735400708018c8cb055005cf1624fa0214cb6a13cb1f14cb3f23fa443070ba8e33f828440370542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007074c8cb02ca07cbffc9d0cf16966c227001cb01e2f4000c000ac98040fb00003e8210d53276db708010c8cb055003cf1622fa0212cb6acb1fcb3fc98042fb00e5760164"; - - /// wc -1 - static const String testnetWorkChain = - "b5ee9c7241020e010002a3000114ff00f4a413f4bcf2c80b01020162050202037a600403001faf16f6a2687d007d206a6a183faa9040007dadbcf6a2687d007d206a6a183618fc1400b82a1009aa0a01e428027d012c678b00e78b666491646580897a007a00658064fc803fba6465816503e5ffe4e8400202cc07060093b3f0508806e0a84026a8280790a009f404b19e2c039e2d99924591960225e801e80196019241f200fee9919605940f97ff93a0ef003191960ab19e2ca009f4042796d625999992e3f60101f5d906380492f81f000e8698180b8d8492f81f07d207d2018fd0018b8eb90fd0018fd001839d4da0001698fe99ff6a2687d007d206a6a18400aa9385d47199a9a9b1b289a6382f97024817d207d006a18106840306b90fd001812881a282178048a502819e428027d012c678b666664f6aa7041083deecbef29385d40804f48ee036373701fa00fa40f82854120670542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007f74c8cb02ca07cbffc9d05006c705f2e04aa1034545c85004fa0258cf16ccccc9ed5401fa403020d70b01c300915be30de082102c76b9735270bae30235373723c003e3023502c0040d0b0a0900428e185124c705f2e049d4304300c85004fa0258cf16ccccc9ed54e05f05840ff2f00034335035c705f2e04903fa403059c85004fa0258cf16ccccc9ed5401fe365f03820898968015a015bcf2e04b02fa40d3003095c821cf16c9916de28210d1735400708018c8cb055005cf1624fa0214cb6a13cb1f14cb3f23fa44307fba8e33f828440370542013541403c85004fa0258cf1601cf16ccc922c8cb0112f400f400cb00c9f9007f74c8cb02ca07cbffc9d0cf16966c227001cb01e2f4000c000ac98040fb00003e8210d53276db708010c8cb055003cf1622fa0212cb6acb1fcb3fc98042fb00fc9823ca"; - - static const int internalTransferOperation = 0x178d4519; - static const int discoverMessageOperation = 0x2c76b973; - static const int changeAddminOperation = 3; - static const int changeContentOperation = 4; -} diff --git a/lib/src/contracts/token/ft/minter/contract/contract.dart b/lib/src/contracts/token/ft/minter/contract/contract.dart deleted file mode 100644 index d609be5..0000000 --- a/lib/src/contracts/token/ft/minter/contract/contract.dart +++ /dev/null @@ -1,361 +0,0 @@ -import 'package:ton_dart/src/address/address.dart'; -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/token/ft/minter/models/minter_wallet_params.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/provider/provider/provider.dart'; -import 'package:ton_dart/src/tuple/tuple.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/token/ft/minter/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/ft/minter/models/jetton_data_response.dart'; -import 'package:ton_dart/src/contracts/token/ft/minter/utils/utils.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; - -class JettonMinter extends TonContract - with ContractProvider { - final VersonedWalletContract owner; - @override - final TonAddress address; - @override - final StateInit? state; - - const JettonMinter({required this.owner, required this.address, this.state}); - factory JettonMinter.create( - {required VersonedWalletContract owner, - TokenMetadata? metadata, - StateInit? state}) { - state ??= - JettonMinterUtils.buildState(owner: owner.address, metadata: metadata); - return JettonMinter( - owner: owner, - address: TonAddress.fromState(state: state, workChain: owner.workChain), - state: state); - } - @override - Cell code(int workchain) { - return JettonMinterUtils.code(workchain); - } - - @override - Cell data(MinterWalletParams params, int workchain) { - return JettonMinterUtils.minterData( - owner: params.owner, - walletCode: params.walletCode ?? code(workChain), - metadata: params.metadata); - } - - Future _sendTransaction( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - StateInit? state, - OnEstimateFee? onEstimateFee}) async { - return await owner.sendTransfer( - messages: [ - TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable, - ), - ...messages - ], - privateKey: privateKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode, - onEstimateFee: onEstimateFee); - } - - Future deploy( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - OnEstimateFee? onEstimateFee}) async { - final active = await isActive(rpc); - if (active) { - throw TonContractException("Account is already active."); - } - if (state == null) { - throw TonContractException( - "For deploy minter please use create constructor to build state"); - } - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: body, - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout, - onEstimateFee: onEstimateFee); - } - - Future mint( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt jettonAmout, - required BigInt forwardTonAmount, - required BigInt totalTonAmount, - required BigInt amount, - required TonAddress to, - int queryId = 0, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - OnEstimateFee? onEstimateFee}) async { - if (totalTonAmount < forwardTonAmount) { - throw TonContractException( - "Forward ton amount must be lower than total ton amout"); - } - if (amount <= totalTonAmount) { - throw TonContractException( - "Amount must be grather than total ton amount"); - } - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: mintBodyMessage( - responseAddress: owner.address, - to: to, - jettonAmount: jettonAmout, - forwardTonAmount: forwardTonAmount, - totalTonAmount: totalTonAmount, - queryId: queryId), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFee: onEstimateFee); - } - - Future discover( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required TonAddress owner, - required bool includeAddress, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: - discoveryMessageBody(owner: owner, includeAddress: includeAddress), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout); - } - - Future changeAdmin( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required TonAddress newOwner, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - OnEstimateFee? onEstimateFee}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: changeAdminMessageBody(newOwner), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFee: onEstimateFee); - } - - Future changeContent( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required TokenMetadata? newContent, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - OnEstimateFee? onEstimateFee}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: changeContentMessageBody(newContent), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFee: onEstimateFee); - } - - Cell internalTransfer( - {required TonAddress from, - required BigInt jettonAmount, - required BigInt forwardTonAmount, - required BigInt totalTonAmount, - int queryId = 0}) { - return beginCell() - .storeUint(JettonMinterConst.internalTransferOperation, 32) - .storeUint(queryId, 64) - .storeCoins(jettonAmount) - .storeAddress(null) - .storeAddress(from) - .storeCoins(forwardTonAmount) - .storeBitBolean(false) - .endCell(); - } - - Cell mintBodyMessage( - {required TonAddress responseAddress, - required TonAddress to, - required BigInt jettonAmount, - required BigInt forwardTonAmount, - required BigInt totalTonAmount, - int queryId = 0}) { - final mintMsg = beginCell() - .storeUint(JettonMinterConst.internalTransferOperation, 32) - .storeUint(0, 64) - .storeCoins(jettonAmount) - .storeAddress(null) - .storeAddress(responseAddress) - .storeCoins(forwardTonAmount) - .storeMaybeRef(cell: null) - .endCell(); - return beginCell() - .storeUint(21, 32) - .storeUint(queryId, 64) - .storeAddress(to) - .storeCoins(totalTonAmount) - .storeCoins(jettonAmount) - .storeRef(mintMsg) - .endCell(); - } - - Cell discoveryMessageBody( - {required TonAddress owner, - required bool includeAddress, - int queryId = 0}) { - return beginCell() - .storeUint(JettonMinterConst.discoverMessageOperation, 32) - .storeUint(queryId, 64) - .storeAddress(owner) - .storeBitBolean(includeAddress) - .endCell(); - } - - Cell changeAdminMessageBody(TonAddress newOwner, {int queryId = 0}) { - return beginCell() - .storeUint(JettonMinterConst.changeAddminOperation, 32) - .storeUint(queryId, 64) - .storeAddress(newOwner) - .endCell(); - } - - Cell changeContentMessageBody(TokenMetadata? metadata, {int queryId = 0}) { - return beginCell() - .storeUint(JettonMinterConst.changeContentOperation, 32) - .storeUint(queryId, 64) - .storeRef(TokneMetadataUtils.encodeMetadata(metadata)) - .endCell(); - } - - Future getJettonData(TonProvider rpc) async { - final data = await getStateStack(rpc: rpc, method: "get_jetton_data"); - final reader = data.reader(); - final item = reader.pop(); - final BigInt totalSupply; - if (item.type == TupleItemTypes.intItem) { - totalSupply = (item as TupleItemInt).value; - } else { - totalSupply = reader.readBigNumber(); - } - final bool mintable = reader.readBoolean(); - - /// mayble is bridge. - final TonAddress? admin = reader.readAddressOpt(); - final Cell content = reader.readCell(); - final Cell walletCode = reader.readCell(); - return JettonDataResponse( - totalSupply: totalSupply, - mintable: mintable, - admin: admin, - content: content, - walletCode: walletCode); - } - - Future getWalletAddress( - {required TonProvider rpc, required TonAddress owner}) async { - final data = - await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ - if (rpc.isTonCenter) - ["tvm.Slice", beginCell().storeAddress(owner).endCell().toBase64()] - else - owner.toString() - ]); - return data.reader().readAddress(); - } - - Future totalSupply(TonProvider rpc) async { - final data = await getJettonData(rpc); - return data.totalSupply; - } - - Future adminAddress(TonProvider rpc) async { - final data = await getJettonData(rpc); - return data.admin; - } - - Future getContent(TonProvider rpc) async { - final data = await getJettonData(rpc); - return data.content; - } - - Future getMetadata(TonProvider rpc) async { - final data = await getJettonData(rpc); - return TokneMetadataUtils.loadContent(data.content); - } - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/token/ft/minter/minter.dart b/lib/src/contracts/token/ft/minter/minter.dart deleted file mode 100644 index 1f2a5d3..0000000 --- a/lib/src/contracts/token/ft/minter/minter.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'models/jetton_data_response.dart'; -export 'models/minter_wallet_params.dart'; -export 'contract/contract.dart'; diff --git a/lib/src/contracts/token/ft/minter/models/jetton_data_response.dart b/lib/src/contracts/token/ft/minter/models/jetton_data_response.dart deleted file mode 100644 index 17c7415..0000000 --- a/lib/src/contracts/token/ft/minter/models/jetton_data_response.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; - -class JettonDataResponse { - final BigInt totalSupply; - final bool mintable; - final TonAddress? admin; - final Cell content; - final Cell walletCode; - const JettonDataResponse( - {required this.totalSupply, - required this.mintable, - required this.admin, - required this.content, - required this.walletCode}); -} diff --git a/lib/src/contracts/token/ft/minter/models/minter_wallet_params.dart b/lib/src/contracts/token/ft/minter/models/minter_wallet_params.dart deleted file mode 100644 index f735f6a..0000000 --- a/lib/src/contracts/token/ft/minter/models/minter_wallet_params.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; - -class MinterWalletParams { - final TonAddress owner; - final Cell? walletCode; - final TokenMetadata? metadata; - - const MinterWalletParams( - {required this.owner, this.walletCode, this.metadata}); -} diff --git a/lib/src/contracts/token/ft/minter/utils/utils.dart b/lib/src/contracts/token/ft/minter/utils/utils.dart deleted file mode 100644 index 45374d5..0000000 --- a/lib/src/contracts/token/ft/minter/utils/utils.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:blockchain_utils/utils/utils.dart'; -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/contracts/token/ft/minter/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/ft/wallet/constants/contants.dart'; - -class JettonMinterUtils { - static Cell minterData( - {required TonAddress owner, - required Cell walletCode, - TokenMetadata? metadata}) { - final content = TokneMetadataUtils.encodeMetadata(metadata); - return beginCell() - .storeCoins(0) - .storeAddress(owner) - .storeRef(content) - .storeRef(walletCode) - .endCell(); - } - - static StateInit buildState( - {required TonAddress owner, TokenMetadata? metadata}) { - return StateInit( - code: code(owner.workChain), - data: minterData( - owner: owner, - walletCode: JettonWalletConst.code(owner.workChain), - metadata: metadata)); - } - - static Cell code(int workchain) { - if (workchain < 0) { - return Cell.fromBytes( - BytesUtils.fromHexString(JettonMinterConst.testnetWorkChain)); - } - return Cell.fromBytes(BytesUtils.fromHexString(JettonMinterConst.code)); - } -} diff --git a/lib/src/contracts/token/ft/types/models/stable_minter_data.dart b/lib/src/contracts/token/ft/types/models/stable_minter_data.dart new file mode 100644 index 0000000..c85b4ae --- /dev/null +++ b/lib/src/contracts/token/ft/types/models/stable_minter_data.dart @@ -0,0 +1,55 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/tuple/tuple/tuple_reader.dart'; + +class StableTokenMinterData { + final TonAddress adminAddress; + final Cell content; + final bool mutable; + final BigInt totalSupply; + final Cell? walletCode; + Map toJson() { + return { + "adminAddress": adminAddress.toRawAddress(), + "content": content.toBase64(), + "totalSupply": totalSupply.toString(), + "walletCode": walletCode?.toBase64(), + "metadata": metadata.toJson() + }; + } + + const StableTokenMinterData({ + required this.adminAddress, + required this.totalSupply, + required this.walletCode, + required this.content, + required this.mutable, + }); + factory StableTokenMinterData.fromJson(Map json) { + return StableTokenMinterData( + adminAddress: TonAddress(json["adminAddress"]), + totalSupply: BigintUtils.parse(json["totalSupply"]), + mutable: json["mutable"], + walletCode: Cell.fromBase64(json["walletCode"]), + content: Cell.fromBase64(json["content"])); + } + + factory StableTokenMinterData.fromTuple(TupleReader reader) { + final BigInt totalSupply = reader.readBigNumber(); + final bool mutable = reader.readBoolean(); + final TonAddress adminAddress = reader.readAddress(); + final Cell content = reader.readCell(); + final Cell walletCode = reader.readCell(); + + return StableTokenMinterData( + adminAddress: adminAddress, + mutable: mutable, + totalSupply: totalSupply, + walletCode: walletCode, + content: content); + } + + TokenMetadata get metadata => TokneMetadataUtils.loadContent(content); +} diff --git a/lib/src/contracts/token/ft/types/operations/jetton.dart b/lib/src/contracts/token/ft/types/operations/jetton.dart new file mode 100644 index 0000000..2103b95 --- /dev/null +++ b/lib/src/contracts/token/ft/types/operations/jetton.dart @@ -0,0 +1,759 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant/minter.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant/wallet.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/contracts/utils/parser.dart'; +import 'package:ton_dart/src/serialization/serialization.dart'; + +class JettonWalletOperationType { + final String name; + final int id; + const JettonWalletOperationType._({required this.name, required this.id}); + static const JettonWalletOperationType transfer = JettonWalletOperationType._( + name: "Transfer", id: JettonWalletConst.transfer); + static const JettonWalletOperationType burn = + JettonWalletOperationType._(name: "Burn", id: JettonWalletConst.burn); + static const JettonWalletOperationType withdrawTon = + JettonWalletOperationType._( + name: "WithdrawTon", id: JettonWalletConst.withdrawTon); + static const JettonWalletOperationType withdrawJetton = + JettonWalletOperationType._( + name: "WithdrawJetton", id: JettonWalletConst.withdrawJetton); + + static const List values = [ + transfer, + burn, + withdrawTon, + withdrawJetton + ]; + static JettonWalletOperationType fromTag(int? tag, + {JettonWalletOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static JettonWalletOperationType fromName(String? name, + {JettonWalletOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } +} + +abstract class JettonWalletOperation extends TonSerialization { + final JettonWalletOperationType type; + final BigInt queryId; + Cell toBody() => beginCell().store(this).endCell(); + JettonWalletOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + factory JettonWalletOperation.deserialize(Slice slice) { + final type = JettonWalletOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case JettonWalletOperationType.transfer: + return JettonWalletTransfer.deserialize(slice); + case JettonWalletOperationType.burn: + return JettonWalletBurn.deserialize(slice); + case JettonWalletOperationType.withdrawTon: + return JettonWalletWithdrawTon.deserialize(slice); + case JettonWalletOperationType.withdrawJetton: + return JettonWalletWithdrawJetton.deserialize(slice); + default: + throw const TonContractException("unsuported jetton wallet operation."); + } + } + factory JettonWalletOperation.fromJson(Map? json) { + final type = JettonWalletOperationType.fromName(json?["type"]); + switch (type) { + case JettonWalletOperationType.transfer: + return JettonWalletTransfer.fromJson(json!); + case JettonWalletOperationType.burn: + return JettonWalletBurn.fromJson(json!); + case JettonWalletOperationType.withdrawTon: + return JettonWalletWithdrawTon.fromJson(json!); + case JettonWalletOperationType.withdrawJetton: + return JettonWalletWithdrawJetton.fromJson(json!); + default: + throw const TonContractException("unsuported jetton wallet operation."); + } + } + T cast() { + if (this is! T) { + throw TonContractException( + "Incorrect Jetton wallet casting. excepted: $runtimeType got: $T"); + } + return this as T; + } +} + +class JettonWalletTransfer extends JettonWalletOperation { + final TonAddress destination; + final TonAddress? responseDestination; + final BigInt forwardTonAmount; + final BigInt amount; + final Cell? customPayload; + final Cell? forwardPayload; + JettonWalletTransfer( + {required this.amount, + required this.destination, + required this.forwardTonAmount, + this.responseDestination, + BigInt? queryId, + this.customPayload, + this.forwardPayload}) + : super(type: JettonWalletOperationType.transfer, queryId: queryId); + + factory JettonWalletTransfer.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonWalletOperationType.transfer); + return JettonWalletTransfer( + queryId: slice.loadUint64(), + amount: slice.loadCoins(), + destination: slice.loadAddress(), + responseDestination: slice.loadMaybeAddress(), + customPayload: slice.loadMaybeRef(), + forwardTonAmount: slice.loadCoins(), + forwardPayload: slice.loadMaybeRef(), + ); + }, + name: JettonWalletOperationType.transfer.name); + } + factory JettonWalletTransfer.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + JettonWalletOperationType.fromName(json["type"], + excepted: JettonWalletOperationType.transfer); + return JettonWalletTransfer( + queryId: BigintUtils.tryParse(json["queryId"]), + amount: BigintUtils.parse(json["amount"]), + destination: TonAddress(json["destination"]), + forwardTonAmount: BigintUtils.parse(json["forwardTonAmount"]), + responseDestination: json["responseDestination"] == null + ? null + : TonAddress(json["responseDestination"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"]), + forwardPayload: json["forwardPayload"] == null + ? null + : Cell.fromBase64(json["forwardPayload"])); + }, + name: JettonWalletOperationType.transfer.name, + data: json); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(amount); + builder.storeAddress(destination); + builder.storeAddress(responseDestination); + builder.storeMaybeRef(cell: customPayload); + builder.storeCoins(forwardTonAmount); + builder.storeMaybeRef(cell: forwardPayload); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "amount": amount.toString(), + "destination": destination.toRawAddress(), + "responseDestination": responseDestination?.toRawAddress(), + "customPayload": customPayload?.toBase64(), + "forwardTonAmount": forwardTonAmount.toString(), + "forwardPayload": forwardPayload?.toBase64(), + "type": type.name + }; + } +} + +class JettonWalletBurn extends JettonWalletOperation { + final TonAddress? from; + final BigInt burnAmount; + final Cell? customPayload; + JettonWalletBurn({ + BigInt? queryId, + this.from, + required this.burnAmount, + this.customPayload, + }) : super(type: JettonWalletOperationType.burn, queryId: queryId); + + factory JettonWalletBurn.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonWalletOperationType.burn); + return JettonWalletBurn( + queryId: slice.loadUint64(), + burnAmount: slice.loadCoins(), + from: slice.loadMaybeAddress(), + customPayload: slice.loadMaybeRef(), + ); + }, + name: JettonWalletOperationType.burn.name); + } + factory JettonWalletBurn.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + JettonWalletOperationType.fromName(json["type"], + excepted: JettonWalletOperationType.burn); + return JettonWalletBurn( + queryId: BigintUtils.tryParse(json["queryId"]), + burnAmount: BigintUtils.parse(json["burnAmount"]), + from: json["from"] == null ? null : TonAddress(json["from"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"])); + }, + name: JettonWalletOperationType.burn.name, + data: json); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(burnAmount); + builder.storeAddress(from); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "burnAmount": burnAmount.toString(), + "from": from?.toRawAddress(), + "customPayload": customPayload?.toBase64(), + "type": type.name + }; + } +} + +class JettonWalletWithdrawTon extends JettonWalletOperation { + final Cell? customPayload; + JettonWalletWithdrawTon({ + BigInt? queryId, + this.customPayload, + }) : super(type: JettonWalletOperationType.withdrawTon, queryId: queryId); + + factory JettonWalletWithdrawTon.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonWalletOperationType.withdrawTon); + return JettonWalletWithdrawTon( + queryId: slice.loadUint64(), + customPayload: slice.loadMaybeRef(), + ); + }, + name: JettonWalletOperationType.withdrawTon.name); + } + factory JettonWalletWithdrawTon.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + JettonWalletOperationType.fromName(json["type"], + excepted: JettonWalletOperationType.withdrawTon); + return JettonWalletWithdrawTon( + queryId: BigintUtils.tryParse(json["queryId"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"])); + }, + name: JettonWalletOperationType.withdrawTon.name, + data: json); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "customPayload": customPayload?.toBase64(), + "type": type.name + }; + } +} + +class JettonWalletWithdrawJetton extends JettonWalletOperation { + final Cell? customPayload; + final TonAddress from; + final BigInt amount; + JettonWalletWithdrawJetton({ + BigInt? queryId, + required this.from, + required this.amount, + this.customPayload, + }) : super(type: JettonWalletOperationType.withdrawJetton, queryId: queryId); + + factory JettonWalletWithdrawJetton.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonWalletOperationType.withdrawJetton); + return JettonWalletWithdrawJetton( + queryId: slice.loadUint64(), + from: slice.loadAddress(), + amount: slice.loadCoins(), + customPayload: slice.tryLoadRef()); + }, + name: JettonWalletOperationType.withdrawJetton.name); + } + factory JettonWalletWithdrawJetton.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + JettonWalletOperationType.fromName(json["type"], + excepted: JettonWalletOperationType.withdrawJetton); + return JettonWalletWithdrawJetton( + queryId: BigintUtils.tryParse(json["queryId"]), + from: TonAddress(json["from"]), + amount: BigintUtils.parse(json["amount"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"])); + }, + name: JettonWalletOperationType.withdrawJetton.name, + data: json); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(from); + builder.storeCoins(amount); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "from": from.toRawAddress(), + "amount": amount.toString(), + "customPayload": customPayload?.toBase64(), + "type": type.name + }; + } +} + +class JettonMinterOperationType { + final String name; + final int id; + const JettonMinterOperationType._({required this.name, required this.id}); + static const JettonMinterOperationType mint = JettonMinterOperationType._( + name: "Mint", id: JettonMinterConst.mintOperation); + static const JettonMinterOperationType discovery = + JettonMinterOperationType._( + name: "Discovery", id: JettonMinterConst.discoverMessageOperation); + static const JettonMinterOperationType changeAdmin = + JettonMinterOperationType._( + name: "ChangeAdmin", id: JettonMinterConst.changeAdminOperation); + static const JettonMinterOperationType changeContent = + JettonMinterOperationType._( + name: "ChangeContent", id: JettonMinterConst.changeContentOperation); + static const JettonMinterOperationType internalTransfer = + JettonMinterOperationType._( + name: "InternalTransfer", + id: JettonMinterConst.internalTransferOperation); + static List values = [ + discovery, + changeAdmin, + changeContent, + internalTransfer, + mint + ]; + static JettonMinterOperationType fromTag(int? tag, + {JettonMinterOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static JettonMinterOperationType fromName(String? name, + {JettonMinterOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + @override + String toString() { + return "JettonMinterOperationType.$name"; + } +} + +abstract class JettonMinterOperation extends TonSerialization { + final JettonMinterOperationType type; + final BigInt queryId; + JettonMinterOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + Cell toBody() => beginCell().store(this).endCell(); + factory JettonMinterOperation.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = + JettonMinterOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case JettonMinterOperationType.mint: + return JettonMinterMint.deserialize(slice); + case JettonMinterOperationType.changeAdmin: + return JettonMinterChangeAdmin.deserialize(slice); + case JettonMinterOperationType.changeContent: + return JettonMinterChangeContent.deserialize(slice); + case JettonMinterOperationType.discovery: + return JettonMinterDiscovery.deserialize(slice); + case JettonMinterOperationType.internalTransfer: + return JettonMinterInternalTransfer.deserialize(slice); + + default: + throw TonContractException("Invalid Minter operation type.", + details: {"type": type.name}); + } + }, + name: "Minter"); + } + factory JettonMinterOperation.fromJson(Map? json) { + return TonModelParser.parseJson( + parse: () { + final type = JettonMinterOperationType.fromName(json?["type"]); + switch (type) { + case JettonMinterOperationType.mint: + return JettonMinterMint.fromJson(json!); + case JettonMinterOperationType.changeAdmin: + return JettonMinterChangeAdmin.fromJson(json!); + case JettonMinterOperationType.changeContent: + return JettonMinterChangeContent.fromJson(json!); + case JettonMinterOperationType.discovery: + return JettonMinterDiscovery.fromJson(json!); + case JettonMinterOperationType.internalTransfer: + return JettonMinterInternalTransfer.fromJson(json!); + default: + throw TonContractException("Invalid Minter operation type.", + details: {"type": type.name}); + } + }, + name: "Minter"); + } + T cast() { + if (this is! T) { + throw TonContractException("Incorrect JettonMinterOperation casting.", + details: {"excepted": "$runtimeType", "got": "$T"}); + } + return this as T; + } +} + +class JettonMinterMint extends JettonMinterOperation { + final BigInt totalTonAmount; + final BigInt jettonAmount; + final TonAddress to; + final JettonMinterInternalTransfer transfer; + + JettonMinterMint({ + required this.totalTonAmount, + required this.to, + required this.transfer, + required this.jettonAmount, + BigInt? queryId, + }) : super(type: JettonMinterOperationType.mint, queryId: queryId); + factory JettonMinterMint.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return JettonMinterMint( + queryId: BigintUtils.parse(json["queryId"]), + totalTonAmount: BigintUtils.parse(json["totalTonAmount"]), + to: TonAddress(json["to"]), + jettonAmount: BigintUtils.parse(json["jettonAmount"]), + transfer: + JettonMinterInternalTransfer.fromJson(json["transfer"])); + }, + name: JettonMinterOperationType.mint.name); + } + factory JettonMinterMint.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonMinterOperationType.mint); + return JettonMinterMint( + queryId: slice.loadUint64(), + to: slice.loadAddress(), + totalTonAmount: slice.loadCoins(), + jettonAmount: slice.loadCoins(), + transfer: JettonMinterInternalTransfer.deserialize( + slice.loadRef().beginParse()), + ); + }, + name: JettonMinterOperationType.mint.name); + } + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(to); + builder.storeCoins(totalTonAmount); + builder.storeCoins(jettonAmount); + builder.storeRef(transfer.toBody()); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "to": to.toRawAddress(), + "totalTonAmount": totalTonAmount.toString(), + "jettonAmount": jettonAmount.toString(), + "transfer": transfer.toJson(), + }; + } +} + +class JettonMinterInternalTransfer extends JettonMinterOperation { + final BigInt jettonAmount; + final TonAddress? from; + final TonAddress? responseAddress; + final Cell? customPayload; + final BigInt forwardTonAmount; + + JettonMinterInternalTransfer({ + required this.jettonAmount, + this.from, + this.responseAddress, + this.customPayload, + required this.forwardTonAmount, + BigInt? queryId, + }) : super( + type: JettonMinterOperationType.internalTransfer, queryId: queryId); + factory JettonMinterInternalTransfer.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return JettonMinterInternalTransfer( + queryId: BigintUtils.parse(json["queryId"]), + forwardTonAmount: BigintUtils.parse(json["forwardTonAmount"]), + jettonAmount: BigintUtils.parse(json["jettonAmount"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"]), + from: json["from"] == null ? null : TonAddress(json["from"]), + responseAddress: json["responseAddress"] == null + ? null + : TonAddress(json["responseAddress"])); + }, + name: JettonMinterOperationType.internalTransfer.name); + } + factory JettonMinterInternalTransfer.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonMinterOperationType.internalTransfer); + return JettonMinterInternalTransfer( + queryId: slice.loadUint64(), + jettonAmount: slice.loadCoins(), + from: slice.loadMaybeAddress(), + responseAddress: slice.loadMaybeAddress(), + forwardTonAmount: slice.loadCoins(), + customPayload: slice.loadMaybeRef(), + ); + }, + name: JettonMinterOperationType.internalTransfer.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(jettonAmount); + builder.storeAddress(from); + builder.storeAddress(responseAddress); + builder.storeCoins(forwardTonAmount); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "jettonAmount": jettonAmount.toString(), + "responseAddress": responseAddress?.toRawAddress(), + "customPayload": customPayload?.toBase64(), + "forwardTonAmount": forwardTonAmount.toString(), + }; + } +} + +class JettonMinterDiscovery extends JettonMinterOperation { + final TonAddress owner; + final bool includeAddress; + JettonMinterDiscovery( + {required this.owner, required this.includeAddress, BigInt? queryId}) + : super(type: JettonMinterOperationType.discovery, queryId: queryId); + factory JettonMinterDiscovery.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return JettonMinterDiscovery( + owner: TonAddress(json["owner"]), + includeAddress: json["includeAddress"], + queryId: BigintUtils.parse(json["queryId"])); + }, + name: JettonMinterOperationType.discovery.name); + } + factory JettonMinterDiscovery.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonMinterOperationType.discovery); + return JettonMinterDiscovery( + queryId: slice.loadUint64(), + owner: slice.loadAddress(), + includeAddress: slice.loadBoolean()); + }, + name: JettonMinterOperationType.discovery.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(owner); + builder.storeBitBolean(includeAddress); + } + + @override + Map toJson() { + return { + "type": type.name, + "owner": owner.toRawAddress(), + "includeAddress": includeAddress, + "queryId": queryId.toString() + }; + } +} + +class JettonMinterChangeAdmin extends JettonMinterOperation { + final TonAddress newOwner; + JettonMinterChangeAdmin({required this.newOwner, BigInt? queryId}) + : super(type: JettonMinterOperationType.changeAdmin, queryId: queryId); + factory JettonMinterChangeAdmin.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return JettonMinterChangeAdmin( + newOwner: TonAddress(json["newOwner"]), + queryId: BigintUtils.parse(json["queryId"])); + }, + name: JettonMinterOperationType.changeAdmin.name); + } + factory JettonMinterChangeAdmin.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonMinterOperationType.changeAdmin); + return JettonMinterChangeAdmin( + queryId: slice.loadUint64(), newOwner: slice.loadAddress()); + }, + name: JettonMinterOperationType.changeAdmin.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(newOwner); + } + + @override + Map toJson() { + return { + "type": type.name, + "newOwner": newOwner.toRawAddress(), + "queryId": queryId.toString() + }; + } +} + +class JettonMinterChangeContent extends JettonMinterOperation { + final Cell content; + JettonMinterChangeContent({required this.content, BigInt? queryId}) + : super(type: JettonMinterOperationType.changeContent, queryId: queryId); + factory JettonMinterChangeContent.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return JettonMinterChangeContent( + content: Cell.fromBase64(json["content"]), + queryId: BigintUtils.parse(json["queryId"])); + }, + name: JettonMinterOperationType.changeContent.name); + } + factory JettonMinterChangeContent.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + JettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: JettonMinterOperationType.changeContent); + return JettonMinterChangeContent( + queryId: slice.loadUint64(), content: slice.loadRef()); + }, + name: JettonMinterOperationType.changeContent.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeRef(content); + } + + @override + Map toJson() { + return { + "type": type.name, + "content": content.toBase64(), + "queryId": queryId.toString() + }; + } + + TokenMetadata get contentMetaData { + return TokneMetadataUtils.loadContent(content); + } +} diff --git a/lib/src/contracts/token/ft/types/operations/stable_jetton.dart b/lib/src/contracts/token/ft/types/operations/stable_jetton.dart new file mode 100644 index 0000000..147e71f --- /dev/null +++ b/lib/src/contracts/token/ft/types/operations/stable_jetton.dart @@ -0,0 +1,975 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/utils/parser.dart'; +import 'package:ton_dart/ton_dart.dart'; + +class StableJettonMinterOperationType { + final int id; + final String name; + const StableJettonMinterOperationType._( + {required this.id, required this.name}); + static const StableJettonMinterOperationType discovery = + StableJettonMinterOperationType._(id: 0x2c76b973, name: "Discovery"); + static const StableJettonMinterOperationType topUp = + StableJettonMinterOperationType._(id: 0xd372158c, name: "TopUp"); + static const StableJettonMinterOperationType changeAdmin = + StableJettonMinterOperationType._(id: 0x6501f354, name: "ChangeAdmin"); + static const StableJettonMinterOperationType claimAdmin = + StableJettonMinterOperationType._(id: 0xfb88e119, name: "ClaimAdmin"); + static const StableJettonMinterOperationType changeContent = + StableJettonMinterOperationType._(id: 0xcb862902, name: "ChangeContent"); + static const StableJettonMinterOperationType callTo = + StableJettonMinterOperationType._(id: 0x235caf52, name: "CallTo"); + static const StableJettonMinterOperationType mint = + StableJettonMinterOperationType._(id: 0x642b7d07, name: "Mint"); + static const StableJettonMinterOperationType internalTransfer = + StableJettonMinterOperationType._( + id: 0x178d4519, name: "InternalTransfer"); + + static const List values = [ + mint, + discovery, + topUp, + changeAdmin, + claimAdmin, + changeContent, + callTo, + internalTransfer + ]; + static StableJettonMinterOperationType fromTag(int? tag, + {StableJettonMinterOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static StableJettonMinterOperationType fromName(String? name, + {StableJettonMinterOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } +} + +class StableJettonWalletOperationType { + final int id; + final String name; + const StableJettonWalletOperationType._( + {required this.id, required this.name}); + static const StableJettonWalletOperationType setStatus = + StableJettonWalletOperationType._(id: 0xeed236d3, name: "SetStatus"); + static const StableJettonWalletOperationType transfer = + StableJettonWalletOperationType._(id: 0xf8a7ea5, name: "Transfer"); + static const StableJettonWalletOperationType burn = + StableJettonWalletOperationType._(id: 0x595f07bc, name: "Burn"); + static const StableJettonWalletOperationType withdrawTon = + StableJettonWalletOperationType._(id: 0x6d8e5e3c, name: "WithdrawTon"); + static const StableJettonWalletOperationType withdrawJetton = + StableJettonWalletOperationType._(id: 0x768a50b2, name: "WithdrawJetton"); + + /// + static const List values = [ + setStatus, + transfer, + burn, + withdrawTon, + withdrawJetton + ]; + static StableJettonWalletOperationType fromTag(int? tag, + {StableJettonWalletOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static StableJettonWalletOperationType fromName(String? name, + {StableJettonWalletOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } +} + +abstract class StableJettonMinterCallToOperations extends TonSerialization { + abstract final StableJettonWalletOperationType type; + Cell toBody() => beginCell().store(this).endCell(); + T cast() { + if (this is! T) { + throw TonContractException( + "Incorrect stable jetton minter casting. excepted: $runtimeType got: $T"); + } + return this as T; + } + + factory StableJettonMinterCallToOperations.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = + StableJettonWalletOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case StableJettonWalletOperationType.transfer: + return StableJettonWalletTransfer.deserialize(slice); + case StableJettonWalletOperationType.burn: + return StableJettonWalletBurn.deserialize(slice); + case StableJettonWalletOperationType.setStatus: + return StableJettonWalletSetStatus.deserialize(slice); + default: + throw TonContractException("Invalid call to operation type.", + details: {"type": type.name}); + } + }, + name: "CallTo"); + } + factory StableJettonMinterCallToOperations.fromJson( + Map json) { + return TonModelParser.parseJson( + parse: () { + final type = StableJettonWalletOperationType.fromName(json["type"]); + switch (type) { + case StableJettonWalletOperationType.transfer: + return StableJettonWalletTransfer.fromJson(json); + case StableJettonWalletOperationType.burn: + return StableJettonWalletBurn.fromJson(json); + case StableJettonWalletOperationType.setStatus: + return StableJettonWalletSetStatus.fromJson(json); + default: + throw TonContractException("Invalid call to operation type.", + details: {"type": type.name}); + } + }, + name: "CallTo"); + } +} + +abstract class StableJettonMinterOperation extends TonSerialization { + final StableJettonMinterOperationType type; + final BigInt queryId; + StableJettonMinterOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + Cell toBody() => beginCell().store(this).endCell(); + factory StableJettonMinterOperation.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = + StableJettonMinterOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case StableJettonMinterOperationType.topUp: + return StableJettonMinterTopUp.deserialize(slice); + case StableJettonMinterOperationType.mint: + return StableJettonMinterMint.deserialize(slice); + case StableJettonMinterOperationType.changeAdmin: + return StableJettonMinterChangeAdmin.deserialize(slice); + case StableJettonMinterOperationType.changeContent: + return StableJettonMinterChangeContent.deserialize(slice); + case StableJettonMinterOperationType.discovery: + return StableJettonMinterDiscovery.deserialize(slice); + case StableJettonMinterOperationType.internalTransfer: + return StableJettonMinterInternalTransfer.deserialize(slice); + case StableJettonMinterOperationType.claimAdmin: + return StableJettonMinterClaimAdmin.deserialize(slice); + case StableJettonMinterOperationType.callTo: + return StableJettonMinterCallTo.deserialize(slice); + default: + throw TonContractException("Invalid Minter operation type.", + details: {"type": type.name}); + } + }, + name: "Minter"); + } + factory StableJettonMinterOperation.fromJson(Map? json) { + return TonModelParser.parseJson( + parse: () { + final type = StableJettonMinterOperationType.fromName(json?["type"]); + switch (type) { + case StableJettonMinterOperationType.topUp: + return StableJettonMinterTopUp.fromJson(json!); + case StableJettonMinterOperationType.mint: + return StableJettonMinterMint.fromJson(json!); + case StableJettonMinterOperationType.changeAdmin: + return StableJettonMinterChangeAdmin.fromJson(json!); + case StableJettonMinterOperationType.changeContent: + return StableJettonMinterChangeContent.fromJson(json!); + case StableJettonMinterOperationType.discovery: + return StableJettonMinterDiscovery.fromJson(json!); + case StableJettonMinterOperationType.internalTransfer: + return StableJettonMinterInternalTransfer.fromJson(json!); + case StableJettonMinterOperationType.claimAdmin: + return StableJettonMinterClaimAdmin.fromJson(json!); + case StableJettonMinterOperationType.callTo: + return StableJettonMinterCallTo.fromJson(json!); + default: + throw TonContractException("Invalid Minter operation type.", + details: {"type": type.name}); + } + }, + name: "Minter"); + } + + T cast() { + if (this is! T) { + throw TonContractException( + "Incorrect stable jetton minter casting. excepted: $runtimeType got: $T"); + } + return this as T; + } +} + +class StableJettonMinterMint extends StableJettonMinterOperation { + final BigInt totalTonAmount; + final TonAddress to; + final StableJettonMinterInternalTransfer transfer; + + StableJettonMinterMint({ + required this.totalTonAmount, + required this.to, + required this.transfer, + BigInt? queryId, + }) : super(type: StableJettonMinterOperationType.mint, queryId: queryId); + factory StableJettonMinterMint.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterMint( + queryId: BigintUtils.parse(json["queryId"]), + totalTonAmount: BigintUtils.parse(json["totalTonAmount"]), + to: TonAddress(json["to"]), + transfer: StableJettonMinterInternalTransfer.fromJson( + json["transfer"])); + }, + name: StableJettonMinterOperationType.mint.name); + } + factory StableJettonMinterMint.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.mint); + return StableJettonMinterMint( + queryId: slice.loadUint64(), + to: slice.loadAddress(), + totalTonAmount: slice.loadCoins(), + transfer: StableJettonMinterInternalTransfer.deserialize( + slice.loadRef().beginParse())); + }, + name: StableJettonMinterOperationType.mint.name); + } + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(to); + builder.storeCoins(totalTonAmount); + builder.storeRef(transfer.toBody()); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "to": to.toRawAddress(), + "totalTonAmount": totalTonAmount.toString(), + "transfer": transfer.toJson() + }; + } +} + +class StableJettonMinterInternalTransfer extends StableJettonMinterOperation { + final BigInt jettonAmount; + final TonAddress? from; + final TonAddress? responseAddress; + final Cell? customPayload; + final BigInt forwardTonAmount; + + StableJettonMinterInternalTransfer({ + required this.jettonAmount, + this.from, + this.responseAddress, + this.customPayload, + required this.forwardTonAmount, + BigInt? queryId, + }) : super( + type: StableJettonMinterOperationType.internalTransfer, + queryId: queryId); + factory StableJettonMinterInternalTransfer.fromJson( + Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterInternalTransfer( + queryId: BigintUtils.parse(json["queryId"]), + forwardTonAmount: BigintUtils.parse(json["forwardTonAmount"]), + jettonAmount: BigintUtils.parse(json["jettonAmount"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"]), + from: json["from"] == null ? null : TonAddress(json["from"]), + responseAddress: json["responseAddress"] == null + ? null + : TonAddress(json["responseAddress"])); + }, + name: StableJettonMinterOperationType.internalTransfer.name); + } + factory StableJettonMinterInternalTransfer.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.internalTransfer); + return StableJettonMinterInternalTransfer( + queryId: slice.loadUint64(), + jettonAmount: slice.loadCoins(), + from: slice.loadMaybeAddress(), + responseAddress: slice.loadMaybeAddress(), + forwardTonAmount: slice.loadCoins(), + customPayload: slice.loadMaybeRef(), + ); + }, + name: StableJettonMinterOperationType.internalTransfer.name); + } + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(jettonAmount); + builder.storeAddress(from); + builder.storeAddress(responseAddress); + builder.storeCoins(forwardTonAmount); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "jettonAmount": jettonAmount.toString(), + "responseAddress": responseAddress?.toRawAddress(), + "customPayload": customPayload?.toBase64(), + "forwardTonAmount": forwardTonAmount.toString(), + }; + } +} + +class StableJettonMinterDiscovery extends StableJettonMinterOperation { + final TonAddress owner; + final bool includeAddress; + StableJettonMinterDiscovery( + {required this.owner, required this.includeAddress, BigInt? queryId}) + : super( + type: StableJettonMinterOperationType.discovery, queryId: queryId); + factory StableJettonMinterDiscovery.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterDiscovery( + owner: TonAddress(json["owner"]), + includeAddress: json["includeAddress"], + queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonMinterOperationType.discovery.name); + } + factory StableJettonMinterDiscovery.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.discovery); + return StableJettonMinterDiscovery( + queryId: slice.loadUint64(), + owner: slice.loadAddress(), + includeAddress: slice.loadBoolean()); + }, + name: StableJettonMinterOperationType.discovery.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(owner); + builder.storeBitBolean(includeAddress); + } + + @override + Map toJson() { + return { + "type": type.name, + "owner": owner.toRawAddress(), + "includeAddress": includeAddress, + "queryId": queryId.toString() + }; + } +} + +class StableJettonMinterTopUp extends StableJettonMinterOperation { + StableJettonMinterTopUp({BigInt? queryId}) + : super(type: StableJettonMinterOperationType.topUp, queryId: queryId); + factory StableJettonMinterTopUp.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterTopUp( + queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonMinterOperationType.topUp.name); + } + factory StableJettonMinterTopUp.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.topUp); + return StableJettonMinterTopUp(queryId: slice.loadUint64()); + }, + name: StableJettonMinterOperationType.topUp.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + } + + @override + Map toJson() { + return {"type": type.name, "queryId": queryId.toString()}; + } +} + +class StableJettonMinterChangeAdmin extends StableJettonMinterOperation { + final TonAddress newOwner; + StableJettonMinterChangeAdmin({required this.newOwner, BigInt? queryId}) + : super( + type: StableJettonMinterOperationType.changeAdmin, + queryId: queryId); + factory StableJettonMinterChangeAdmin.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterChangeAdmin( + newOwner: TonAddress(json["newOwner"]), + queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonMinterOperationType.changeAdmin.name); + } + factory StableJettonMinterChangeAdmin.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.changeAdmin); + return StableJettonMinterChangeAdmin( + queryId: slice.loadUint64(), newOwner: slice.loadAddress()); + }, + name: StableJettonMinterOperationType.changeAdmin.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(newOwner); + } + + @override + Map toJson() { + return { + "type": type.name, + "newOwner": newOwner.toRawAddress(), + "queryId": queryId.toString() + }; + } +} + +class StableJettonMinterClaimAdmin extends StableJettonMinterOperation { + StableJettonMinterClaimAdmin({BigInt? queryId}) + : super( + type: StableJettonMinterOperationType.claimAdmin, queryId: queryId); + factory StableJettonMinterClaimAdmin.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterClaimAdmin( + queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonMinterOperationType.claimAdmin.name); + } + factory StableJettonMinterClaimAdmin.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.claimAdmin); + return StableJettonMinterClaimAdmin(queryId: slice.loadUint64()); + }, + name: StableJettonMinterOperationType.claimAdmin.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + } + + @override + Map toJson() { + return {"type": type.name, "queryId": queryId.toString()}; + } +} + +class StableJettonMinterChangeContent extends StableJettonMinterOperation { + final String url; + StableJettonMinterChangeContent({required this.url, BigInt? queryId}) + : super( + type: StableJettonMinterOperationType.changeContent, + queryId: queryId); + factory StableJettonMinterChangeContent.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterChangeContent( + url: json["url"], queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonMinterOperationType.changeContent.name); + } + factory StableJettonMinterChangeContent.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.changeContent); + return StableJettonMinterChangeContent( + queryId: slice.loadUint64(), url: slice.loadStringTail()); + }, + name: StableJettonMinterOperationType.changeContent.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeStringTail(url); + } + + @override + Map toJson() { + return {"type": type.name, "url": url, "queryId": queryId.toString()}; + } +} + +class StableJettonMinterCallTo extends StableJettonMinterOperation { + final TonAddress address; + final BigInt amount; + final StableJettonMinterCallToOperations operation; + + StableJettonMinterCallTo( + {required this.address, + required this.amount, + required this.operation, + BigInt? queryId}) + : super(type: StableJettonMinterOperationType.callTo, queryId: queryId); + factory StableJettonMinterCallTo.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonMinterCallTo( + address: TonAddress(json["address"]), + amount: BigintUtils.parse(json["amount"]), + queryId: BigintUtils.parse(json["queryId"]), + operation: + StableJettonMinterCallToOperations.fromJson(json["operation"]), + ); + }, + name: StableJettonMinterOperationType.callTo.name); + } + factory StableJettonMinterCallTo.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonMinterOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonMinterOperationType.callTo); + return StableJettonMinterCallTo( + queryId: slice.loadUint64(), + address: slice.loadAddress(), + amount: slice.loadCoins(), + operation: StableJettonMinterCallToOperations.deserialize( + slice.loadRef().beginParse())); + }, + name: StableJettonMinterOperationType.callTo.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(address); + builder.storeCoins(amount); + builder.storeRef(operation.toBody()); + } + + @override + Map toJson() { + return { + "type": type.name, + "address": address.toRawAddress(), + "amount": amount.toString(), + "queryId": queryId.toString(), + "operation": operation.toJson() + }; + } +} + +abstract class StableJettonWalletOperation extends TonSerialization { + final StableJettonWalletOperationType type; + final BigInt queryId; + Cell toBody() => beginCell().store(this).endCell(); + + T cast() { + if (this is! T) { + throw TonContractException( + "Incorrect stable jetton wallet casting. excepted: $runtimeType got: $T"); + } + return this as T; + } + + StableJettonWalletOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + + factory StableJettonWalletOperation.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = + StableJettonWalletOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case StableJettonWalletOperationType.transfer: + return StableJettonWalletTransfer.deserialize(slice); + case StableJettonWalletOperationType.burn: + return StableJettonWalletBurn.deserialize(slice); + case StableJettonWalletOperationType.setStatus: + return StableJettonWalletSetStatus.deserialize(slice); + default: + throw TonContractException("Invalid Token Wallet operation type.", + details: {"type": type.name}); + } + }, + name: "Token Wallet"); + } + factory StableJettonWalletOperation.fromJson(Map? json) { + return TonModelParser.parseJson( + parse: () { + final type = StableJettonWalletOperationType.fromName(json?["type"]); + switch (type) { + case StableJettonWalletOperationType.transfer: + return StableJettonWalletTransfer.fromJson(json!); + case StableJettonWalletOperationType.burn: + return StableJettonWalletBurn.fromJson(json!); + case StableJettonWalletOperationType.setStatus: + return StableJettonWalletSetStatus.fromJson(json!); + default: + throw TonContractException("Invalid Token Wallet operation type.", + details: {"type": type.name}); + } + }, + name: "Token Wallet"); + } +} + +class StableJettonWalletSetStatus extends StableJettonWalletOperation + implements StableJettonMinterCallToOperations { + final StableTokenWalletStatus status; + StableJettonWalletSetStatus({required this.status, BigInt? queryId}) + : super( + type: StableJettonWalletOperationType.setStatus, queryId: queryId); + factory StableJettonWalletSetStatus.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonWalletSetStatus( + status: StableTokenWalletStatus.fromName(json["status"]), + queryId: BigintUtils.parse(json["queryId"])); + }, + name: StableJettonWalletOperationType.setStatus.name); + } + factory StableJettonWalletSetStatus.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonWalletOperationType.setStatus); + return StableJettonWalletSetStatus( + queryId: slice.loadUint64(), + status: StableTokenWalletStatus.fromTag(slice.loadUint4())); + }, + name: StableJettonWalletOperationType.setStatus.name); + } + @override + Cell toBody() => beginCell().store(this).endCell(); + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeUint4(status.id); + } + + @override + Map toJson() { + return { + "type": type.name, + "status": status.name, + "queryId": queryId.toString() + }; + } +} + +class StableJettonWalletTransfer extends StableJettonWalletOperation + implements StableJettonMinterCallToOperations { + final BigInt jettonAmount; + final TonAddress to; + final TonAddress? responseAddress; + final Cell? customPayload; + final BigInt forwardTonAmount; + final Cell? forwardPayload; + + StableJettonWalletTransfer({ + required this.jettonAmount, + required this.to, + this.responseAddress, + this.customPayload, + required this.forwardTonAmount, + this.forwardPayload, + BigInt? queryId, + }) : super(type: StableJettonWalletOperationType.transfer, queryId: queryId); + factory StableJettonWalletTransfer.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonWalletTransfer( + queryId: BigintUtils.parse(json["queryId"]), + forwardTonAmount: BigintUtils.parse(json["forwardTonAmount"]), + jettonAmount: BigintUtils.parse(json["jettonAmount"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"]), + forwardPayload: json["forwardPayload"] == null + ? null + : Cell.fromBase64(json["forwardPayload"]), + to: TonAddress(json["to"]), + responseAddress: json["responseAddress"] == null + ? null + : TonAddress(json["responseAddress"])); + }, + name: StableJettonWalletOperationType.transfer.name); + } + factory StableJettonWalletTransfer.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonWalletOperationType.transfer); + return StableJettonWalletTransfer( + queryId: slice.loadUint64(), + jettonAmount: slice.loadCoins(), + to: slice.loadAddress(), + responseAddress: slice.loadMaybeAddress(), + customPayload: slice.loadMaybeRef(), + forwardTonAmount: slice.loadCoins(), + forwardPayload: slice.loadMaybeRef()); + }, + name: StableJettonWalletOperationType.transfer.name); + } + @override + Cell toBody() => beginCell().store(this).endCell(); + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(jettonAmount); + builder.storeAddress(to); + builder.storeAddress(responseAddress); + builder.storeMaybeRef(cell: customPayload); + builder.storeCoins(forwardTonAmount); + builder.storeMaybeRef(cell: forwardPayload); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "jettonAmount": jettonAmount.toString(), + "to": to.toRawAddress(), + "responseAddress": responseAddress?.toRawAddress(), + "customPayload": customPayload?.toBase64(), + "forwardTonAmount": forwardTonAmount.toString(), + "forwardPayload": forwardPayload?.toBase64() + }; + } +} + +class StableJettonWalletBurn extends StableJettonWalletOperation + implements StableJettonMinterCallToOperations { + final BigInt jettonAmount; + final TonAddress? responseAddress; + final Cell? customPayload; + + StableJettonWalletBurn({ + required this.jettonAmount, + this.responseAddress, + this.customPayload, + BigInt? queryId, + }) : super(type: StableJettonWalletOperationType.burn, queryId: queryId); + factory StableJettonWalletBurn.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return StableJettonWalletBurn( + queryId: BigintUtils.parse(json["queryId"]), + jettonAmount: BigintUtils.parse(json["jettonAmount"]), + customPayload: json["customPayload"] == null + ? null + : Cell.fromBase64(json["customPayload"]), + responseAddress: json["responseAddress"] == null + ? null + : TonAddress(json["responseAddress"])); + }, + name: StableJettonWalletOperationType.burn.name); + } + factory StableJettonWalletBurn.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + StableJettonWalletOperationType.fromTag(slice.tryLoadUint32(), + excepted: StableJettonWalletOperationType.burn); + return StableJettonWalletBurn( + queryId: slice.loadUint64(), + jettonAmount: slice.loadCoins(), + responseAddress: slice.loadMaybeAddress(), + customPayload: slice.loadMaybeRef()); + }, + name: StableJettonWalletOperationType.burn.name); + } + @override + Cell toBody() => beginCell().store(this).endCell(); + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeCoins(jettonAmount); + builder.storeAddress(responseAddress); + builder.storeMaybeRef(cell: customPayload); + } + + @override + Map toJson() { + return { + "type": type.name, + "queryId": queryId.toString(), + "jettonAmount": jettonAmount.toString(), + "responseAddress": responseAddress?.toRawAddress(), + "customPayload": customPayload?.toBase64() + }; + } +} + +// class StableTokenWalletWithrawTon extends StableJettonWalletOperation { +// StableTokenWalletWithrawTon({ +// BigInt? queryId, +// }) : super( +// type: StableJettonWalletOperationType.withdrawTon, queryId: queryId); +// factory StableTokenWalletWithrawTon.fromJson(Map json) { +// return TonModelParser.parseJson( +// parse: () { +// return StableTokenWalletWithrawTon( +// queryId: BigintUtils.parse(json["queryId"])); +// }, +// name: StableJettonWalletOperationType.withdrawTon.name); +// } +// factory StableTokenWalletWithrawTon.deserialize(Slice slice) { +// return TonModelParser.parseBoc( +// parse: () { +// StableJettonWalletOperationType.fromTag(slice.tryLoadUint32(), +// excepted: StableJettonWalletOperationType.withdrawTon); +// return StableTokenWalletWithrawTon(queryId: slice.loadUint64()); +// }, +// name: StableJettonWalletOperationType.withdrawTon.name); +// } + +// @override +// void store(Builder builder) { +// builder.storeUint32(type.id); +// builder.storeUint64(queryId); +// } + +// @override +// Map toJson() { +// return {"type": type.name, "queryId": queryId.toString()}; +// } +// } + +// class StableTokenWalletWithrawJetton extends StableJettonWalletOperation { +// final BigInt amount; +// final TonAddress address; +// final Cell? customPayload; + +// StableTokenWalletWithrawJetton({ +// required this.amount, +// required this.address, +// this.customPayload, +// BigInt? queryId, +// }) : super( +// type: StableJettonWalletOperationType.withdrawJetton, +// queryId: queryId); +// factory StableTokenWalletWithrawJetton.fromJson(Map json) { +// return TonModelParser.parseJson( +// parse: () { +// return StableTokenWalletWithrawJetton( +// queryId: BigintUtils.parse(json["queryId"]), +// amount: BigintUtils.parse(json["amount"]), +// customPayload: json["customPayload"] == null +// ? null +// : Cell.fromBase64(json["customPayload"]), +// address: TonAddress(json["address"])); +// }, +// name: StableJettonWalletOperationType.withdrawJetton.name); +// } +// factory StableTokenWalletWithrawJetton.deserialize(Slice slice) { +// return TonModelParser.parseBoc( +// parse: () { +// StableJettonWalletOperationType.fromTag(slice.tryLoadUint32(), +// excepted: StableJettonWalletOperationType.withdrawJetton); +// return StableTokenWalletWithrawJetton( +// queryId: slice.loadUint64(), +// address: slice.loadAddress(), +// amount: slice.loadCoins(), +// customPayload: slice.loadMaybeRef()); +// }, +// name: StableJettonWalletOperationType.withdrawJetton.name); +// } + +// @override +// void store(Builder builder) { +// builder.storeUint32(type.id); +// builder.storeUint64(queryId); +// builder.storeAddress(address); +// builder.storeCoins(amount); +// builder.storeMaybeRef(cell: customPayload); +// } + +// @override +// Map toJson() { +// return { +// "type": type.name, +// "queryId": queryId.toString(), +// "amount": amount.toString(), +// "address": address.toRawAddress(), +// "customPayload": customPayload?.toBase64() +// }; +// } +// } diff --git a/lib/src/contracts/token/ft/types/state/minter.dart b/lib/src/contracts/token/ft/types/state/minter.dart new file mode 100644 index 0000000..1b27a89 --- /dev/null +++ b/lib/src/contracts/token/ft/types/state/minter.dart @@ -0,0 +1,98 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; +import 'package:ton_dart/src/tuple/tuple.dart'; + +class MinterWalletState extends ContractState { + final TonAddress? owner; + final Cell content; + final BigInt totalSupply; + final Cell? walletCode; + Map toJson() { + return { + "owner": owner?.toFriendlyAddress(), + "content": content.toBase64(), + "totalSupply": totalSupply, + "walletCode": walletCode?.toBase64(), + "metadata": metadata.toJson() + }; + } + + factory MinterWalletState.fromTupple(TupleReader reader) { + final item = reader.pop(); + final BigInt totalSupply; + if (item.type == TupleItemTypes.intItem) { + totalSupply = (item as TupleItemInt).value; + } else { + totalSupply = reader.readBigNumber(); + } + reader.readNumber(); + final TonAddress? admin = reader.readAddressOpt(); + final Cell content = reader.readCell(); + final Cell walletCode = reader.readCell(); + return MinterWalletState._( + totalSupply: totalSupply, + owner: admin, + content: content, + walletCode: walletCode); + } + const MinterWalletState._({ + required this.owner, + required this.totalSupply, + required this.walletCode, + required this.content, + }); + factory MinterWalletState( + {required TonAddress owner, + required TonChain chain, + TokenMetadata? metadata, + Cell? contect, + Cell? walletCode, + BigInt? totalSupply}) { + if (metadata != null && contect != null) { + throw const TonContractException( + "Use only content or metadata for jetton content"); + } + return MinterWalletState._( + owner: owner, + totalSupply: totalSupply ?? BigInt.zero, + walletCode: walletCode, + content: contect ?? TokneMetadataUtils.encodeMetadata(metadata)); + } + factory MinterWalletState.deserialize(Slice slice) { + final BigInt totalSupply = slice.loadCoins(); + final TonAddress? owner = slice.loadMaybeAddress(); + final Cell content = slice.loadRef(); + final Cell walletCode = slice.loadRef(); + return MinterWalletState._( + owner: owner, + totalSupply: totalSupply, + walletCode: walletCode, + content: content); + } + + TokenMetadata get metadata => TokneMetadataUtils.loadContent(content); + + @override + StateInit initialState({int? workchain}) { + return StateInit( + data: initialData(), + code: JettonMinterConst.code(owner?.workChain ?? 0)); + } + + @override + Cell initialData({int? workchain}) { + final Cell code = + walletCode ?? JettonWalletConst.code(owner?.workChain ?? 0); + return beginCell() + .storeCoins(totalSupply) + .storeAddress(owner) + .storeRef(content) + .storeRef(code) + .endCell(); + } +} diff --git a/lib/src/contracts/token/ft/types/state/stable_minter.dart b/lib/src/contracts/token/ft/types/state/stable_minter.dart new file mode 100644 index 0000000..5aab19a --- /dev/null +++ b/lib/src/contracts/token/ft/types/state/stable_minter.dart @@ -0,0 +1,99 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class StableTokenMinterState extends ContractState { + final TonAddress adminAddress; + final TonAddress? nextAdminAddress; + final Cell content; + final BigInt totalSupply; + final Cell? walletCode; + Map toJson() { + return { + "adminAddress": adminAddress.toRawAddress(), + "content": content.toBase64(), + "totalSupply": totalSupply.toString(), + "walletCode": walletCode?.toBase64(), + "nextAdminAddress": nextAdminAddress?.toRawAddress(), + "metadata": metadata.toJson() + }; + } + + factory StableTokenMinterState.fromJson(Map json) { + return StableTokenMinterState._( + adminAddress: TonAddress(json["adminAddress"]), + totalSupply: BigintUtils.parse(json["totalSupply"]), + walletCode: Cell.fromBase64(json["walletCode"]), + content: Cell.fromBase64(json["content"]), + nextAdminAddress: json["nextAdminAddress"] == null + ? null + : TonAddress(json["nextAdminAddress"])); + } + + const StableTokenMinterState._({ + required this.adminAddress, + required this.totalSupply, + required this.walletCode, + required this.content, + required this.nextAdminAddress, + }); + factory StableTokenMinterState( + {required TonAddress adminAddress, + TonAddress? transferAdminAddress, + TokenMetadata? metadata, + Cell? contect, + Cell? walletCode, + BigInt? totalSupply}) { + if (metadata != null && contect != null) { + throw const TonContractException( + "Use only content or metadata for jetton content"); + } + return StableTokenMinterState._( + adminAddress: adminAddress, + nextAdminAddress: transferAdminAddress, + totalSupply: totalSupply ?? BigInt.zero, + walletCode: walletCode, + content: contect ?? TokneMetadataUtils.encodeMetadata(metadata)); + } + factory StableTokenMinterState.deserialize(Slice slice) { + final BigInt totalSupply = slice.loadCoins(); + final TonAddress adminAddress = slice.loadAddress(); + final TonAddress? nextAdminAddress = slice.loadMaybeAddress(); + final Cell walletCode = slice.loadRef(); + final Cell content = slice.loadRef(); + return StableTokenMinterState._( + adminAddress: adminAddress, + nextAdminAddress: nextAdminAddress, + totalSupply: totalSupply, + walletCode: walletCode, + content: content); + } + + TokenMetadata get metadata => + StableJettonOffChainMetadata.deserialize(content.beginParse()); + + @override + StateInit initialState({int? workchain}) { + return StateInit( + data: initialData(), + code: JettonMinterConst.stableCode(adminAddress.workChain)); + } + + @override + Cell initialData({int? workchain}) { + final Cell code = + walletCode ?? JettonWalletConst.stableCode(adminAddress.workChain); + return beginCell() + .storeCoins(totalSupply) + .storeAddress(adminAddress) + .storeAddress(nextAdminAddress) + .storeRef(code) + .storeRef(content) + .endCell(); + } +} diff --git a/lib/src/contracts/token/ft/types/state/stable_wallet.dart b/lib/src/contracts/token/ft/types/state/stable_wallet.dart new file mode 100644 index 0000000..13e83f1 --- /dev/null +++ b/lib/src/contracts/token/ft/types/state/stable_wallet.dart @@ -0,0 +1,129 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant/wallet.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; +import 'package:ton_dart/src/tuple/tuple/tuple_reader.dart'; + +class StableTokenWalletStatus { + final int id; + final String name; + const StableTokenWalletStatus._({required this.id, required this.name}); + static const StableTokenWalletStatus statusUnlock = + StableTokenWalletStatus._(id: 0, name: "Unlock"); + static const StableTokenWalletStatus statusOut = + StableTokenWalletStatus._(id: 1, name: "Out"); + static const StableTokenWalletStatus statusIn = + StableTokenWalletStatus._(id: 2, name: "In"); + static const StableTokenWalletStatus statusFull = + StableTokenWalletStatus._(id: 3, name: "Full"); + + static const List values = [ + statusUnlock, + statusOut, + statusIn, + statusFull + ]; + static StableTokenWalletStatus fromTag(int? tag) { + return values.firstWhere( + (e) => e.id == tag, + orElse: () => throw TonContractException( + "Invalid stable token wallet status.", + details: { + "tag": tag, + "availableTags": values.map((e) => e.id).join(", ") + }), + ); + } + + static StableTokenWalletStatus fromName(String? name) { + return values.firstWhere( + (e) => e.name == name, + orElse: () => throw TonContractException( + "Invalid stable token wallet status.", + details: { + "name": name, + "availableName": values.map((e) => e.name).join(", ") + }), + ); + } + + @override + String toString() { + return "StableTokenWalletStatus.$name"; + } +} + +class StableJettonWalletState extends ContractState { + final StableTokenWalletStatus? status; + final BigInt balance; + final TonAddress ownerAddress; + final TonAddress jettonMasterAddress; + final Cell? walletCode; + const StableJettonWalletState( + {required this.balance, + required this.ownerAddress, + required this.jettonMasterAddress, + this.status, + this.walletCode}); + Map toJson() { + return { + "status": status?.name, + "balance": balance.toString(), + "ownerAddress": ownerAddress.toRawAddress(), + "jettonMasterAddress": jettonMasterAddress.toRawAddress(), + "walletCode": walletCode?.toBase64() + }; + } + + factory StableJettonWalletState.fromJson(Map json) { + return StableJettonWalletState( + balance: BigintUtils.parse(json["balance"]), + ownerAddress: TonAddress(json["ownerAddress"]), + jettonMasterAddress: TonAddress(json["jettonMasterAddress"]), + walletCode: json["walletCode"] == null + ? null + : Cell.fromBase64(json["walletCode"]), + status: json["status"] == null + ? null + : StableTokenWalletStatus.fromName(json["status"])); + } + + factory StableJettonWalletState.fromTuple(TupleReader reader) { + final balance = reader.readBigNumber(); + return StableJettonWalletState( + balance: balance, + ownerAddress: reader.readAddress(), + jettonMasterAddress: reader.readAddress(), + walletCode: reader.readCell()); + } + factory StableJettonWalletState.deserialize(Slice slice) { + return StableJettonWalletState( + status: StableTokenWalletStatus.fromTag(slice.loadUint4()), + balance: slice.loadCoins(), + ownerAddress: slice.loadAddress(), + jettonMasterAddress: slice.loadAddress()); + } + + @override + StateInit initialState() { + final Cell code = + walletCode ?? JettonWalletConst.stableCode(ownerAddress.workChain); + return StateInit(data: initialData(), code: code); + } + + @override + Cell initialData() { + final Cell code = + walletCode ?? JettonWalletConst.stableCode(ownerAddress.workChain); + return beginCell() + .storeUint4(0) + .storeCoins(BigInt.zero) + .storeAddress(ownerAddress) + .storeAddress(jettonMasterAddress) + .storeRef(code) + .endCell(); + } +} diff --git a/lib/src/contracts/token/ft/types/state/wallet.dart b/lib/src/contracts/token/ft/types/state/wallet.dart new file mode 100644 index 0000000..fc3ee58 --- /dev/null +++ b/lib/src/contracts/token/ft/types/state/wallet.dart @@ -0,0 +1,52 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/token/ft/constants/constant/wallet.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; +import 'package:ton_dart/src/tuple/tuple/tuple_reader.dart'; + +class JettonWalletState extends ContractState { + final BigInt balance; + final TonAddress owanetOfJettonWallet; + final TonAddress minterAddress; + final Cell? walletCode; + const JettonWalletState( + {required this.balance, + required this.owanetOfJettonWallet, + required this.minterAddress, + this.walletCode}); + factory JettonWalletState.fromTuple(TupleReader reader) { + final balance = reader.readBigNumber(); + return JettonWalletState( + balance: balance, + owanetOfJettonWallet: reader.readAddress(), + minterAddress: reader.readAddress(), + walletCode: reader.readCell()); + } + factory JettonWalletState.deserialize(Slice slice) { + return JettonWalletState( + balance: slice.loadCoins(), + owanetOfJettonWallet: slice.loadAddress(), + minterAddress: slice.loadAddress(), + walletCode: slice.loadRef()); + } + + @override + StateInit initialState() { + final Cell code = + walletCode ?? JettonWalletConst.code(minterAddress.workChain); + return StateInit(data: initialData(), code: code); + } + + @override + Cell initialData() { + final Cell code = + walletCode ?? JettonWalletConst.code(minterAddress.workChain); + return beginCell() + .storeCoins(BigInt.zero) + .storeAddress(owanetOfJettonWallet) + .storeAddress(minterAddress) + .storeRef(code) + .endCell(); + } +} diff --git a/lib/src/contracts/token/ft/types/types.dart b/lib/src/contracts/token/ft/types/types.dart new file mode 100644 index 0000000..d00d7d0 --- /dev/null +++ b/lib/src/contracts/token/ft/types/types.dart @@ -0,0 +1,7 @@ +export 'models/stable_minter_data.dart'; +export 'operations/jetton.dart'; +export 'operations/stable_jetton.dart'; +export 'state/minter.dart'; +export 'state/stable_minter.dart'; +export 'state/stable_wallet.dart'; +export 'state/wallet.dart'; diff --git a/lib/src/contracts/token/ft/wallet/constants/contants.dart b/lib/src/contracts/token/ft/wallet/constants/contants.dart deleted file mode 100644 index b00f110..0000000 --- a/lib/src/contracts/token/ft/wallet/constants/contants.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:blockchain_utils/utils/utils.dart'; -import 'package:ton_dart/src/boc/boc.dart'; - -class JettonWalletConst { - static const String _code = - "b5ee9c7241021201000334000114ff00f4a413f4bcf2c80b010201620302001ba0f605da89a1f401f481f481a8610202cc0f04020148080502012007060083200835c87b51343e803e903e90350c0134c7e08405e3514654882ea0841ef765f784ee84ac7cb8b174cfcc7e800c04e81408f214013e809633c58073c5b3327b552000db3b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b0a0823938702a8005a805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b55200201200d0903f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401c1d3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a020822625a004ad8228608239387028062849f8c3c975c2c070c008e00c0b0a0076c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54000e10491038375f0400705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb001024102301f100f4cffe803e90087c007b51343e803e903e90350c144da8548ab1c17cb8b04a30bffcb8b0950d109c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c032483e401c1d3232c0b281f2fff274013e903d010c7e800835d270803cb8b11de0063232c1540233c59c3e8085f2dac4f3200e00ae8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08208e4e1c0aa008208989680a0a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed540201d4111000113e910c1c2ebcb8536000c30831c02497c138007434c0c05c6c2544d7c0fc02f83e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0238208405e3514654882ea58c511100fc02780d60841657c1ef2ea4d67c02b817c12103fcbc204479e250"; - static const String _testnetWorkChain = - "b5ee9c7241021201000334000114ff00f4a413f4bcf2c80b010201620302001ba0f605da89a1f401f481f481a8610202cc0f04020148080502012007060083200835c87b51343e803e903e90350c0134c7e08405e3514654882ea0841ef765f784ee84ac7cb8b174cfcc7e800c04e81408f214013e809633c58073c5b3327b552000db3b51343e803e903e90350c01f4cffe803e900c145468549271c17cb8b049f0bffcb8b0a0823938702a8005a805af3cb8b0e0841ef765f7b232c7c572cfd400fe8088b3c58073c5b25c60063232c14933c59c3e80b2dab33260103ec01004f214013e809633c58073c5b3327b55200201200d0903f73b51343e803e903e90350c0234cffe80145468017e903e9014d6f1c1551cdb5c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c0327e401fdd3232c0b281f2fff274140371c1472c7cb8b0c2be80146a2860822625a020822625a004ad8228608239387028062849f8c3c975c2c070c008e00c0b0a0076c200b08e218210d53276db708010c8cb055008cf165004fa0216cb6a12cb1f12cb3fc972fb0093356c21e203c85004fa0258cf1601cf16ccc9ed54000e10491038375f0400705279a018a182107362d09cc8cb1f5230cb3f58fa025007cf165007cf16c9718010c8cb0524cf165006fa0215cb6a14ccc971fb001024102301f100f4cffe803e90087c007b51343e803e903e90350c144da8548ab1c17cb8b04a30bffcb8b0950d109c150804d50500f214013e809633c58073c5b33248b232c044bd003d0032c032483e401fdd3232c0b281f2fff274013e903d010c7e800835d270803cb8b11de0063232c1540233c59c3e8085f2dac4f3200e00ae8210178d4519c8cb1f19cb3f5007fa0222cf165006cf1625fa025003cf16c95005cc2391729171e25008a813a08208e4e1c0aa008208989680a0a014bcf2e2c504c98040fb001023c85004fa0258cf1601cf16ccc9ed540201d4111000113e910c1feebcb8536000c30831c02497c138007434c0c05c6c2544d7c0fc02f83e903e900c7e800c5c75c87e800c7e800c1cea6d0000b4c7e08403e29fa954882ea54c4d167c0238208405e3514654882ea58c511100fc02780d60841657c1ef2ea4d67c02b817c12103fcbc207dada3a3"; - static Cell code(int workchain) { - if (workchain < 0) { - return Cell.fromBytes(BytesUtils.fromHexString(_testnetWorkChain)); - } - return Cell.fromBytes(BytesUtils.fromHexString(_code)); - } - - static const int transfer = 0xf8a7ea5; - static const int burn = 0x595f07bc; -} diff --git a/lib/src/contracts/token/ft/wallet/contract/contract.dart b/lib/src/contracts/token/ft/wallet/contract/contract.dart deleted file mode 100644 index d733b01..0000000 --- a/lib/src/contracts/token/ft/wallet/contract/contract.dart +++ /dev/null @@ -1,310 +0,0 @@ -import 'package:ton_dart/src/address/address.dart'; -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/token/ft/wallet/models/jetton_transfer_params.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/token/ft/wallet/constants/contants.dart'; -import 'package:ton_dart/src/contracts/token/ft/wallet/utils/utils.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; - -class JettonWallet extends TonContract { - final VersonedWalletContract owner; - - @override - final TonAddress address; - - @override - final StateInit state; - - const JettonWallet( - {required this.owner, required this.state, required this.address}); - factory JettonWallet.fromAddress( - {required TonAddress jettonWalletAddress, - required VersonedWalletContract owner}) { - return JettonWallet( - owner: owner, - state: JettonWalletUtils.buildState(owner.workChain), - address: jettonWalletAddress); - } - - @override - Cell code(int workchain) { - return JettonWalletConst.code(workchain); - } - - @override - Cell data(params, int workchain) { - return beginCell().endCell(); - } - - Future _sendTransaction( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - StateInit? state, - OnEstimateFee? onEstimateFees}) async { - final message = TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable); - return await owner.sendTransfer( - messages: [message, ...messages], - privateKey: privateKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode, - onEstimateFee: onEstimateFees); - } - - Future deploy( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - OnEstimateFee? onEstimateFees}) async { - final active = await isActive(rpc); - if (active) { - throw TonContractException("Account is already active."); - } - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: body, - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout); - } - - Cell transferMessageBody( - {required BigInt jettonAmount, - required TonAddress destination, - required TonAddress responseDestination, - required BigInt forwardTonAmount, - Cell? customPayload, - Cell? forwardPayload}) { - return beginCell() - .storeUint(JettonWalletConst.transfer, 32) - .storeUint(0, 64) // op, queryId - .storeCoins(jettonAmount) - .storeAddress(destination) - .storeAddress(responseDestination) - .storeMaybeRef(cell: customPayload) - .storeCoins(forwardTonAmount) - .storeMaybeRef(cell: forwardPayload) - .endCell(); - } - - Cell burnMessageBody( - {required BigInt burnAmount, - required TonAddress from, - Cell? customPayload}) { - return beginCell() - .storeUint(JettonWalletConst.burn, 32) - .storeUint(0, 64) // op, queryId - .storeCoins(burnAmount) - .storeAddress(from) - .storeMaybeRef(cell: customPayload) - .endCell(); - } - - Cell withdrawTonsMessageBody() { - return beginCell().storeUint(0x6d8e5e3c, 32).storeUint(0, 64).endCell(); - } - - Future transfer( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required TonAddress destination, - required BigInt forwardTonAmount, - required BigInt jettonAmount, - required BigInt amount, - Cell? customPayload, - Cell? forwardPayload, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - OnEstimateFee? onEstimateFees}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: transferMessageBody( - responseDestination: owner.address, - destination: destination, - forwardTonAmount: forwardTonAmount, - jettonAmount: jettonAmount, - customPayload: customPayload, - forwardPayload: forwardPayload), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFees: onEstimateFees); - } - - Future multipleTransfer( - {required List transfers, - required TonPrivateKey signerKey, - required TonProvider rpc, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - OnEstimateFee? onEstimateFees}) async { - final jettonMessages = transfers.map((e) => TransactioUtils.internal( - destination: address, - amount: e.amount, - initState: state, - body: transferMessageBody( - responseDestination: owner.address, - destination: e.destination, - forwardTonAmount: e.forwardTonAmount, - jettonAmount: e.jettonAmount, - customPayload: e.customPayload, - forwardPayload: e.forwardPayload), - bounce: e.destination.isBounceable)); - return await owner.sendTransfer( - messages: [...jettonMessages, ...messages], - privateKey: signerKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode, - onEstimateFee: onEstimateFees); - } - - Future burn( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt burnJettonAmount, - required BigInt amount, - Cell? customPayload, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - OnEstimateFee? onEstimateFees}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: burnMessageBody( - from: owner.address, - customPayload: customPayload, - burnAmount: burnJettonAmount), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFees: onEstimateFees); - } - - Future withdrawTons({ - required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - OnEstimateFee? onEstimateFees, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - }) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: withdrawTonsMessageBody(), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFees: onEstimateFees); - } - - Future withdrawJettons({ - required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt withdrawAmount, - required BigInt amount, - required TonAddress from, - List messages = const [], - OnEstimateFee? onEstimateFees, - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - }) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: withdrawJettonsMessageBody(from: from, amount: withdrawAmount), - bounce: bounce, - bounced: bounced, - messages: messages, - timeout: timeout, - onEstimateFees: onEstimateFees); - } - - Cell withdrawJettonsMessageBody( - {required TonAddress from, required BigInt amount}) { - return beginCell() - .storeUint(0x768a50b2, 32) - .storeUint(0, 64) - .storeAddress(from) - .storeCoins(amount) - .storeMaybeRef() - .endCell(); - } - - Future getBalance(TonProvider rpc) async { - final data = await getStateStack(rpc: rpc, method: "get_wallet_data"); - final reader = data.reader(); - final balance = reader.readBigNumber(); - return balance; - } - - Future getWalletAddress( - {required TonProvider rpc, required TonAddress owner}) async { - final data = - await getStateStack(rpc: rpc, method: "get_wallet_address", stack: [ - if (rpc.isTonCenter) - ["tvm.Slice", beginCell().storeAddress(owner).endCell().toBase64()] - else - owner.toString() - ]); - return data.reader().readAddress(); - } - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/token/ft/wallet/models/jetton_transfer_params.dart b/lib/src/contracts/token/ft/wallet/models/jetton_transfer_params.dart deleted file mode 100644 index fbe071b..0000000 --- a/lib/src/contracts/token/ft/wallet/models/jetton_transfer_params.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:ton_dart/ton_dart.dart'; - -class JettonTransferParams { - final TonAddress destination; - final BigInt forwardTonAmount; - final BigInt jettonAmount; - final BigInt amount; - final Cell? customPayload; - final Cell? forwardPayload; - const JettonTransferParams( - {required this.destination, - required this.forwardTonAmount, - required this.jettonAmount, - required this.amount, - this.customPayload, - this.forwardPayload}); -} diff --git a/lib/src/contracts/token/ft/wallet/utils/utils.dart b/lib/src/contracts/token/ft/wallet/utils/utils.dart deleted file mode 100644 index e923d7b..0000000 --- a/lib/src/contracts/token/ft/wallet/utils/utils.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/contracts/token/ft/wallet/constants/contants.dart'; - -class JettonWalletUtils { - static StateInit buildState(int workchain) { - return StateInit( - code: JettonWalletConst.code(workchain), data: beginCell().endCell()); - } -} diff --git a/lib/src/contracts/token/ft/wallet/wallet.dart b/lib/src/contracts/token/ft/wallet/wallet.dart deleted file mode 100644 index 4674f17..0000000 --- a/lib/src/contracts/token/ft/wallet/wallet.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'contract/contract.dart'; -export 'models/jetton_transfer_params.dart'; diff --git a/lib/src/contracts/token/metadata/core/metadata.dart b/lib/src/contracts/token/metadata/core/metadata.dart index 53b50d8..c829bda 100644 --- a/lib/src/contracts/token/metadata/core/metadata.dart +++ b/lib/src/contracts/token/metadata/core/metadata.dart @@ -1,15 +1,43 @@ -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/exception/exception.dart'; +import 'package:ton_dart/ton_dart.dart'; enum TokenContentType { unknown, offchain, onchain } abstract class TokenMetadata with JsonSerialization { TokenContentType get type; - Cell encode(); + const TokenMetadata(); + Cell toContent(); + T cast() { + if (this is! T) { + throw TonDartPluginException( + "Invalid token metadata casting. excepted: $runtimeType got: $T"); + } + return this as T; + } } abstract class NFTMetadata extends TonSerialization { const NFTMetadata(); + Cell toContent({bool collectionless = false}); + factory NFTMetadata.deserialize(Slice slice) { + try { + if (slice.remainingRefs == 0) { + return NFTItemMetadata.deserialize(slice); + } + if (slice.remainingRefs == 2) { + return NFTCollectionMetadata.deserialize(slice); + } + } catch (_) {} + return NFTRawMetadata(slice.asCell()); + } + T cast() { + if (this is! T) { + throw TonContractException( + "Invalid metadata casting. excepted: $runtimeType got: $T"); + } + return this as T; + } } enum OnChainMetadataFormat { snake, chunked } diff --git a/lib/src/contracts/token/metadata/metadata.dart b/lib/src/contracts/token/metadata/metadata.dart index 7cfd916..c59a2b3 100644 --- a/lib/src/contracts/token/metadata/metadata.dart +++ b/lib/src/contracts/token/metadata/metadata.dart @@ -1,7 +1,8 @@ export 'core/metadata.dart'; -export 'models/ft_token_metadata.dart'; -export 'models/token_metadata.dart'; +export 'models/on_chain.dart'; +export 'models/off_chain.dart'; export 'models/nft_collection.dart'; export 'models/nft_item_metadata.dart'; export 'models/nft_raw_metadata.dart'; export 'utils/metadata.dart'; +export 'models/of_chain_stable_token_metadata.dart'; diff --git a/lib/src/contracts/token/metadata/models/nft_collection.dart b/lib/src/contracts/token/metadata/models/nft_collection.dart index 68fe4c3..7a4aabb 100644 --- a/lib/src/contracts/token/metadata/models/nft_collection.dart +++ b/lib/src/contracts/token/metadata/models/nft_collection.dart @@ -1,3 +1,4 @@ +import 'package:ton_dart/src/contracts/exception/exception.dart'; import 'package:ton_dart/src/contracts/token/metadata/constant/constant.dart'; import 'package:ton_dart/ton_dart.dart'; @@ -11,27 +12,42 @@ class NFTCollectionMetadata extends NFTMetadata { const NFTCollectionMetadata( {required this.collectionMetadataUri, this.collectionBase}); - /// B B, b> ref, - /// b> ref, // content cell + factory NFTCollectionMetadata.deserialize(Slice slice) { + final collectionCell = slice.loadRef().beginParse(); + final tag = collectionCell.loadUint8(); + if (tag != TonMetadataConstant.ftMetadataOffChainTag) { + throw const TonContractException( + "Invalid nft offchain collection metadata"); + } + final String collectionMetadataUri = collectionCell.loadStringTail(); + final Slice commonCell = slice.loadRef().beginParse(); + return NFTCollectionMetadata( + collectionMetadataUri: collectionMetadataUri, + collectionBase: commonCell.loadStringTail()); + } + @override void store(Builder builder) { - final collentionCell = beginCell(); - collentionCell.storeUint(TonMetadataConstant.ftMetadataOffChainTag, 8); - collentionCell.storeStringTail(collectionMetadataUri); - final content = beginCell().storeRef(collentionCell.endCell()); - final commonCell = beginCell(); - commonCell.storeStringTail(collectionBase ?? ""); - content.storeRef(commonCell.endCell()); - builder.storeRef(content.endCell()); + builder.storeRef(toContent()); } @override Map toJson() { return { - "coolection_content": collectionMetadataUri, - "common_content": collectionBase + "collection": collectionMetadataUri, + "collection_base": collectionBase }; } + + @override + Cell toContent({bool collectionless = false}) { + final collection = beginCell(); + collection.storeUint(TonMetadataConstant.ftMetadataOffChainTag, 8); + collection.storeStringTail(collectionMetadataUri); + final content = beginCell().storeRef(collection.endCell()); + final commonCell = beginCell(); + commonCell.storeStringTail(collectionBase ?? ""); + content.storeRef(commonCell.endCell()); + return content.endCell(); + } } diff --git a/lib/src/contracts/token/metadata/models/nft_item_metadata.dart b/lib/src/contracts/token/metadata/models/nft_item_metadata.dart index 7dd1d24..5647543 100644 --- a/lib/src/contracts/token/metadata/models/nft_item_metadata.dart +++ b/lib/src/contracts/token/metadata/models/nft_item_metadata.dart @@ -4,19 +4,35 @@ import 'package:ton_dart/ton_dart.dart'; class NFTItemMetadata extends NFTMetadata { final String uri; const NFTItemMetadata(this.uri); + factory NFTItemMetadata.deserialize(Slice slice) { + if (slice.remainingBits == 0) { + return const NFTItemMetadata(""); + } + final tag = slice.preloadUint(8); + if (tag != TonMetadataConstant.ftMetadataOffChainTag) { + return NFTItemMetadata(slice.loadStringTail()); + } + slice.loadUint8(); + return NFTItemMetadata(slice.loadStringTail()); + } @override void store(Builder builder, {bool collectionLess = false}) { - final content = beginCell(); - if (collectionLess) { - content.storeUint8(TonMetadataConstant.ftMetadataOffChainTag); - } - content.storeStringTail(uri); - builder.storeRef(content.endCell()); + builder.storeRef(toContent(collectionless: collectionLess)); } @override Map toJson() { return {"uri": uri}; } + + @override + Cell toContent({bool collectionless = false}) { + final content = beginCell(); + if (collectionless) { + content.storeUint8(TonMetadataConstant.ftMetadataOffChainTag); + } + content.storeStringTail(uri); + return content.endCell(); + } } diff --git a/lib/src/contracts/token/metadata/models/nft_raw_metadata.dart b/lib/src/contracts/token/metadata/models/nft_raw_metadata.dart index c7851b9..8edad11 100644 --- a/lib/src/contracts/token/metadata/models/nft_raw_metadata.dart +++ b/lib/src/contracts/token/metadata/models/nft_raw_metadata.dart @@ -15,4 +15,9 @@ class NFTRawMetadata extends NFTMetadata { Map toJson() { return {"content": content.toBase64()}; } + + @override + Cell toContent({bool collectionless = false}) { + return content; + } } diff --git a/lib/src/contracts/token/metadata/models/of_chain_stable_token_metadata.dart b/lib/src/contracts/token/metadata/models/of_chain_stable_token_metadata.dart new file mode 100644 index 0000000..9cdec2a --- /dev/null +++ b/lib/src/contracts/token/metadata/models/of_chain_stable_token_metadata.dart @@ -0,0 +1,25 @@ +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/token/metadata/core/metadata.dart'; + +class StableJettonOffChainMetadata extends TokenMetadata { + final String uri; + const StableJettonOffChainMetadata(this.uri); + factory StableJettonOffChainMetadata.deserialize(Slice slice) { + return StableJettonOffChainMetadata(slice.loadStringTail()); + } + + @override + TokenContentType get type => TokenContentType.offchain; + + @override + Map toJson() { + return {"uri": uri}; + } + + @override + Cell toContent() { + final cell = beginCell(); + cell.storeStringTail(uri); + return cell.endCell(); + } +} diff --git a/lib/src/contracts/token/metadata/models/token_metadata.dart b/lib/src/contracts/token/metadata/models/off_chain.dart similarity index 84% rename from lib/src/contracts/token/metadata/models/token_metadata.dart rename to lib/src/contracts/token/metadata/models/off_chain.dart index 1bf8cb1..89d104e 100644 --- a/lib/src/contracts/token/metadata/models/token_metadata.dart +++ b/lib/src/contracts/token/metadata/models/off_chain.dart @@ -2,7 +2,7 @@ import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/contracts/token/metadata/core/metadata.dart'; import 'package:ton_dart/src/contracts/token/metadata/utils/metadata.dart'; -class JettonRawMetadata implements TokenMetadata { +class JettonRawMetadata extends TokenMetadata { final Cell content; const JettonRawMetadata(this.content); @override @@ -14,12 +14,12 @@ class JettonRawMetadata implements TokenMetadata { } @override - Cell encode() { + Cell toContent() { return content; } } -class JettonOffChainMetadata implements TokenMetadata { +class JettonOffChainMetadata extends TokenMetadata { final String uri; const JettonOffChainMetadata(this.uri); @@ -32,7 +32,7 @@ class JettonOffChainMetadata implements TokenMetadata { } @override - Cell encode() { + Cell toContent() { return TokneMetadataUtils.createJettonOffChainMetadata(uri); } } diff --git a/lib/src/contracts/token/metadata/models/ft_token_metadata.dart b/lib/src/contracts/token/metadata/models/on_chain.dart similarity index 98% rename from lib/src/contracts/token/metadata/models/ft_token_metadata.dart rename to lib/src/contracts/token/metadata/models/on_chain.dart index 98f3409..5c409d1 100644 --- a/lib/src/contracts/token/metadata/models/ft_token_metadata.dart +++ b/lib/src/contracts/token/metadata/models/on_chain.dart @@ -34,7 +34,7 @@ class _JettonOnChainMetadataUtils { } } -class JettonOnChainMetadata with JsonSerialization implements TokenMetadata { +class JettonOnChainMetadata extends TokenMetadata with JsonSerialization { /// Optional. Used by "Semi-chain content layout". ASCII string. A URI pointing to JSON document with metadata. final String? uri; @@ -168,7 +168,7 @@ class JettonOnChainMetadata with JsonSerialization implements TokenMetadata { TokenContentType get type => TokenContentType.onchain; @override - Cell encode() { + Cell toContent() { if (dataFormat == OnChainMetadataFormat.chunked) { return TokneMetadataUtils.createOnChainContentChunckedFormat( Map>.from(content)); diff --git a/lib/src/contracts/token/metadata/utils/metadata.dart b/lib/src/contracts/token/metadata/utils/metadata.dart index da2ab79..f3b7c11 100644 --- a/lib/src/contracts/token/metadata/utils/metadata.dart +++ b/lib/src/contracts/token/metadata/utils/metadata.dart @@ -4,8 +4,8 @@ import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/contracts/token/metadata/constant/constant.dart'; import 'package:ton_dart/src/contracts/token/metadata/core/metadata.dart'; import 'package:ton_dart/src/contracts/token/metadata/exception/exception.dart'; -import 'package:ton_dart/src/contracts/token/metadata/models/ft_token_metadata.dart'; -import 'package:ton_dart/src/contracts/token/metadata/models/token_metadata.dart'; +import 'package:ton_dart/src/contracts/token/metadata/models/on_chain.dart'; +import 'package:ton_dart/src/contracts/token/metadata/models/off_chain.dart'; import 'package:ton_dart/src/dict/dictionary.dart'; class TokneMetadataUtils { @@ -29,26 +29,27 @@ class TokneMetadataUtils { } /// load metadata - static TokenMetadata? loadContent(Cell content) { - final slice = content.beginParse(); - final type = slice.loadUint8(); - if (type == TonMetadataConstant.ftMetadataOffChainTag) { - if (slice.remainingBits ~/ 8 != 0) { - return JettonOffChainMetadata(slice.loadStringTail()); + static TokenMetadata loadContent(Cell content) { + try { + final slice = content.beginParse(); + final type = slice.loadUint8(); + if (type == TonMetadataConstant.ftMetadataOffChainTag) { + if (slice.remainingBits ~/ 8 != 0) { + return JettonOffChainMetadata(slice.loadStringTail()); + } + } else if (type == TonMetadataConstant.ftMetadataOnChainTag) { + final dict = _onChainMetadataDict({}); + dict.loadFromClice(slice); + final result = dict.asMap + .map((key, value) => MapEntry(BytesUtils.toHexString(key), value)); + return JettonOnChainMetadata.fromJson(result); } - return null; - } else if (type == TonMetadataConstant.ftMetadataOnChainTag) { - final dict = _onChainMetadataDict({}); - dict.loadFromClice(slice); - final result = dict.asMap - .map((key, value) => MapEntry(BytesUtils.toHexString(key), value)); - return JettonOnChainMetadata.fromJson(result); - } + } catch (_) {} return JettonRawMetadata(content); } static Cell encodeMetadata(TokenMetadata? metadata) { - if (metadata != null) return metadata.encode(); + if (metadata != null) return metadata.toContent(); final cell = beginCell(); cell.storeUint(TonMetadataConstant.ftMetadataOffChainTag, 8); return cell.endCell(); diff --git a/lib/src/contracts/token/nft/constant/constant.dart b/lib/src/contracts/token/nft/constant/constant.dart index 1124358..25d9c83 100644 --- a/lib/src/contracts/token/nft/constant/constant.dart +++ b/lib/src/contracts/token/nft/constant/constant.dart @@ -1,6 +1,6 @@ import 'package:ton_dart/src/boc/boc.dart'; -class TomNftConst { +class TonNftConst { static const int mintNFtOperationId = 1; static const int batchMintNFtOperationId = 2; static const int changeCollectionOwnerOperationId = 3; diff --git a/lib/src/contracts/token/nft/contracts/contracts.dart b/lib/src/contracts/token/nft/contracts/contracts.dart index 6a95e6e..3c65f68 100644 --- a/lib/src/contracts/token/nft/contracts/contracts.dart +++ b/lib/src/contracts/token/nft/contracts/contracts.dart @@ -1,3 +1,3 @@ -export 'nft_contracts/nft_collection.dart'; -export 'nft_contracts/nft_collection_editable.dart'; -export 'nft_contracts/nft_item.dart'; +export 'nft/collection.dart'; +export 'nft/editable.dart'; +export 'nft/item.dart'; diff --git a/lib/src/contracts/token/nft/contracts/nft/collection.dart b/lib/src/contracts/token/nft/contracts/nft/collection.dart new file mode 100644 index 0000000..44194f4 --- /dev/null +++ b/lib/src/contracts/token/nft/contracts/nft/collection.dart @@ -0,0 +1,209 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/metadata/constant/constant.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/contracts/token/nft/types/types.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/provider/provider/provider.dart'; +import 'item.dart'; + +class NFTCollectionContract + extends TonContract { + final WalletContract owner; + @override + final TonAddress address; + @override + final NftCollectionState? state; + + const NFTCollectionContract( + {required this.owner, required this.address, this.state}); + + factory NFTCollectionContract.create( + {required WalletContract owner, + required NftCollectionState state}) { + return NFTCollectionContract( + owner: owner, + address: TonAddress.fromState( + state: state.initialState(), workChain: owner.address.workChain), + state: state); + } + static Future + fromAddress( + {required WalletContract owner, + required TonAddress address, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(address: address, rpc: rpc); + final state = NftCollectionState.deserialize(stateData.data!.beginParse()); + return NFTCollectionContract(owner: owner, address: address, state: state); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + final active = await isActive(rpc); + if (!active && state == null) { + throw const TonContractException( + "The account is inactive and requires state initialization."); + } + + final message = TransactioUtils.internal( + destination: address, + amount: amount, + initState: active ? null : this.state!.initialState(), + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ); + return await owner.sendTransfer( + messages: [message], + params: params, + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + ); + } + + Future deploy( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: body, + bounce: bounce, + bounced: bounced, + timeout: timeout); + } + + Future sendOperation( + {required E params, + required TonProvider rpc, + required BigInt amount, + required NFTCollectionOperation operation, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + if (operation.type == NFTCollectionOperationType.changeContent) { + throw const TonContractException( + "The ChangeContent operation is not available in the NFTCollectionContract."); + } + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout); + } + + Future getCollectionData(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "get_collection_data"); + final reader = data.reader(); + final nexItemIndex = reader.readBigNumber(); + final content = reader.readCell(); + final ownerAddress = reader.readAddress(); + + NFTMetadata metadata = NFTRawMetadata(content); + try { + final contentSlice = content.beginParse(); + if (contentSlice.loadUint8() == + TonMetadataConstant.ftMetadataOffChainTag) { + metadata = NFTCollectionMetadata( + collectionMetadataUri: contentSlice.loadStringTail()); + } else { + metadata = NFTRawMetadata(content); + } + // ignore: empty_catches + } catch (e) { + metadata = NFTRawMetadata(content); + } + + return NFTCollectionData( + nexItemIndex: nexItemIndex, + content: metadata, + ownerAddress: ownerAddress); + } + + Future getNftAddressByIndex( + {required BigInt index, required TonProvider rpc}) async { + final result = await getStateStack( + rpc: rpc, + method: "get_nft_address_by_index", + stack: [ + if (rpc.isTonCenter) ["num", index.toString()] else index.toString() + ]); + final reader = result.reader(); + return reader.readAddress(); + } + + Future> + getNFTItemContractByIndex( + {required BigInt index, + required TonProvider rpc, + required WalletContract owner}) async { + final itemAddress = await getNftAddressByIndex(index: index, rpc: rpc); + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: itemAddress); + return NFTItemContract( + address: itemAddress, + owner: owner, + state: NFTItemState.deserialize(stateData.data!.beginParse())); + } + + Future royaltyParams(TonProvider rpc) async { + final data = await getStateStack(rpc: rpc, method: "royalty_params"); + final reader = data.reader(); + final int royaltyFactor = reader.readNumber(); + final int royaltyBase = reader.readNumber(); + final TonAddress address = reader.readAddress(); + return RoyaltyParams( + royaltyFactor: royaltyFactor, + royaltyBase: royaltyBase, + address: address); + } + + Future getNftContent( + {required TonProvider rpc, required NFTItemData nftData}) async { + if (!nftData.init) { + throw const TonContractException("The NFT has not been initialized."); + } + final data = + await getStateStack(rpc: rpc, method: "get_nft_content", stack: [ + if (rpc.isTonCenter) ...[ + ["num", nftData.index.toString()], + ["tvm.Cell", nftData.content!.toBase64()] + ] else ...[ + nftData.index, + nftData.content?.toBase64(), + ] + ]); + final reader = data.reader(); + final slice = reader.readCell().beginParse(); + slice.loadUint8(); + return slice.loadStringTail(); + } +} diff --git a/lib/src/contracts/token/nft/contracts/nft/editable.dart b/lib/src/contracts/token/nft/contracts/nft/editable.dart new file mode 100644 index 0000000..5e5a575 --- /dev/null +++ b/lib/src/contracts/token/nft/contracts/nft/editable.dart @@ -0,0 +1,99 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/nft/types/types.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'collection.dart'; + +class NFTCollectionEditableContract + extends NFTCollectionContract { + const NFTCollectionEditableContract( + {required WalletContract owner, + required TonAddress address, + NftEditableCollectionState? state}) + : super( + owner: owner, + address: address, + state: state, + ); + + factory NFTCollectionEditableContract.create({ + required WalletContract owner, + required NftEditableCollectionState state, + }) { + return NFTCollectionEditableContract( + owner: owner, + address: TonAddress.fromState( + state: state.initialState(), workChain: owner.address.workChain), + state: state); + } + static Future + fromAddress( + {required WalletContract owner, + required TonAddress address, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + NftEditableCollectionState.deserialize(stateData.data!.beginParse()); + return NFTCollectionEditableContract( + owner: owner, address: address, state: state); + } + + Future _sendTransaction({ + required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + }) async { + final active = await isActive(rpc); + if (!active && state == null) { + throw const TonContractException( + "The account is inactive and requires state initialization."); + } + final message = TransactioUtils.internal( + destination: address, + amount: amount, + initState: active ? null : state!.initialState(), + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ); + return await owner.sendTransfer( + messages: [message], + params: params, + rpc: rpc, + timeout: timeout, + sendMode: sendMode); + } + + @override + Future sendOperation( + {required E params, + required TonProvider rpc, + required BigInt amount, + required NFTCollectionOperation operation, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) { + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout); + } +} diff --git a/lib/src/contracts/token/nft/contracts/nft/item.dart b/lib/src/contracts/token/nft/contracts/nft/item.dart new file mode 100644 index 0000000..4616564 --- /dev/null +++ b/lib/src/contracts/token/nft/contracts/nft/item.dart @@ -0,0 +1,133 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider/provider.dart'; + +/// https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md +class NFTItemContract + extends TonContract { + final WalletContract owner; + @override + final TonAddress address; + @override + final NFTItemState? state; + + factory NFTItemContract.create( + {required WalletContract owner, + required NFTItemState state}) { + return NFTItemContract( + address: TonAddress.fromState( + state: state.initialState(), workChain: owner.address.workChain), + owner: owner, + state: state); + } + static Future + fromAddress( + {required WalletContract owner, + required TonAddress address, + required TonProvider rpc}) async { + final stateData = + await ContractProvider.getActiveState(address: address, rpc: rpc); + final state = NFTItemState.deserialize(stateData.data!.beginParse()); + return NFTItemContract(address: address, owner: owner, state: state); + } + + const NFTItemContract( + {required this.address, required this.owner, this.state}); + + Future _sendTransaction({ + required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + }) async { + final active = await isActive(rpc); + if (!active && state == null) { + throw const TonContractException( + "The account is inactive and requires state initialization."); + } + final message = TransactioUtils.internal( + destination: address, + amount: amount, + initState: active ? null : state!.initialState(), + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ); + return await owner.sendTransfer( + params: params, + messages: [ + message, + ], + rpc: rpc, + timeout: timeout, + sendMode: sendMode); + } + + Future deploy( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: body, + bounce: bounce, + bounced: bounced, + timeout: timeout); + } + + Future sendOperation( + {required E params, + required TonProvider rpc, + required BigInt amount, + required NFTItemOperation operation, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: operation.toBody(), + bounce: bounce, + bounced: bounced, + timeout: timeout); + } + + Future getNftData(TonProvider rpc) async { + final result = await getStateStack(rpc: rpc, method: "get_nft_data"); + final reader = result.reader(); + final bool init = reader.readNumber() != 0; + final index = reader.readBigNumber(); + if (!init) { + return NFTItemData(init: init, index: index); + } + final collectionAddress = reader.readAddressOpt(); + final ownerAddress = reader.readAddressOpt(); + final content = reader.readCell(); + + return NFTItemData( + init: init, + collectionAddress: collectionAddress, + content: content, + ownerAddress: ownerAddress, + index: index); + } +} diff --git a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection.dart b/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection.dart deleted file mode 100644 index 6e1df52..0000000 --- a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection.dart +++ /dev/null @@ -1,278 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/token/metadata/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/contracts/token/nft/models/models.dart'; -import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/nft/utils/utils.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/provider/provider/provider.dart'; - -class NFTCollectionContract extends TonContract { - final VersonedWalletContract ownerWallet; - @override - final TonAddress address; - @override - final StateInit? state; - @override - final int? subWalletId = null; - - const NFTCollectionContract( - {required this.ownerWallet, required this.address, this.state}); - - factory NFTCollectionContract.create( - {required VersonedWalletContract ownerWallet, - required RoyaltyParams royaltyParams, - required NFTMetadata content, - BigInt? nextItemIndex}) { - final state = NftWalletUtils.buildNftCollectionState( - ownerAddress: ownerWallet.address, - royaltyParams: royaltyParams, - content: content, - code: TomNftConst.nftCollectionCode(ownerWallet.workChain), - nextItemIndex: nextItemIndex); - return NFTCollectionContract( - ownerWallet: ownerWallet, - address: TonAddress.fromState( - state: state, workChain: ownerWallet.workChain), - state: state); - } - - @override - Cell code(int workchain) { - return TomNftConst.nftCollectionCode(workchain); - } - - @override - Cell data(NftCollectionParams params, int workchain) { - return params.serialize(workchain: workchain); - } - - Future _sendTransaction( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - StateInit? state}) async { - final message = TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable, - ); - return await ownerWallet.sendTransfer( - messages: [message, ...messages], - privateKey: privateKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode); - } - - Future deploy( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - final active = await isActive(rpc); - if (active) { - throw TonContractException("Account is already active."); - } - if (state == null) { - throw TonContractException( - "For deploy minter please use create constructor to build state"); - } - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: body, - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout); - } - - Future mintNft( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required NFTMintParams params, - BigInt? queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - if (params.initAmount > amount) { - throw TonContractException( - "Amount must be greather than nft init amount"); - } - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.mintNFtOperationId) - .storeUint64(queryId ?? BigInt.zero) - .store(params) - .endCell(), - bounced: bounced, - messages: messages, - timeout: timeout); - } - - Future batchMintNfts( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required BatchNFTsMintParams params, - BigInt? queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - if (params.initializeAmount > amount) { - throw TonContractException( - "Amount must be greather than sum of nfts init amount"); - } - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.batchMintNFtOperationId) - .storeUint64(queryId ?? BigInt.zero) - .store(params) - .endCell(), - bounced: bounced, - messages: messages, - timeout: timeout); - } - - Future changeOwner( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required TonAddress newOwner, - BigInt? queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.changeCollectionOwnerOperationId) - .storeUint64(queryId ?? BigInt.zero) - .storeAddress(newOwner) - .endCell(), - bounced: bounced, - messages: messages, - timeout: timeout); - } - - Future getCollectionData(TonProvider rpc) async { - final data = await getStateStack(rpc: rpc, method: "get_collection_data"); - final reader = data.reader(); - final nexItemIndex = reader.readBigNumber(); - final content = reader.readCell(); - final ownerAddress = reader.readAddress(); - - NFTMetadata metadata = NFTRawMetadata(content); - try { - final contentSlice = content.beginParse(); - if (contentSlice.loadUint8() == - TonMetadataConstant.ftMetadataOffChainTag) { - metadata = NFTCollectionMetadata( - collectionMetadataUri: contentSlice.loadStringTail()); - } else { - metadata = NFTRawMetadata(content); - } - // ignore: empty_catches - } catch (e) { - metadata = NFTRawMetadata(content); - } - - return NFTCollectionData( - nexItemIndex: nexItemIndex, - content: metadata, - ownerAddress: ownerAddress); - } - - Future getNftAddressByIndex( - {required BigInt index, required TonProvider rpc}) async { - final result = await getStateStack( - rpc: rpc, - method: "get_nft_address_by_index", - stack: [ - if (rpc.isTonCenter) ["num", index.toString()] else index.toString() - ]); - final reader = result.reader(); - return reader.readAddress(); - } - - Future royaltyParams(TonProvider rpc) async { - final data = await getStateStack(rpc: rpc, method: "royalty_params"); - final reader = data.reader(); - final int royaltyFactor = reader.readNumber(); - final int royaltyBase = reader.readNumber(); - final TonAddress address = reader.readAddress(); - return RoyaltyParams( - royaltyFactor: royaltyFactor, - royaltyBase: royaltyBase, - address: address); - } - - Future getNftContent({ - required TonProvider rpc, - required NFTItemData nftData, - }) async { - if (!nftData.init) { - throw TonContractException("The NFT has not been initialized."); - } - final data = - await getStateStack(rpc: rpc, method: "get_nft_content", stack: [ - if (rpc.isTonCenter) ...[ - ["num", nftData.index.toString()], - ["tvm.Cell", nftData.content!.toBase64()] - ] else ...[ - nftData.index, - nftData.content?.toBase64(), - ] - ]); - final reader = data.reader(); - final slice = reader.readCell().beginParse(); - slice.loadUint8(); - return slice.loadStringTail(); - } -} diff --git a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection_editable.dart b/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection_editable.dart deleted file mode 100644 index 8a26db5..0000000 --- a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_collection_editable.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/nft/models/models.dart'; -import 'package:ton_dart/src/contracts/token/nft/utils/utils.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'nft_collection.dart'; - -class NFTCollectionEditableContract extends NFTCollectionContract { - const NFTCollectionEditableContract( - {required VersonedWalletContract ownerWallet, - required TonAddress address, - StateInit? state}) - : super(ownerWallet: ownerWallet, address: address, state: state); - - factory NFTCollectionEditableContract.create( - {required VersonedWalletContract ownerWallet, - required RoyaltyParams royaltyParams, - required NFTMetadata content, - BigInt? nextItemIndex}) { - final state = NftWalletUtils.buildNftCollectionState( - ownerAddress: ownerWallet.address, - royaltyParams: royaltyParams, - content: content, - code: TomNftConst.nftEditableCollectionCode(ownerWallet.workChain), - nextItemIndex: nextItemIndex); - return NFTCollectionEditableContract( - ownerWallet: ownerWallet, - address: TonAddress.fromState( - state: state, workChain: ownerWallet.workChain), - state: state); - } - - @override - Cell code(int workchain) { - return TomNftConst.nftEditableCollectionCode(workchain); - } - - @override - Cell data(NftCollectionParams params, int workchain) { - return params.serialize(workchain: workchain); - } - - Future _sendTransaction( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - StateInit? state}) async { - final message = TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable, - ); - return await ownerWallet.sendTransfer( - messages: [message, ...messages], - privateKey: privateKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode); - } - - Future changeContent( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - required UpdateEditableNFTContent newContent, - BigInt? queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - return _sendTransaction( - privateKey: privateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.changeContent) - .storeUint64(queryId ?? BigInt.zero) - .store(newContent) - .endCell(), - bounced: bounced, - messages: messages, - timeout: timeout); - } -} diff --git a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_item.dart b/lib/src/contracts/token/nft/contracts/nft_contracts/nft_item.dart deleted file mode 100644 index 268679a..0000000 --- a/lib/src/contracts/token/nft/contracts/nft_contracts/nft_item.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/contracts.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; -import 'package:ton_dart/src/crypto/crypto.dart'; -import 'package:ton_dart/src/models/models.dart'; -import 'package:ton_dart/src/provider/provider/provider.dart'; - -/// https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md -class NFTItemContract extends TonContract { - final VersonedWalletContract ownerWallet; - @override - final TonAddress address; - @override - final StateInit? state; - - @override - final int? subWalletId = null; - factory NFTItemContract.create({ - required VersonedWalletContract ownerWallet, - required NFTItemParams params, - }) { -// cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { -// cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); -// return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); -// } - final state = StateInit( - code: TomNftConst.nftItemCode(ownerWallet.workChain), - data: params.serialize()); - return NFTItemContract( - address: TonAddress.fromState( - state: state, workChain: ownerWallet.workChain), - ownerWallet: ownerWallet, - state: state); - } - - const NFTItemContract( - {required this.address, required this.ownerWallet, this.state}); - - @override - Cell code(int workchain) { - return TomNftConst.nftItemCode(workchain); - } - - @override - Cell data(NFTItemParams params, int workchain) { - return params.serialize(); - } - - Future _sendTransaction( - {required TonPrivateKey privateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body, - StateInit? state}) async { - final message = TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable, - ); - return await ownerWallet.sendTransfer( - messages: [message, ...messages], - privateKey: privateKey, - rpc: rpc, - timeout: timeout, - sendMode: sendMode); - } - - /// https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#1-transfer - Future transfer( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - required TransferNFTParams params, - BigInt? queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.nftTransferOperationId) - .storeUint64(queryId ?? BigInt.zero) - .store(params) - .endCell(), - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout); - } - - Future deploy( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - final active = await isActive(rpc); - if (active) { - throw TonContractException("Account is already active."); - } - if (state == null) { - throw TonContractException( - "For deploy minter please use create constructor to build state"); - } - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: body, - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout); - } - - /// https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#2-get_static_data - Future getStaticData( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - - /// arbitrary request number. - required BigInt queryId, - List messages = const [], - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - final active = await isActive(rpc); - if (active) { - throw TonContractException("Account is already active."); - } - if (state == null) { - throw TonContractException( - "For deploy minter please use create constructor to build state"); - } - return _sendTransaction( - privateKey: ownerPrivateKey, - rpc: rpc, - amount: amount, - sendMode: sendMode, - body: beginCell() - .storeUint32(TomNftConst.getStaticDataOperationId) - .storeUint64(queryId) - .endCell(), - bounce: bounce, - bounced: bounced, - state: state, - messages: messages, - timeout: timeout); - } - - Future getNftData(TonProvider rpc) async { - final result = await getStateStack(rpc: rpc, method: "get_nft_data"); - final reader = result.reader(); - final bool init = reader.readNumber() != 0; - final index = reader.readBigNumber(); - if (!init) { - return NFTItemData(init: init, index: index); - } - final collectionAddress = reader.readAddressOpt(); - final ownerAddress = reader.readAddressOpt(); - final content = reader.readCell(); - - return NFTItemData( - init: init, - collectionAddress: collectionAddress, - content: content, - ownerAddress: ownerAddress, - index: index); - } -} diff --git a/lib/src/contracts/token/nft/models/models.dart b/lib/src/contracts/token/nft/models/models.dart deleted file mode 100644 index da86436..0000000 --- a/lib/src/contracts/token/nft/models/models.dart +++ /dev/null @@ -1,8 +0,0 @@ -export 'nft_models/nft_collection_data.dart'; -export 'nft_models/nft_collection_editable_params.dart'; -export 'nft_models/royalty_params.dart'; -export 'nft_models/nft_mint_params.dart'; -export 'nft_models/nft_item_params.dart'; -export 'nft_models/transfer_nft_params.dart'; -export 'nft_models/nft_item_data.dart'; -export 'nft_models/nft_editable_update_content.dart'; diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_collection_editable_params.dart b/lib/src/contracts/token/nft/models/nft_models/nft_collection_editable_params.dart deleted file mode 100644 index 06bd60d..0000000 --- a/lib/src/contracts/token/nft/models/nft_models/nft_collection_editable_params.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/contracts.dart'; -import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; - -class NftCollectionParams extends TonSerialization { - final RoyaltyParams royaltyParams; - final TonAddress ownerAddress; - final NFTMetadata content; - final Cell? nftItemCode; - final BigInt? nextItemIndex; - NftCollectionParams( - {required this.royaltyParams, - required this.ownerAddress, - required this.content, - this.nftItemCode, - this.nextItemIndex}); - - @override - void store(Builder builder, {int? workchain}) { - builder.storeAddress(ownerAddress); - builder.storeUint64(nextItemIndex ?? BigInt.zero); - content.store(builder); - builder.storeRef(nftItemCode ?? - TomNftConst.nftItemCode(workchain ?? ownerAddress.workChain)); - builder.store(royaltyParams); - } - - @override - Cell serialize({int? workchain}) { - final builder = beginCell(); - store(builder, workchain: workchain); - return builder.endCell(); - } - - @override - Map toJson() { - return { - "royalty_params": royaltyParams.toJson(), - "owner_address": ownerAddress.toFriendlyAddress(), - "content": content.toJson(), - "nft_item_code": nftItemCode?.toBase64(), - "next_item_index": nextItemIndex.toString() - }; - } -} diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_editable_update_content.dart b/lib/src/contracts/token/nft/models/nft_models/nft_editable_update_content.dart deleted file mode 100644 index 01023c0..0000000 --- a/lib/src/contracts/token/nft/models/nft_models/nft_editable_update_content.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/ton_dart.dart'; - -class UpdateEditableNFTContent extends TonSerialization { - final RoyaltyParams royaltyParams; - final NFTMetadata content; - const UpdateEditableNFTContent( - {required this.content, required this.royaltyParams}); - - @override - void store(Builder builder) { - builder.store(content); - builder.store(royaltyParams); - } - - @override - Map toJson() { - return { - "royalty_params": royaltyParams.toJson(), - "content": content.toJson() - }; - } -} diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_item_params.dart b/lib/src/contracts/token/nft/models/nft_models/nft_item_params.dart deleted file mode 100644 index 75de074..0000000 --- a/lib/src/contracts/token/nft/models/nft_models/nft_item_params.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/bit/builder.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; - -class NFTItemParams extends TonSerialization { - final BigInt index; - final TonAddress? collectionAddress; - final TonAddress? ownerAddress; - final NFTItemMetadata content; - const NFTItemParams( - {required this.index, - this.collectionAddress, - required this.ownerAddress, - required this.content}); - - @override - void store(Builder builder) { - builder.storeUint64(index); - builder.storeAddress(collectionAddress); - builder.storeAddress(ownerAddress); - content.store(builder, collectionLess: collectionAddress == null); - } - - @override - Map toJson() { - return { - "index": index.toString(), - "collection_address": collectionAddress?.toFriendlyAddress(), - "owner_address": ownerAddress?.toFriendlyAddress(), - "content": content.toJson() - }; - } -} diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_mint_params.dart b/lib/src/contracts/token/nft/models/nft_models/nft_mint_params.dart deleted file mode 100644 index 67bb92f..0000000 --- a/lib/src/contracts/token/nft/models/nft_models/nft_mint_params.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/dict/dictionary.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; - -class NFTMintParams extends TonSerialization { - final TonAddress ownerAddress; - final NFTMetadata metadata; - final BigInt initAmount; - final BigInt itemIndex; - NFTMintParams({ - required this.ownerAddress, - required this.metadata, - required this.initAmount, - required this.itemIndex, - }); - - @override - void store(Builder builder) { - builder.storeUint64(itemIndex); - builder.storeCoins(initAmount); - final content = beginCell(); - content.storeAddress(ownerAddress); - metadata.store(content); - builder.storeRef(content.endCell()); - } - - // @override - // void store(Builder builder) { - // builder.storeUint64(itemIndex); - // final newCell = beginCell(); - // newCell.storeCoins(initAmount); - // final content = beginCell(); - // content.storeAddress(ownerAddress); - // metadata.store(content); - // newCell.storeRef(content.endCell()); - // builder.storeRef(newCell.endCell()); - // } - - @override - Map toJson() { - return { - "metadata": metadata.toJson(), - "amount": initAmount.toString(), - "owner_address": ownerAddress.toFriendlyAddress(), - "item_index": itemIndex.toString() - }; - } -} - -class _BatchNFTsMintParamsUtils { - static final DictionaryValue nftBatchMintsCodec = - DictionaryValue( - serialize: (source, builder) { - builder.storeCoins(source.initAmount); - final content = beginCell(); - content.storeAddress(source.ownerAddress); - source.metadata.store(content); - builder.storeRef(content.endCell()); - }, - parse: (slice) => throw UnimplementedError()); - static Dictionary getDict( - {Map enteries = const {}}) { - return Dictionary.fromEnteries( - key: DictionaryKey.bigIntCodec(64), - value: nftBatchMintsCodec, - map: enteries); - } -} - -class BatchNFTsMintParams extends TonSerialization { - final List nfts; - - BatchNFTsMintParams(List nfts) - : nfts = List.unmodifiable(nfts); - - @override - void store(Builder builder) { - final Map nftOBjects = - Map.fromEntries(nfts.map((e) => MapEntry(e.itemIndex, e))); - final dict = _BatchNFTsMintParamsUtils.getDict(enteries: nftOBjects); - builder.storeDict(dict: dict); - } - - BigInt get initializeAmount => nfts.fold( - BigInt.zero, - (previousValue, element) => previousValue + element.initAmount, - ); - - @override - Map toJson() { - return {"nfts": nfts.map((e) => e.toJson()).toList()}; - } -} diff --git a/lib/src/contracts/token/nft/models/nft_models/transfer_nft_params.dart b/lib/src/contracts/token/nft/models/nft_models/transfer_nft_params.dart deleted file mode 100644 index 1f57f2e..0000000 --- a/lib/src/contracts/token/nft/models/nft_models/transfer_nft_params.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/ton_dart.dart'; - -/// transfer#5fcc3d14 query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress -/// custom_payload:(Maybe ^Cell) forward_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) = InternalMsgBody; -class TransferNFTParams extends TonSerialization { - /// address of the new owner of the NFT item. - final TonAddress newOwnerAddress; - - /// address where to send a response with confirmation of a - /// successful transfer and the rest of the incoming message coins. - final TonAddress? responseDestination; - - // /// optional custom data. - // final Cell? customPayload; - - /// the amount of nanotons to be sent to the new owner. - final BigInt forwardAmount; - - /// optional custom data that should be sent to the new owner. - final Cell? forwardPayload; - const TransferNFTParams( - {required this.newOwnerAddress, - this.responseDestination, - required this.forwardAmount, - this.forwardPayload}); - - @override - void store(Builder builder) { - builder.storeAddress(newOwnerAddress); - builder.storeAddress(responseDestination); - builder.storeBit(0); - builder.storeCoins(forwardAmount); - builder.storeMaybeRef(cell: forwardPayload); - } - - @override - Map toJson() { - return { - "new_owner_address": newOwnerAddress.toFriendlyAddress(), - "response_destination": responseDestination?.toFriendlyAddress(), - "forward_amount": forwardAmount.toString() - }; - } -} diff --git a/lib/src/contracts/token/nft/nft.dart b/lib/src/contracts/token/nft/nft.dart index a9d4fda..02f3349 100644 --- a/lib/src/contracts/token/nft/nft.dart +++ b/lib/src/contracts/token/nft/nft.dart @@ -1,2 +1,3 @@ export 'contracts/contracts.dart'; -export 'models/models.dart'; +export 'types/types.dart'; +export 'constant/constant.dart'; diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_collection_data.dart b/lib/src/contracts/token/nft/types/models/collection_data.dart similarity index 88% rename from lib/src/contracts/token/nft/models/nft_models/nft_collection_data.dart rename to lib/src/contracts/token/nft/types/models/collection_data.dart index 5631178..8accdbf 100644 --- a/lib/src/contracts/token/nft/models/nft_models/nft_collection_data.dart +++ b/lib/src/contracts/token/nft/types/models/collection_data.dart @@ -1,4 +1,3 @@ -import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:ton_dart/ton_dart.dart'; class NFTCollectionData with JsonSerialization { diff --git a/lib/src/contracts/token/nft/models/nft_models/nft_item_data.dart b/lib/src/contracts/token/nft/types/models/item_data.dart similarity index 95% rename from lib/src/contracts/token/nft/models/nft_models/nft_item_data.dart rename to lib/src/contracts/token/nft/types/models/item_data.dart index 4245589..48af2f3 100644 --- a/lib/src/contracts/token/nft/models/nft_models/nft_item_data.dart +++ b/lib/src/contracts/token/nft/types/models/item_data.dart @@ -1,5 +1,4 @@ import 'package:ton_dart/src/contracts/token/metadata/constant/constant.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:ton_dart/ton_dart.dart'; class NFTItemData with JsonSerialization { diff --git a/lib/src/contracts/token/nft/types/models/mint_params.dart b/lib/src/contracts/token/nft/types/models/mint_params.dart new file mode 100644 index 0000000..9bcf435 --- /dev/null +++ b/lib/src/contracts/token/nft/types/models/mint_params.dart @@ -0,0 +1,72 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/serialization/serialization.dart'; + +class NFTMintParams extends TonSerialization { + final TonAddress ownerAddress; + final Cell content; + final BigInt initAmount; + final BigInt itemIndex; + + factory NFTMintParams.deserialize(Slice slice, {BigInt? index}) { + final BigInt itemIndex = index ?? slice.loadUint64(); + final BigInt initAmount = slice.loadCoins(); + final collectionData = slice.loadRef().beginParse(); + final TonAddress ownerAddress = collectionData.loadAddress(); + return NFTMintParams( + ownerAddress: ownerAddress, + content: collectionData.loadRef(), + initAmount: initAmount, + itemIndex: itemIndex); + } + + factory NFTMintParams.fromJson(Map json) { + return NFTMintParams( + ownerAddress: TonAddress(json["ownerAddress"]), + content: Cell.fromBase64(json["content"]), + initAmount: BigintUtils.parse(json["initAmount"]), + itemIndex: BigintUtils.parse(json["itemIndex"])); + } + NFTMintParams copyWith( + {TonAddress? ownerAddress, + Cell? content, + BigInt? initAmount, + BigInt? itemIndex}) { + return NFTMintParams( + ownerAddress: ownerAddress ?? this.ownerAddress, + content: content ?? this.content, + initAmount: initAmount ?? this.initAmount, + itemIndex: itemIndex ?? this.itemIndex); + } + + NFTMintParams( + {required this.ownerAddress, + required this.content, + required this.initAmount, + required this.itemIndex}); + + @override + void store(Builder builder) { + builder.storeUint64(itemIndex); + builder.storeCoins(initAmount); + final content = beginCell(); + content.storeAddress(ownerAddress); + content.storeRef(this.content); + builder.storeRef(content.endCell()); + } + + NFTMetadata get metadata => NFTMetadata.deserialize(content.beginParse()); + + @override + Map toJson() { + return { + "metadata": metadata.toJson(), + "content": content.toBase64(), + "initAmount": initAmount.toString(), + "ownerAddress": ownerAddress.toRawAddress(), + "itemIndex": itemIndex.toString() + }; + } +} diff --git a/lib/src/contracts/token/nft/models/nft_models/royalty_params.dart b/lib/src/contracts/token/nft/types/models/royalty_params.dart similarity index 51% rename from lib/src/contracts/token/nft/models/nft_models/royalty_params.dart rename to lib/src/contracts/token/nft/types/models/royalty_params.dart index 6956d68..8b51bff 100644 --- a/lib/src/contracts/token/nft/models/nft_models/royalty_params.dart +++ b/lib/src/contracts/token/nft/types/models/royalty_params.dart @@ -1,16 +1,29 @@ //default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams; +import 'package:blockchain_utils/utils/numbers/utils/int_utils.dart'; import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/bit/builder.dart'; +import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; class RoyaltyParams extends TonSerialization { final int royaltyFactor; final int royaltyBase; final TonAddress address; - RoyaltyParams( + const RoyaltyParams( {required this.royaltyFactor, required this.royaltyBase, required this.address}); + factory RoyaltyParams.deserialize(Slice slice) { + return RoyaltyParams( + royaltyFactor: slice.loadUint16(), + royaltyBase: slice.loadUint16(), + address: slice.loadAddress()); + } + factory RoyaltyParams.fromJson(Map json) { + return RoyaltyParams( + royaltyFactor: IntUtils.parse(json["royaltyFactor"]), + royaltyBase: IntUtils.parse(json["royaltyBase"]), + address: TonAddress(json["address"])); + } @override void store(Builder builder) { @@ -24,9 +37,9 @@ class RoyaltyParams extends TonSerialization { @override Map toJson() { return { - "royalty_factor": royaltyFactor, - "royalty_base": royaltyBase, - "royalty_address": address.toString() + "royaltyFactor": royaltyFactor, + "royaltyBase": royaltyBase, + "address": address.toRawAddress() }; } } diff --git a/lib/src/contracts/token/nft/types/operations/operations.dart b/lib/src/contracts/token/nft/types/operations/operations.dart new file mode 100644 index 0000000..c56f01d --- /dev/null +++ b/lib/src/contracts/token/nft/types/operations/operations.dart @@ -0,0 +1,536 @@ +import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; +import 'package:ton_dart/src/contracts/token/nft/types/types.dart'; +import 'package:ton_dart/src/contracts/utils/parser.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/serialization/serialization.dart'; + +class NFTItemOperationType { + final int id; + final String name; + const NFTItemOperationType._({required this.name, required this.id}); + static const NFTItemOperationType transfer = NFTItemOperationType._( + name: "Transfer", id: TonNftConst.nftTransferOperationId); + static const NFTItemOperationType getStaticData = NFTItemOperationType._( + name: "GetStaticData", id: TonNftConst.getStaticDataOperationId); + static const List values = [transfer, getStaticData]; + static NFTItemOperationType fromTag(int? tag, + {NFTItemOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static NFTItemOperationType fromName(String? name, + {NFTItemOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } +} + +abstract class NFTItemOperation extends TonSerialization { + final NFTItemOperationType type; + final BigInt queryId; + + NFTItemOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + Cell toBody() => beginCell().store(this).endCell(); + factory NFTItemOperation.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = NFTItemOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case NFTItemOperationType.transfer: + return NFTItemTransfer.deserialize(slice); + case NFTItemOperationType.getStaticData: + return NFTItemGetStaticData.deserialize(slice); + default: + throw TonContractException("Invalid NFT Item operation type.", + details: {"type": type.name}); + } + }, + name: "NFTItem"); + } + factory NFTItemOperation.fromJson(Map? json) { + return TonModelParser.parseJson( + parse: () { + final type = NFTItemOperationType.fromName(json?["type"]); + switch (type) { + case NFTItemOperationType.transfer: + return NFTItemTransfer.fromJson(json!); + case NFTItemOperationType.getStaticData: + return NFTItemGetStaticData.fromJson(json!); + default: + throw TonContractException("Invalid NFT Item operation type.", + details: {"type": type.name}); + } + }, + name: "NFTItem"); + } + + T cast() { + if (this is! T) { + throw TonContractException("Incorrect NFTItemOperation casting.", + details: {"excepted": "$runtimeType", "got": "$T"}); + } + return this as T; + } +} + +class NFTItemTransfer extends NFTItemOperation { + /// address of the new owner of the NFT item. + final TonAddress newOwnerAddress; + + /// address where to send a response with confirmation of a + /// successful transfer and the rest of the incoming message coins. + final TonAddress? responseDestination; + + // /// optional custom data. + // final Cell? customPayload; + + /// the amount of nanotons to be sent to the new owner. + final BigInt forwardAmount; + + /// optional custom data that should be sent to the new owner. + final Cell? forwardPayload; + NFTItemTransfer( + {BigInt? queryId, + required this.newOwnerAddress, + this.responseDestination, + required this.forwardAmount, + this.forwardPayload}) + : super(queryId: queryId, type: NFTItemOperationType.transfer); + factory NFTItemTransfer.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + NFTItemOperationType.fromTag(slice.tryLoadUint32()); + final BigInt queryId = slice.loadUint64(); + final TonAddress newOwnerAddress = slice.loadAddress(); + final TonAddress? responseDestination = slice.loadMaybeAddress(); + slice.loadBit(); + return NFTItemTransfer( + queryId: queryId, + newOwnerAddress: newOwnerAddress, + responseDestination: responseDestination, + forwardAmount: slice.loadCoins(), + forwardPayload: slice.loadMaybeRef()); + }, + name: NFTItemOperationType.transfer.name); + } + factory NFTItemTransfer.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return NFTItemTransfer( + queryId: BigintUtils.tryParse(json["queryId"]), + newOwnerAddress: TonAddress(json["newOwnerAddress"]), + responseDestination: json["responseDestination"] == null + ? null + : TonAddress(json["responseDestination"]), + forwardAmount: BigintUtils.parse(json["forwardAmount"]), + forwardPayload: json["forwardPayload"] == null + ? null + : Cell.fromBase64(json["forwardPayload"])); + }, + name: NFTItemOperationType.transfer.name); + } + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(newOwnerAddress); + builder.storeAddress(responseDestination); + builder.storeBit(0); + builder.storeCoins(forwardAmount); + builder.storeMaybeRef(cell: forwardPayload); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "newOwnerAddress": newOwnerAddress.toFriendlyAddress(), + "responseDestination": responseDestination?.toFriendlyAddress(), + "forwardAmount": forwardAmount.toString(), + "forwardPayload": forwardPayload?.toBase64(), + "type": type.name + }; + } +} + +class NFTItemGetStaticData extends NFTItemOperation { + NFTItemGetStaticData({required BigInt? queryId}) + : super(queryId: queryId, type: NFTItemOperationType.getStaticData); + factory NFTItemGetStaticData.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + NFTItemOperationType.fromTag(slice.tryLoadUint32()); + return NFTItemGetStaticData(queryId: slice.loadUint64()); + }, + name: NFTItemOperationType.getStaticData.name); + } + factory NFTItemGetStaticData.fromJson(Map json) { + return TonModelParser.parseJson( + parse: () { + return NFTItemGetStaticData( + queryId: BigintUtils.parse(json["queryId"])); + }, + name: NFTItemOperationType.getStaticData.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + } + + @override + Map toJson() { + return {"queryId": queryId.toString(), "type": type.name}; + } +} + +class NFTCollectionOperationType { + final String name; + final int id; + const NFTCollectionOperationType._({required this.name, required this.id}); + + static const NFTCollectionOperationType mint = NFTCollectionOperationType._( + name: "Mint", id: TonNftConst.mintNFtOperationId); + static const NFTCollectionOperationType batchMint = + NFTCollectionOperationType._( + name: "BatchMint", id: TonNftConst.batchMintNFtOperationId); + static const NFTCollectionOperationType changeOwner = + NFTCollectionOperationType._( + name: "ChangeOwner", + id: TonNftConst.changeCollectionOwnerOperationId); + static const NFTCollectionOperationType changeContent = + NFTCollectionOperationType._( + name: "ChangeContent", id: TonNftConst.changeContent); + static const List values = [ + mint, + batchMint, + changeOwner, + changeContent + ]; + static NFTCollectionOperationType fromTag(int? tag, + {NFTCollectionOperationType? excepted}) { + final type = values.firstWhere((e) => e.id == tag, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: tag)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + static NFTCollectionOperationType fromName(String? name, + {NFTCollectionOperationType? excepted}) { + final type = values.firstWhere((e) => e.name == name, + orElse: () => + throw TonContractExceptionConst.invalidOperationId(tag: name)); + if (excepted != null) { + if (type != excepted) { + throw TonContractExceptionConst.incorrectOperation( + excepted: excepted.name, got: type.name); + } + } + return type; + } + + @override + String toString() { + return "NFTCollectionOperationType.$name"; + } +} + +abstract class NFTCollectionOperation extends TonSerialization { + final NFTCollectionOperationType type; + final BigInt queryId; + NFTCollectionOperation({required this.type, BigInt? queryId}) + : queryId = queryId ?? BigInt.zero; + Cell toBody() => beginCell().store(this).endCell(); + factory NFTCollectionOperation.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + final type = + NFTCollectionOperationType.fromTag(slice.tryPreloadUint32()); + switch (type) { + case NFTCollectionOperationType.batchMint: + return NFTCollectionBatchMint.deserialize(slice); + case NFTCollectionOperationType.changeContent: + return NFTEditableCollectionChangeContent.deserialize(slice); + case NFTCollectionOperationType.changeOwner: + return NFTCollectionChangeOwner.deserialize(slice); + case NFTCollectionOperationType.mint: + return NFTCollectionMint.deserialize(slice); + default: + throw TonContractException( + "Invalid NFT Collection operation type.", + details: {"type": type.name}); + } + }, + name: "NFTCollection"); + } + factory NFTCollectionOperation.fromJson(Map? json) { + return TonModelParser.parseJson( + parse: () { + final type = NFTCollectionOperationType.fromName(json?["type"]); + switch (type) { + case NFTCollectionOperationType.batchMint: + return NFTCollectionBatchMint.fromJson(json!); + case NFTCollectionOperationType.changeContent: + return NFTEditableCollectionChangeContent.fromJson(json!); + case NFTCollectionOperationType.changeOwner: + return NFTCollectionChangeOwner.fromJson(json!); + case NFTCollectionOperationType.mint: + return NFTCollectionMint.fromJson(json!); + default: + throw TonContractException( + "Invalid NFT Collection operation type.", + details: {"type": type.name}); + } + }, + name: "NFTCollection"); + } + T cast() { + if (this is! T) { + throw TonContractException("Incorrect NFTCollectionOperation casting.", + details: {"excepted": "$runtimeType", "got": "$T"}); + } + return this as T; + } +} + +class NFTCollectionMint extends NFTCollectionOperation { + final NFTMintParams mint; + + NFTCollectionMint({BigInt? queryId, required this.mint}) + : super(type: NFTCollectionOperationType.mint, queryId: queryId); + factory NFTCollectionMint.deserialize(Slice slice) { + NFTCollectionOperationType.fromTag(slice.tryLoadUint32()); + return NFTCollectionMint( + queryId: slice.loadUint64(), mint: NFTMintParams.deserialize(slice)); + } + factory NFTCollectionMint.fromJson(Map json) { + return NFTCollectionMint( + queryId: BigintUtils.parse(json["queryId"]), + mint: NFTMintParams.fromJson(json["mint"])); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.store(mint); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "mint": mint.toJson(), + "type": type.name + }; + } +} + +class _BatchNFTsMintParamsUtils { + static final DictionaryValue nftBatchMintsCodec = + DictionaryValue( + serialize: (source, builder) { + builder.storeCoins(source.initAmount); + final content = beginCell(); + content.storeAddress(source.ownerAddress); + source.metadata.store(content); + builder.storeRef(content.endCell()); + }, + parse: (slice) => + NFTMintParams.deserialize(slice, index: BigInt.from(-1))); + static Dictionary getDict( + {Map enteries = const {}}) { + return Dictionary.fromEnteries( + key: DictionaryKey.bigIntCodec(64), + value: nftBatchMintsCodec, + map: enteries); + } + + static List load(Slice slice) { + final dict = Dictionary.fromEnteries( + key: DictionaryKey.bigIntCodec(64), + value: nftBatchMintsCodec, + map: const {}); + dict.loadFromClice(slice); + final asMap = dict.asMap; + return asMap.entries + .map((e) => e.value.copyWith(itemIndex: e.key)) + .toList(); + } +} + +class NFTCollectionBatchMint extends NFTCollectionOperation { + final List nfts; + + NFTCollectionBatchMint({BigInt? queryId, required List nfts}) + : nfts = List.unmodifiable(nfts), + super(type: NFTCollectionOperationType.batchMint, queryId: queryId); + factory NFTCollectionBatchMint.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + NFTCollectionOperationType.fromTag(slice.tryLoadUint32()); + final BigInt queryId = slice.loadUint64(); + final nfts = _BatchNFTsMintParamsUtils.load(slice); + return NFTCollectionBatchMint(queryId: queryId, nfts: nfts); + }, + name: NFTCollectionOperationType.batchMint.name); + } + factory NFTCollectionBatchMint.fromJson(Map json) { + return TonModelParser.parseBoc( + parse: () { + return NFTCollectionBatchMint( + queryId: BigintUtils.parse(json["queryId"]), + nfts: (json["nfts"] as List) + .map((e) => NFTMintParams.fromJson(e)) + .toList()); + }, + name: NFTCollectionOperationType.batchMint.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + final Map nftOBjects = + Map.fromEntries(nfts.map((e) => MapEntry(e.itemIndex, e))); + final dict = _BatchNFTsMintParamsUtils.getDict(enteries: nftOBjects); + builder.storeDict(dict: dict); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "nfts": nfts.map((e) => e.toJson()).toList(), + "type": type.name + }; + } +} + +class NFTCollectionChangeOwner extends NFTCollectionOperation { + final TonAddress newOwnerAddress; + NFTCollectionChangeOwner({BigInt? queryId, required this.newOwnerAddress}) + : super(queryId: queryId, type: NFTCollectionOperationType.changeOwner); + factory NFTCollectionChangeOwner.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + NFTCollectionOperationType.fromTag(slice.tryLoadUint32()); + return NFTCollectionChangeOwner( + queryId: slice.loadUint64(), + newOwnerAddress: slice.loadAddress()); + }, + name: NFTCollectionOperationType.changeOwner.name); + } + factory NFTCollectionChangeOwner.fromJson(Map json) { + return TonModelParser.parseBoc( + parse: () { + return NFTCollectionChangeOwner( + queryId: BigintUtils.parse(json["queryId"]), + newOwnerAddress: TonAddress(json["newOwnerAddress"])); + }, + name: NFTCollectionOperationType.changeOwner.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeAddress(newOwnerAddress); + } + + @override + NFTCollectionOperationType get type => NFTCollectionOperationType.changeOwner; + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "newOwnerAddress": newOwnerAddress.toRawAddress(), + "type": type.name + }; + } +} + +class NFTEditableCollectionChangeContent extends NFTCollectionOperation { + final RoyaltyParams royaltyParams; + final Cell content; + NFTEditableCollectionChangeContent( + {required BigInt? queryId, + required this.royaltyParams, + required this.content}) + : super(type: NFTCollectionOperationType.changeContent, queryId: queryId); + factory NFTEditableCollectionChangeContent.deserialize(Slice slice) { + return TonModelParser.parseBoc( + parse: () { + NFTCollectionOperationType.fromTag(slice.tryLoadUint32(), + excepted: NFTCollectionOperationType.changeContent); + return NFTEditableCollectionChangeContent( + queryId: slice.loadUint64(), + content: slice.loadRef(), + royaltyParams: + RoyaltyParams.deserialize(slice.loadRef().beginParse())); + }, + name: NFTCollectionOperationType.changeContent.name); + } + factory NFTEditableCollectionChangeContent.fromJson( + Map json) { + return TonModelParser.parseJson( + parse: () { + return NFTEditableCollectionChangeContent( + queryId: BigintUtils.parse(json["queryId"]), + content: Cell.fromBase64(json["content"]), + royaltyParams: RoyaltyParams.fromJson(json["royaltyParams"])); + }, + name: NFTCollectionOperationType.changeContent.name); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.id); + builder.storeUint64(queryId); + builder.storeRef(content); + builder.store(royaltyParams); + } + + @override + Map toJson() { + return { + "queryId": queryId.toString(), + "royaltyParams": royaltyParams.toJson(), + "content": content.toBase64(), + "metadata": metadata.toJson(), + "type": type.name + }; + } + + NFTMetadata get metadata => NFTMetadata.deserialize(content.beginParse()); +} diff --git a/lib/src/contracts/token/nft/types/state/collection.dart b/lib/src/contracts/token/nft/types/state/collection.dart new file mode 100644 index 0000000..2dcf63e --- /dev/null +++ b/lib/src/contracts/token/nft/types/state/collection.dart @@ -0,0 +1,117 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class NftCollectionState extends ContractState { + final RoyaltyParams royaltyParams; + final TonAddress ownerAddress; + final Cell content; + final Cell nftItemCode; + final BigInt nextItemIndex; + NFTMetadata get metadata => NFTMetadata.deserialize(content.beginParse()); + + Map toJson() { + return { + "royaltyParams": royaltyParams.toJson(), + "ownerAddress": ownerAddress.toFriendlyAddress(), + "content": content.toBase64(), + "nftItemCode": nftItemCode.toBase64(), + "nextItemIndex": nextItemIndex, + "metadata": metadata.toJson() + }; + } + + NftCollectionState._( + {required this.royaltyParams, + required this.ownerAddress, + required this.content, + required this.nftItemCode, + required this.nextItemIndex}); + factory NftCollectionState( + {required RoyaltyParams royaltyParams, + required TonAddress ownerAddress, + required NFTMetadata metadata, + Cell? nftItemCode, + BigInt? nextItemIndex}) { + return NftCollectionState._( + royaltyParams: royaltyParams, + ownerAddress: ownerAddress, + content: metadata.toContent(), + nextItemIndex: nextItemIndex ?? BigInt.zero, + nftItemCode: + nftItemCode ?? TonNftConst.nftItemCode(ownerAddress.workChain)); + } + factory NftCollectionState.deserialize(Slice slice) { + return NftCollectionState._( + ownerAddress: slice.loadAddress(), + nextItemIndex: slice.loadUint64(), + content: slice.loadRef(), + nftItemCode: slice.loadRef(), + royaltyParams: RoyaltyParams.deserialize(slice.loadRef().beginParse()), + ); + } + + @override + StateInit initialState() { + return StateInit( + code: TonNftConst.nftCollectionCode(ownerAddress.workChain), + data: initialData()); + } + + @override + Cell initialData() { + final builder = beginCell(); + builder.storeAddress(ownerAddress); + builder.storeUint64(nextItemIndex); + builder.storeRef(content); + builder.storeRef(nftItemCode); + builder.store(royaltyParams); + return builder.endCell(); + } +} + +class NftEditableCollectionState extends NftCollectionState { + NftEditableCollectionState._( + {required RoyaltyParams royaltyParams, + required TonAddress ownerAddress, + required Cell content, + required Cell nftItemCode, + required BigInt nextItemIndex}) + : super._( + royaltyParams: royaltyParams, + ownerAddress: ownerAddress, + content: content, + nftItemCode: nftItemCode, + nextItemIndex: nextItemIndex); + factory NftEditableCollectionState( + {required RoyaltyParams royaltyParams, + required TonAddress ownerAddress, + required NFTMetadata metadata, + Cell? nftItemCode, + BigInt? nextItemIndex}) { + return NftEditableCollectionState._( + royaltyParams: royaltyParams, + ownerAddress: ownerAddress, + content: metadata.toContent(), + nextItemIndex: nextItemIndex ?? BigInt.zero, + nftItemCode: + nftItemCode ?? TonNftConst.nftItemCode(ownerAddress.workChain)); + } + factory NftEditableCollectionState.deserialize(Slice slice) { + return NftEditableCollectionState._( + ownerAddress: slice.loadAddress(), + nextItemIndex: slice.loadUint64(), + content: slice.loadRef(), + nftItemCode: slice.loadRef(), + royaltyParams: RoyaltyParams.deserialize(slice.loadRef().beginParse()), + ); + } + + @override + StateInit initialState() { + return StateInit( + code: TonNftConst.nftEditableCollectionCode(ownerAddress.workChain), + data: initialData()); + } +} diff --git a/lib/src/contracts/token/nft/types/state/item.dart b/lib/src/contracts/token/nft/types/state/item.dart new file mode 100644 index 0000000..334bfb2 --- /dev/null +++ b/lib/src/contracts/token/nft/types/state/item.dart @@ -0,0 +1,102 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class NFTItemState extends ContractState { + final BigInt index; + final TonAddress? collectionAddress; + final TonAddress ownerAddress; + final Cell content; + const NFTItemState._( + {required this.index, + this.collectionAddress, + required this.ownerAddress, + required this.content}); + factory NFTItemState({ + required BigInt index, + TonAddress? collectionAddress, + required NFTItemMetadata metadata, + required TonAddress ownerAddress, + }) { + return NFTItemState._( + index: index, + ownerAddress: ownerAddress, + content: metadata.toContent(collectionless: collectionAddress == null), + collectionAddress: collectionAddress); + } + factory NFTItemState.deserialize(Slice slice) { + return NFTItemState._( + index: slice.loadUint64(), + collectionAddress: slice.loadMaybeAddress(), + ownerAddress: slice.loadAddress(), + content: slice.loadRef(), + ); + } + + @override + StateInit initialState() { + return StateInit( + code: TonNftConst.nftItemCode(ownerAddress.workChain), + data: initialData()); + } + + @override + Cell initialData() { + final builder = beginCell(); + builder.storeUint64(index); + builder.storeAddress(collectionAddress); + builder.storeAddress(ownerAddress); + builder.storeRef(content); + return builder.endCell(); + } + + NFTItemMetadata get metadata => + NFTItemMetadata.deserialize(content.beginParse()); + + Map toJson() { + return { + "index": index, + "collectionAddress": collectionAddress?.toFriendlyAddress(), + "ownerAddress": ownerAddress.toFriendlyAddress(), + "content": content.toBase64(), + "metadata": metadata.toJson() + }; + } +} + +// import 'package:ton_dart/src/address/address/address.dart'; +// import 'package:ton_dart/src/boc/bit/builder.dart'; +// import 'package:ton_dart/src/boc/boc.dart'; +// import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; +// import 'package:ton_dart/src/serialization/serialization.dart'; + +// class NFTItemState extends TonSerialization { +// final BigInt index; +// final TonAddress? collectionAddress; +// final TonAddress ownerAddress; +// final Cell content; +// const NFTItemState( +// {required this.index, +// this.collectionAddress, +// required this.ownerAddress, +// required this.content}); + +// // @override +// // void store(Builder builder) { +// builder.storeUint64(index); +// builder.storeAddress(collectionAddress); +// builder.storeAddress(ownerAddress); +// content.store(builder, collectionLess: collectionAddress == null); +// // } + +// // @override +// // Map toJson() { +// // return { +// // "index": index.toString(), +// // "collection_address": collectionAddress?.toFriendlyAddress(), +// // "owner_address": ownerAddress?.toFriendlyAddress(), +// // "content": content.toJson() +// // }; +// // } +// } diff --git a/lib/src/contracts/token/nft/types/types.dart b/lib/src/contracts/token/nft/types/types.dart new file mode 100644 index 0000000..b0f0886 --- /dev/null +++ b/lib/src/contracts/token/nft/types/types.dart @@ -0,0 +1,9 @@ +export 'models/collection_data.dart'; +export 'models/item_data.dart'; +export 'models/mint_params.dart'; +export 'models/royalty_params.dart'; + +export 'operations/operations.dart'; + +export 'state/collection.dart'; +export 'state/item.dart'; diff --git a/lib/src/contracts/token/nft/utils/utils.dart b/lib/src/contracts/token/nft/utils/utils.dart deleted file mode 100644 index f90953d..0000000 --- a/lib/src/contracts/token/nft/utils/utils.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/token/metadata/metadata.dart'; -import 'package:ton_dart/src/contracts/token/nft/constant/constant.dart'; -import 'package:ton_dart/src/contracts/token/nft/models/models.dart'; -import 'package:ton_dart/src/models/models.dart'; - -class NftWalletUtils { - static Cell buildNftCollectionData( - {required TonAddress ownerAddress, - required RoyaltyParams royaltyParams, - required NFTMetadata content, - BigInt? nextItemIndex}) { - final builder = beginCell(); - builder.storeAddress(ownerAddress); - builder.storeUint64(nextItemIndex ?? BigInt.zero); - content.store(builder); - builder.storeRef(TomNftConst.nftItemCode(ownerAddress.workChain)); - builder.store(royaltyParams); - return builder.endCell(); - } - - static StateInit buildNftCollectionState( - {required TonAddress ownerAddress, - required RoyaltyParams royaltyParams, - required NFTMetadata content, - required Cell code, - BigInt? nextItemIndex}) { - return StateInit( - code: code, - data: buildNftCollectionData( - ownerAddress: ownerAddress, - royaltyParams: royaltyParams, - content: content, - nextItemIndex: nextItemIndex)); - } -} diff --git a/lib/src/contracts/utils/parser.dart b/lib/src/contracts/utils/parser.dart new file mode 100644 index 0000000..3596a01 --- /dev/null +++ b/lib/src/contracts/utils/parser.dart @@ -0,0 +1,27 @@ +import 'package:ton_dart/src/contracts/exception/exception.dart'; + +class TonModelParser { + static T parseJson( + {required T Function() parse, required String name, Map? data}) { + try { + return parse(); + } on TonContractException { + rethrow; + } catch (e, t) { + throw TonContractExceptionConst.invalidJson(name, + data: data, message: e.toString(), trace: t.toString()); + } + } + + static T parseBoc( + {required T Function() parse, required String name, Map? data}) { + try { + return parse(); + } on TonContractException { + rethrow; + } catch (e, t) { + throw TonContractExceptionConst.unknownBody(name, + message: e.toString(), trace: t.toString()); + } + } +} diff --git a/lib/src/contracts/utils/serialization_utils.dart b/lib/src/contracts/utils/serialization_utils.dart new file mode 100644 index 0000000..fed7700 --- /dev/null +++ b/lib/src/contracts/utils/serialization_utils.dart @@ -0,0 +1,165 @@ +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/models/models.dart'; + +class TonSerializationUtils { + static Dictionary arrayToDict( + {required List obj, + required DictionaryKey key, + required DictionaryValue value}) { + final dict = Dictionary.empty(key: key, value: value); + for (int i = 0; i < obj.length; i++) { + dict[BigInt.from(i)] = obj[i]; + } + return dict; + } + + static Cell seralizeV5({ + required OutActionsV5 actions, + required WalletV5AuthType type, + int? accountSeqno, + V5R1Context? context, + int? timeout, + BigInt? queryId, + }) { + if (type != WalletV5AuthType.extension) { + if (accountSeqno == null || context == null) { + throw const TonContractException( + "accountSeqno and context required for build wallet message v5."); + } + } + if (type == WalletV5AuthType.extension) { + return beginCell() + .storeUint32(type.tag) + .storeUint64(queryId ?? 0) + .store(actions) + .endCell(); + } + if (type == WalletV5AuthType.external) { + List fixedMode = []; + for (final i in actions.actions) { + if (i.type != OutActionType.sendMsg) { + fixedMode.add(i); + continue; + } + final sendMessage = (i as OutActionSendMsg) + .copyWith(mode: i.mode | SendMode.ignoreErrors.mode); + fixedMode.add(sendMessage); + } + actions = OutActionsV5(actions: fixedMode); + } + final signingMessage = beginCell().storeUint32(type.tag).store(context!); + timeout ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 60; + if (accountSeqno == 0) { + for (int i = 0; i < 32; i++) { + signingMessage.storeBit(1); + } + } else { + signingMessage.storeUint32(timeout); + } + signingMessage.storeUint(accountSeqno, 32).store(actions); + return signingMessage.endCell(); + } + + static Cell serializeV2( + {required List messages, + required int accountSeqno, + int? timeout}) { + final transaction = beginCell(); + transaction.storeUint(accountSeqno, 32); + if (accountSeqno == 0) { + for (int i = 0; i < 32; i++) { + transaction.storeBit(1); + } + } else { + timeout ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 60; + transaction.storeUint(timeout, 32); + } + for (final message in messages) { + transaction.storeUint(message.mode, 8); + transaction.storeRef(beginCell().store(message.outMessage).endCell()); + } + return transaction.endCell(); + } + + static Cell serializeV4( + {required int subWalletId, + required List messages, + required int accountSeqno, + required WalletVersion type, + int? timeout}) { + final transaction = beginCell(); + transaction.storeUint(subWalletId, 32); + if (accountSeqno == 0) { + for (int i = 0; i < 32; i++) { + transaction.storeBit(1); + } + } else { + timeout ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 60; + transaction.storeUint(timeout, 32); + } + transaction.storeUint(accountSeqno, 32); + if (type == WalletVersion.v4) { + transaction.storeUint(0, 8); + } + + for (final message in messages) { + transaction.storeUint(message.mode, 8); + transaction.storeRef(beginCell().store(message.outMessage).endCell()); + } + return transaction.endCell(); + } + + static Cell serializeV1({ + required List messages, + required int accountSeqno, + }) { + final transaction = beginCell(); + transaction.storeUint(accountSeqno, 32); + for (final message in messages) { + transaction.storeUint(message.mode, 8); + transaction.storeRef(beginCell().store(message.outMessage).endCell()); + } + return transaction.endCell(); + } + + static Cell serializeMessage( + {required List actions, + required VersionedWalletState state, + required int seqno, + int? timeOut}) { + if (actions.length > state.version.maxMessageLength) { + throw TonContractException( + "Only ${state.version.maxMessageLength} message can transfer with wallet contract version ${state.version.name}"); + } + switch (state.version) { + case WalletVersion.v4: + case WalletVersion.v3R1: + case WalletVersion.v3R2: + state as SubWalletVersionedWalletState; + return TonSerializationUtils.serializeV4( + subWalletId: state.subwallet, + messages: actions, + accountSeqno: seqno, + type: state.version); + case WalletVersion.v1R1: + case WalletVersion.v1R2: + case WalletVersion.v1R3: + return TonSerializationUtils.serializeV1( + messages: actions, + accountSeqno: seqno, + ); + case WalletVersion.v2R1: + case WalletVersion.v2R2: + return TonSerializationUtils.serializeV2( + messages: actions, + accountSeqno: seqno, + timeout: timeOut, + ); + default: + throw UnimplementedError(); + } + } +} diff --git a/lib/src/contracts/utils/transaction_utils.dart b/lib/src/contracts/utils/transaction_utils.dart index c159685..db3d101 100644 --- a/lib/src/contracts/utils/transaction_utils.dart +++ b/lib/src/contracts/utils/transaction_utils.dart @@ -1,17 +1,20 @@ import 'package:ton_dart/src/address/address/address.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; import 'package:ton_dart/src/models/models.dart'; class TransactioUtils { - static MessageRelaxed internal({ - required TonAddress destination, - required BigInt amount, - Cell? body, - StateInit? initState, - bool bounce = false, - bool bounced = false, - }) { + static MessageRelaxed internal( + {required TonAddress destination, + required BigInt amount, + Cell? body, + StateInit? initState, + String? memo, + bool bounce = false, + bool bounced = false}) { + assert(memo == null || body == null, + "You have to choose a memo or body for each message."); return MessageRelaxed( info: CommonMessageInfoRelaxedInternal( ihrDisabled: true, @@ -39,7 +42,7 @@ class TransactioUtils { required List messages, required int accountSeqno, required WalletVersion type, - SendMode sendMode = SendMode.payGasSeparately, + int sendMode = SendModeConst.payGasSeparately, int? timeout}) { final transaction = beginCell(); transaction.storeUint(subWalletId, 32); @@ -57,7 +60,7 @@ class TransactioUtils { } for (final message in messages) { - transaction.storeUint(sendMode.mode, 8); + transaction.storeUint(sendMode, 8); transaction.storeRef(beginCell().store(message).endCell()); } return transaction.endCell(); @@ -66,7 +69,7 @@ class TransactioUtils { static Cell createV2( {required List messages, required int accountSeqno, - SendMode sendMode = SendMode.payGasSeparately, + int sendMode = SendModeConst.payGasSeparately, int? timeout}) { final transaction = beginCell(); transaction.storeUint(accountSeqno, 32); @@ -79,7 +82,7 @@ class TransactioUtils { transaction.storeUint(timeout, 32); } for (final message in messages) { - transaction.storeUint(sendMode.mode, 8); + transaction.storeUint(sendMode, 8); transaction.storeRef(beginCell().store(message).endCell()); } return transaction.endCell(); @@ -88,34 +91,81 @@ class TransactioUtils { static Cell createV1({ required List messages, required int accountSeqno, - SendMode sendMode = SendMode.payGasSeparately, + int sendMode = SendModeConst.payGasSeparately, }) { final transaction = beginCell(); transaction.storeUint(accountSeqno, 32); for (final message in messages) { - transaction.storeUint(sendMode.mode, 8); + transaction.storeUint(sendMode, 8); transaction.storeRef(beginCell().store(message).endCell()); } return transaction.endCell(); } - static Cell serializeMessage({ - required List messages, - required int accountSeqno, - required WalletVersion type, - SendMode sendMode = SendMode.payGasSeparately, - int? timeOut, - int? subwalletId, + static Cell createV5({ + required OutActionsV5 actions, + required WalletV5AuthType type, + int? accountSeqno, + V5R1Context? context, + int? timeout, + BigInt? queryId, }) { - switch (type) { + if (type != WalletV5AuthType.extension) { + if (accountSeqno == null || context == null) { + throw const TonContractException( + "accountSeqno and context required for build wallet message v5."); + } + } + if (type == WalletV5AuthType.extension) { + return beginCell() + .storeUint32(type.tag) + .storeUint64(queryId ?? 0) + .store(actions) + .endCell(); + } + if (type == WalletV5AuthType.external) { + List fixedMode = []; + for (final i in actions.actions) { + if (i.type != OutActionType.sendMsg) { + fixedMode.add(i); + continue; + } + final sendMessage = (i as OutActionSendMsg) + .copyWith(mode: i.mode | SendMode.ignoreErrors.mode); + fixedMode.add(sendMessage); + } + actions = OutActionsV5(actions: fixedMode); + } + final signingMessage = beginCell().storeUint32(type.tag).store(context!); + timeout ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 60; + if (accountSeqno == 0) { + for (int i = 0; i < 32; i++) { + signingMessage.storeBit(1); + } + } else { + signingMessage.storeUint32(timeout); + } + signingMessage.storeUint(accountSeqno, 32).store(actions); + return signingMessage.endCell(); + } + + static Cell serializeMessage( + {required List messages, + required int accountSeqno, + required ContractState type, + int sendMode = SendModeConst.payGasSeparately, + int? timeOut}) { + type as VersionedWalletState; + switch (type.version) { case WalletVersion.v4: case WalletVersion.v3R1: case WalletVersion.v3R2: + final subwalletState = type as SubWalletVersionedWalletState; return createV4( - subWalletId: subwalletId!, + subWalletId: subwalletState.subwallet, messages: messages, accountSeqno: accountSeqno, - type: type, + type: type.version, sendMode: sendMode); case WalletVersion.v1R1: case WalletVersion.v1R2: @@ -134,3 +184,15 @@ class TransactioUtils { } } } + +class WalletV5AuthType { + final String name; + final int tag; + const WalletV5AuthType._({required this.name, required this.tag}); + static const WalletV5AuthType extension = + WalletV5AuthType._(name: "Extension", tag: 0x6578746e); + static const WalletV5AuthType external = + WalletV5AuthType._(name: "External", tag: 0x7369676e); + static const WalletV5AuthType internal = + WalletV5AuthType._(name: "Internal", tag: 0x73696e74); +} diff --git a/lib/src/contracts/wallet/contracts/v1r1.dart b/lib/src/contracts/wallet/contracts/v1r1.dart deleted file mode 100644 index cab0295..0000000 --- a/lib/src/contracts/wallet/contracts/v1r1.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This is the simplest one. It only allows you to send one transaction at -/// the time and it doesn't check anything besides your signature and seqno. -/// This version isn't even used in regular apps because it has some major issues: -/// No easy way to retrieve the seqno and public key from the contract -/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. -/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV1R1 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - const WalletV1R1({this.state, required this.address}); - - factory WalletV1R1.create( - {required int workChain, - required List publicKey, - bool bounceableAddress = false}) { - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, type: WalletVersion.v1R1); - return WalletV1R1( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress)); - } - - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v1R1); - return WalletV1R1(state: state.item1, address: address); - } - - @override - WalletVersion get type => WalletVersion.v1R1; - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/wallet/contracts/v1r2.dart b/lib/src/contracts/wallet/contracts/v1r2.dart deleted file mode 100644 index 7337cb3..0000000 --- a/lib/src/contracts/wallet/contracts/v1r2.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This is the simplest one. It only allows you to send one transaction at -/// the time and it doesn't check anything besides your signature and seqno. -/// This version isn't even used in regular apps because it has some major issues: -/// No easy way to retrieve the seqno and public key from the contract -/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. -/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV1R2 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - const WalletV1R2({this.state, required this.address}); - - factory WalletV1R2.create( - {required int workChain, - required List publicKey, - bool bounceableAddress = false}) { - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, type: WalletVersion.v1R2); - return WalletV1R2( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress)); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v1R2); - return WalletV1R2(state: state.item1, address: address); - } - - factory WalletV1R2.watch(TonAddress address) { - return WalletV1R2(address: address); - } - - @override - WalletVersion get type => WalletVersion.v1R2; - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/wallet/contracts/v1r3.dart b/lib/src/contracts/wallet/contracts/v1r3.dart deleted file mode 100644 index a8fd49e..0000000 --- a/lib/src/contracts/wallet/contracts/v1r3.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This is the simplest one. It only allows you to send one transaction at -/// the time and it doesn't check anything besides your signature and seqno. -/// This version isn't even used in regular apps because it has some major issues: -/// No easy way to retrieve the seqno and public key from the contract -/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. -/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV1R3 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - const WalletV1R3({this.state, required this.address}); - - factory WalletV1R3.create( - {required int workChain, - required List publicKey, - bool bounceableAddress = false}) { - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, type: WalletVersion.v1R3); - return WalletV1R3( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress)); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v1R3); - return WalletV1R3(state: state.item1, address: address); - } - - @override - WalletVersion get type => WalletVersion.v1R3; - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/wallet/contracts/v2r1.dart b/lib/src/contracts/wallet/contracts/v2r1.dart deleted file mode 100644 index fd657ed..0000000 --- a/lib/src/contracts/wallet/contracts/v2r1.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This version introduces the valid_until parameter which is used to set a time limit for a -/// transaction in case you don't want it to be confirmed too late. This version also doesn't -/// have the get-method for public key, which is added in V2R2. -/// It can be used in most cases, but it misses one cool feature, which was added in V3. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV2R1 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - const WalletV2R1({this.state, required this.address}); - - factory WalletV2R1.create( - {required int workChain, - required List publicKey, - bool bounceableAddress = false}) { - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, type: WalletVersion.v2R1); - return WalletV2R1( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress)); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v2R1); - return WalletV2R1(state: state.item1, address: address); - } - - @override - WalletVersion get type => WalletVersion.v2R1; - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/wallet/contracts/v2r2.dart b/lib/src/contracts/wallet/contracts/v2r2.dart deleted file mode 100644 index 7b4e44e..0000000 --- a/lib/src/contracts/wallet/contracts/v2r2.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This version introduces the valid_until parameter which is used to set a time limit for a -/// transaction in case you don't want it to be confirmed too late. This version also doesn't -/// have the get-method for public key, which is added in V2R2. -/// It can be used in most cases, but it misses one cool feature, which was added in V3. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV2R2 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - const WalletV2R2({this.state, required this.address}); - - factory WalletV2R2.create( - {required int workChain, - required List publicKey, - bool bounceableAddress = false}) { - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, type: WalletVersion.v2R2); - return WalletV2R2( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress)); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v2R2); - return WalletV2R2(state: state.item1, address: address); - } - - @override - WalletVersion get type => WalletVersion.v2R2; - - @override - int? get subWalletId => null; -} diff --git a/lib/src/contracts/wallet/contracts/v3r1.dart b/lib/src/contracts/wallet/contracts/v3r1.dart deleted file mode 100644 index 833fc82..0000000 --- a/lib/src/contracts/wallet/contracts/v3r1.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/constant/constant.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This version introduces the subwallet_id parameter, which allows you to create multiple wallets -/// using the same public key (so you can have only one seed phrase and lots of wallets). And, -/// as before, V3R2 only adds the get-method for public key. -/// Basically, subwallet_id is just a number which is added to the contract state when it is deployed. -/// And since the contract address in TON is a hash of its state and code, the wallet address will change with a different subwallet_id. -/// This version is the most used right now. It covers most use-cases and remains clean and simple. -class WalletV3R1 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - @override - final int? subWalletId; - - const WalletV3R1._( - {this.state, required this.address, required this.subWalletId}); - - const WalletV3R1( - {this.state, required this.address, required int this.subWalletId}); - - factory WalletV3R1.create( - {required int workChain, - required List publicKey, - int? subWalletId, - bool bounceableAddress = false}) { - subWalletId ??= VersionedWalletConst.defaultSubWalletId + workChain; - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, - subWalletId: subWalletId, - type: WalletVersion.v3R1); - return WalletV3R1._( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress), - subWalletId: subWalletId); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v3R1); - return WalletV3R1._( - state: state.item1, address: address, subWalletId: state.item2!); - } - - factory WalletV3R1.watch(TonAddress address) { - return WalletV3R1._(address: address, subWalletId: null); - } - - @override - WalletVersion get type => WalletVersion.v3R1; -} diff --git a/lib/src/contracts/wallet/contracts/v3r2.dart b/lib/src/contracts/wallet/contracts/v3r2.dart deleted file mode 100644 index 5650afc..0000000 --- a/lib/src/contracts/wallet/contracts/v3r2.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/constant/constant.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// This version introduces the subwallet_id parameter, which allows you to create multiple wallets -/// using the same public key (so you can have only one seed phrase and lots of wallets). And, -/// as before, V3R2 only adds the get-method for public key. -/// Basically, subwallet_id is just a number which is added to the contract state when it is deployed. -/// And since the contract address in TON is a hash of its state and code, the wallet address will change with a different subwallet_id. -/// This version is the most used right now. It covers most use-cases and remains clean and simple. -/// https://docs.ton.org/participate/wallets/contracts -class WalletV3R2 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - @override - final int? subWalletId; - - const WalletV3R2._( - {this.state, required this.address, required this.subWalletId}); - const WalletV3R2( - {this.state, required this.address, required int this.subWalletId}); - - factory WalletV3R2.create( - {required int workChain, - required List publicKey, - int? subWalletId, - bool bounceableAddress = false}) { - subWalletId ??= VersionedWalletConst.defaultSubWalletId + workChain; - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, - subWalletId: subWalletId, - type: WalletVersion.v3R2); - return WalletV3R2._( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress), - subWalletId: subWalletId); - } - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v3R2); - return WalletV3R2._( - state: state.item1, address: address, subWalletId: state.item2!); - } - - factory WalletV3R2.watch(TonAddress address) { - return WalletV3R2._(address: address, subWalletId: null); - } - - @override - WalletVersion get type => WalletVersion.v3R2; -} diff --git a/lib/src/contracts/wallet/contracts/v4.dart b/lib/src/contracts/wallet/contracts/v4.dart deleted file mode 100644 index 596f15d..0000000 --- a/lib/src/contracts/wallet/contracts/v4.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/contracts/core/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/constant/constant.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; - -/// It is the most modern wallet version at the moment. It still has all the functionality of the previous versions, -/// but also introduces something very powerful — plugins. -/// This feature allows developers to implement complex logic that will work in tandem with a -/// user's wallet. For example, some DApp may require a user to pay a small amount of coins every day -/// to use some features, so the user will need to install the plugin on their wallet by signing a transaction. -/// This plugin will send coins to the destination address every day when it will be reqested by an external message. -/// This is a very customizable feature which is unique to TON Blockchain. -/// https://docs.ton.org/participate/wallets/contracts#wallet-v4 -class WalletV4 extends WalletContract { - @override - final StateInit? state; - - @override - final TonAddress address; - - @override - final int? subWalletId; - - const WalletV4._( - {required this.state, required this.address, required this.subWalletId}); - - const WalletV4( - {required this.state, - required this.address, - required int this.subWalletId}); - - factory WalletV4.create( - {required int workChain, - required List publicKey, - int? subWalletId, - bool bounceableAddress = false}) { - subWalletId ??= VersionedWalletConst.defaultSubWalletId + workChain; - final state = VersionedWalletUtils.buildState( - publicKey: publicKey, subWalletId: subWalletId, type: WalletVersion.v4); - return WalletV4._( - state: state, - address: TonAddress.fromState( - state: state, workChain: workChain, bounceable: bounceableAddress), - subWalletId: subWalletId); - } - - static Future fromAddress( - {required TonAddress address, required TonProvider rpc}) async { - final data = - await ContractProvider.getStaticState(rpc: rpc, address: address); - final state = VersionedWalletUtils.buildFromAddress( - address: address, stateData: data.data, type: WalletVersion.v4); - return WalletV4._( - state: state.item1, address: address, subWalletId: state.item2!); - } - - factory WalletV4.watch(TonAddress address) { - return WalletV4._(state: null, address: address, subWalletId: null); - } - @override - WalletVersion get type => WalletVersion.v4; -} diff --git a/lib/src/contracts/wallet/core/versioned_wallet.dart b/lib/src/contracts/wallet/core/versioned_wallet.dart deleted file mode 100644 index c2fcb46..0000000 --- a/lib/src/contracts/wallet/core/versioned_wallet.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:ton_dart/src/boc/cell/cell.dart'; -import 'package:ton_dart/src/contracts/core/contract.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/contracts/wallet/models/versioned_wallet_account_params.dart'; -import 'package:ton_dart/src/contracts/wallet/provider/provider_impl.dart'; -import 'package:ton_dart/src/contracts/wallet/transaction/transaction_impl.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models/message.dart'; -import 'package:ton_dart/src/models/models/message_relaxed.dart'; -import 'package:ton_dart/src/models/models/send_mode.dart'; -import 'package:ton_dart/src/provider/provider.dart'; - -typedef OnEstimateFee = Future Function(Message message); - -abstract class VersonedWalletContract - extends TonContract { - const VersonedWalletContract(); - abstract final WalletVersion type; - Future sendTransfer( - {required List messages, - required TonPrivateKey privateKey, - required TonProvider rpc, - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - OnEstimateFee? onEstimateFee}); - - @override - Cell code(int workchain) { - return type.getCode(); - } - - @override - Cell data(VersionedWalletAccountPrams params, int workchain) { - return VersionedWalletUtils.buldData( - type: type, publicKey: params.publicKey, subWalletId: params.subwallet); - } -} - -abstract class WalletContract extends VersonedWalletContract - with VersionedWalletTransactionImpl, VerionedProviderImpl { - const WalletContract(); -} diff --git a/lib/src/contracts/wallet/models/versioned_wallet_account_params.dart b/lib/src/contracts/wallet/models/versioned_wallet_account_params.dart deleted file mode 100644 index f599542..0000000 --- a/lib/src/contracts/wallet/models/versioned_wallet_account_params.dart +++ /dev/null @@ -1,7 +0,0 @@ -class VersionedWalletAccountPrams { - final int? subwallet; - final List publicKey; - final int seqno; - const VersionedWalletAccountPrams( - {required this.subwallet, required this.publicKey, required this.seqno}); -} diff --git a/lib/src/contracts/wallet/provider/provider_impl.dart b/lib/src/contracts/wallet/provider/provider_impl.dart deleted file mode 100644 index f6c817a..0000000 --- a/lib/src/contracts/wallet/provider/provider_impl.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/wallet/models/versioned_wallet_account_params.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models/message_relaxed.dart'; -import 'package:ton_dart/src/models/models/send_mode.dart'; -import 'package:ton_dart/src/provider/provider.dart'; -import 'package:ton_dart/src/contracts/wallet/utils/utils.dart'; -import 'package:ton_dart/src/contracts/wallet/transaction/transaction_impl.dart'; - -mixin VerionedProviderImpl on VersionedWalletTransactionImpl { - Future getSeqno(TonProvider rpc) async { - final state = await getState(rpc: rpc); - if (!state.state.isActive) return 0; - final readState = - VersionedWalletUtils.readState(stateData: state.data, type: type); - return readState.seqno; - } - - Future getBalance(TonProvider rpc) async { - final state = await getState(rpc: rpc); - return state.balance; - } - - Future getPublicKey(TonProvider rpc) async { - final state = await getState(rpc: rpc); - final readState = - VersionedWalletUtils.readState(stateData: state.data, type: type); - return BytesUtils.toHexString(readState.publicKey); - } - - Future readState(TonProvider rpc) async { - final state = await getState(rpc: rpc); - return VersionedWalletUtils.readState(stateData: state.data, type: type); - } - - @override - Future sendTransfer( - {required List messages, - required TonPrivateKey privateKey, - required TonProvider rpc, - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - OnEstimateFee? onEstimateFee}) async { - final stateData = await readState(rpc); - - final exMessage = createAndSignTransfer( - messages: messages, - accountSeqno: stateData.seqno, - sendMode: sendMode, - timeout: timeout, - privateKey: privateKey, - subWalletId: - type.hasSubwalletId ? (subWalletId ?? stateData.subwallet) : null); - if (onEstimateFee != null) { - await onEstimateFee(exMessage); - } - return sendMessage(rpc: rpc, exMessage: exMessage); - } - - Future deploy( - {required TonPrivateKey ownerPrivateKey, - required TonProvider rpc, - required BigInt amount, - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - bool? bounce, - bool bounced = false, - Cell? body}) async { - final stateData = await readState(rpc); - - if (stateData.seqno != 0) { - throw TonContractException("Account is already active."); - } - if (state == null) { - throw TonContractException( - "For deploy minter please use create constructor to build state"); - } - final boc = createAndSignTransfer( - messages: [ - TransactioUtils.internal( - destination: address, - amount: amount, - initState: state, - bounced: bounced, - body: body, - bounce: bounce ?? address.isBounceable) - ], - accountSeqno: stateData.seqno, - sendMode: sendMode, - timeout: timeout, - privateKey: ownerPrivateKey, - subWalletId: - type.hasSubwalletId ? (subWalletId ?? stateData.subwallet) : null); - return sendMessage(rpc: rpc, exMessage: boc); - } -} diff --git a/lib/src/contracts/wallet/transaction/transaction_impl.dart b/lib/src/contracts/wallet/transaction/transaction_impl.dart deleted file mode 100644 index 34bd4e7..0000000 --- a/lib/src/contracts/wallet/transaction/transaction_impl.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/crypto/keypair/private_key.dart'; -import 'package:ton_dart/src/models/models/common_message_info.dart'; -import 'package:ton_dart/src/models/models/common_message_info_relaxed.dart'; -import 'package:ton_dart/src/models/models/currency_collection.dart'; -import 'package:ton_dart/src/models/models/message.dart'; -import 'package:ton_dart/src/models/models/message_relaxed.dart'; -import 'package:ton_dart/src/models/models/send_mode.dart'; -import 'package:ton_dart/src/contracts/wallet/core/versioned_wallet.dart'; -import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; - -mixin VersionedWalletTransactionImpl on VersonedWalletContract { - MessageRelaxed createMessageInfo({ - required TonAddress destination, - required BigInt amount, - String? memo, - Cell? body, - bool? bounce, - bool bounced = false, - }) { - assert(memo == null || body == null, - "You have to choose a memo or body for each message."); - return MessageRelaxed( - info: CommonMessageInfoRelaxedInternal( - ihrDisabled: true, - bounce: bounce ?? destination.isBounceable, - bounced: bounced, - dest: destination, - value: CurrencyCollection(coins: amount), - ihrFee: BigInt.zero, - forwardFee: BigInt.zero, - createdLt: BigInt.zero, - createdAt: 0), - body: body ?? TransactioUtils.buildMessageBody(memo), - ); - } - - Cell createTransfer( - {required List messages, - required int accountSeqno, - SendMode sendMode = SendMode.payGasSeparately, - int? subWalletId, - int? timeout}) { - final sId = subWalletId ?? this.subWalletId; - if (type.hasSubwalletId && sId == null) { - throw TonContractException( - "cannot create transfer with watch only wallet."); - } - return TransactioUtils.serializeMessage( - messages: messages, - accountSeqno: accountSeqno, - type: type, - sendMode: sendMode, - subwalletId: sId, - timeOut: timeout); - } - - Message createAndSignTransfer( - {required List messages, - required int accountSeqno, - required TonPrivateKey privateKey, - SendMode sendMode = SendMode.payGasSeparately, - int? timeout, - int? subWalletId}) { - final sId = subWalletId ?? this.subWalletId; - if (type.hasSubwalletId && sId == null) { - throw TonContractException( - "cannot create transfer with watch only wallet."); - } - final message = TransactioUtils.serializeMessage( - messages: messages, - accountSeqno: accountSeqno, - type: type, - sendMode: sendMode, - subwalletId: sId, - timeOut: timeout); - final body = beginCell() - .storeBuffer(privateKey.sign(message.hash())) - .storeSlice(message.beginParse()) - .endCell(); - return Message( - init: (accountSeqno == 0 ? state : null), - info: - CommonMessageInfoExternalIn(dest: address, importFee: BigInt.zero), - body: body); - } -} diff --git a/lib/src/contracts/wallet/utils/utils.dart b/lib/src/contracts/wallet/utils/utils.dart deleted file mode 100644 index 5737a3f..0000000 --- a/lib/src/contracts/wallet/utils/utils.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:ton_dart/src/address/address/address.dart'; -import 'package:ton_dart/src/boc/boc.dart'; -import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/wallet/core/version.dart'; -import 'package:ton_dart/src/crypto/crypto.dart'; -import 'package:ton_dart/src/models/models/state_init.dart'; -import 'package:ton_dart/src/contracts/wallet/models/versioned_wallet_account_params.dart'; - -class VersionedWalletUtils { - static VersionedWalletAccountPrams readState({ - required Cell? stateData, - required WalletVersion type, - }) { - try { - final cell = stateData!.beginParse(); - int seqno = cell.loadUint(32); - List pubkeyBytes; - int? subWallet; - switch (type) { - case WalletVersion.v1R1: - case WalletVersion.v1R2: - case WalletVersion.v1R3: - case WalletVersion.v2R1: - case WalletVersion.v2R2: - pubkeyBytes = cell.loadBuffer(32); - break; - case WalletVersion.v3R1: - case WalletVersion.v3R2: - case WalletVersion.v4: - subWallet = cell.loadUint(32); - pubkeyBytes = cell.loadBuffer(32); - break; - default: - throw UnimplementedError(); - } - return VersionedWalletAccountPrams( - subwallet: subWallet, - publicKey: pubkeyBytes, - seqno: seqno, - ); - } catch (e) { - throw MessageException("Invalid ${type.name} state account data."); - } - } - - static Tuple buildFromAddress( - {required Cell? stateData, - required WalletVersion type, - required TonAddress address}) { - final state = readState(stateData: stateData, type: type); - StateInit currentState = buildState( - type: type, publicKey: state.publicKey, subWalletId: state.subwallet); - final currentAddress = - TonAddress.fromState(state: currentState, workChain: address.workChain); - if (currentAddress.toRawAddress() != address.toRawAddress()) { - throw MessageException( - "Invalid wallet address. state gives a different address", - details: { - "excepted": currentAddress.toRawAddress(), - "address": address.toRawAddress() - }); - } - return Tuple(currentState, state.subwallet); - } - - static StateInit buildState({ - required WalletVersion type, - required List publicKey, - int? subWalletId, - }) { - return StateInit( - code: type.getCode(), - data: buldData( - type: type, publicKey: publicKey, subWalletId: subWalletId)); - } - - static Cell buldData({ - required WalletVersion type, - required List publicKey, - required int? subWalletId, - }) { - if (type.version > 2 && subWalletId == null) { - throw TonContractException( - "Subwallet id is required for wallet version 3, 4", - details: {"version": type.name}); - } - final pubkey = TonPublicKey.fromBytes(publicKey); - switch (type) { - case WalletVersion.v3R1: - case WalletVersion.v3R2: - return beginCell() - .storeUint(0, 32) - .storeUint(subWalletId, 32) - .storeBuffer(pubkey.toBytes()) - .endCell(); - case WalletVersion.v4: - return beginCell() - .storeUint(0, 32) - .storeUint(subWalletId, 32) - .storeBuffer(pubkey.toBytes()) - .storeBit(0) - .endCell(); - default: - return beginCell() - .storeUint(0, 32) - .storeBuffer(pubkey.toBytes()) - .endCell(); - } - } -} diff --git a/lib/src/contracts/wallet/wallet.dart b/lib/src/contracts/wallet/wallet.dart deleted file mode 100644 index 2d5dede..0000000 --- a/lib/src/contracts/wallet/wallet.dart +++ /dev/null @@ -1,10 +0,0 @@ -export 'contracts/v1r1.dart'; -export 'contracts/v1r2.dart'; -export 'contracts/v1r3.dart'; -export 'contracts/v2r1.dart'; -export 'contracts/v2r2.dart'; -export 'contracts/v3r1.dart'; -export 'contracts/v3r2.dart'; -export 'contracts/v4.dart'; -export 'core/version.dart'; -export 'core/versioned_wallet.dart'; diff --git a/lib/src/contracts/wallet_contracts/constant/constant.dart b/lib/src/contracts/wallet_contracts/constant/constant.dart new file mode 100644 index 0000000..a3a8094 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/constant/constant.dart @@ -0,0 +1,3 @@ +export 'constants/highload.dart'; +export 'constants/versioned.dart'; +export 'constants/mutli_owner.dart'; diff --git a/lib/src/contracts/highload/constant/constant.dart b/lib/src/contracts/wallet_contracts/constant/constants/highload.dart similarity index 85% rename from lib/src/contracts/highload/constant/constant.dart rename to lib/src/contracts/wallet_contracts/constant/constants/highload.dart index 7973643..8de5f9f 100644 --- a/lib/src/contracts/highload/constant/constant.dart +++ b/lib/src/contracts/wallet_contracts/constant/constants/highload.dart @@ -1,9 +1,14 @@ +import 'package:ton_dart/src/boc/boc.dart'; + class HighloadWalletConst { static const int transferOp = 0xae42e5a4; static const int defaultHighLoadSubWallet = 0x10ad; static const int highLoadTimeStampSize = 64; static const int highLoadTimeOutSize = 22; static const int defaultTimeout = 128; - static const String hightloadWallet3State = + static const String _hightloadWallet3State = "te6cckECEAEAAigAART/APSkE/S88sgLAQIBIAINAgFIAwQAeNAg10vAAQHAYLCRW+EB0NMDAXGwkVvg+kAw+CjHBbORMODTHwGCEK5C5aS6nYBA1yHXTPgqAe1V+wTgMAIBIAUKAgJzBgcAEa3OdqJoa4X/wAIBIAgJABqrtu1E0IEBItch1ws/ABiqO+1E0IMH1yHXCx8CASALDAAbuabu1E0IEBYtch1wsVgA5bi/Ltou37IasJAoQJsO1E0IEBINch9AT0BNM/0xXRBY4b+CMloVIQuZ8ybfgjBaoAFaESuZIwbd6SMDPikjAz4lIwgA30D2+hntAh1yHXCgCVXwN/2zHgkTDiWYAN9A9voZzQAdch1woAk3/bMeCRW+JwgB9vLUgwjXGNEh+QDtRNDT/9Mf9AT0BNM/0xXR+CMhoVIguY4SM234IySqAKESuZJtMt5Y+CMB3lQWdfkQ8qEG0NMf1NMH0wzTCdM/0xXRUWi68qJRWrrypvgjKqFSULzyowT4I7vyo1MEgA30D2+hmdAk1yHXCgDyZJEw4g4B/lMJgA30D2+hjhPQUATXGNIAAfJkyFjPFs+DAc8WjhAwyCTPQM+DhAlQBaGlFM9A4vgAyUA5gA30FwTIy/8Tyx/0ABL0ABLLPxLLFcntVPgPIdDTAAHyZdMCAXGwkl8D4PpAAdcLAcAA8qX6QDH6ADH0AfoAMfoAMYBg1yHTAAEPACDyZdIAAZPUMdGRMOJysfsAtYW/Aw=="; + static Cell code() { + return Cell.fromBase64(_hightloadWallet3State); + } } diff --git a/lib/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart b/lib/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart new file mode 100644 index 0000000..189a0fb --- /dev/null +++ b/lib/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart @@ -0,0 +1,18 @@ +class MultiOwnerContractConst { + static const String librianCode = + "b5ee9c72410104010099000114ff00f4a413f4bcf2c80b01037ed33031d0d3030130fa4030ed44f807218103e8f94130f8075003a17ff83b02821012cc03007ff837a08010fb020170c880108306db3c72fb0688fb0488ed54030202000000888e40c85801cb055004cf1658fa02547120ed44ed45ed479d5bc85003cf17c9127158cb6acced67ed65ed64737fed11977001cb6a01cf17ed41edf101f2ffc901fb00db05afd31959"; + static const String orderHashCode = + "b5ee9c72410101010023000842026305a8061c856c2ccf05dcb0df5815c71475870567cab5f049e340bcf59251f34028268d"; + static const String multisigCode = + "b5ee9c7241021201000495000114ff00f4a413f4bcf2c80b010201620802020120060302016605040159b0c9fe0a00405c00b21633c5804072fff26208b232c07d003d0032c0325c007e401d3232c084b281f2fff274201100f1b0cafb513434ffc04074c1c0407534c1c0407d01348000407448dfdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b08548c2ebcb81b085fdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b084c08b0803cb81b8930803cb81b5490eefcb81b40648cdfe440f880e00143bf74ff6a26869ff8080e9838080ea69838080fa0269000080e8881aaf8280fc11d0c0700c2f80703830cf94130038308f94130f8075006a18127f801a070f83681120670f836a0812bec70f836a0811d9870f836a022a60622a081053926a027a070f83823a481029827a070f838a003a60658a08106e05005a05005a0430370f83759a001a002cad033d0d3030171b0925f03e0fa403022d749c000925f03e002d31f0120c000925f04e001d33f01ed44d0d3ff0101d3070101d4d3070101f404d2000101d1288210f718510fbae30f054443c8500601cbff500401cb0712cc0101cb07f4000101ca00c9ed540d09029a363826821075097f5dba8eba068210a32c59bfba8ea9f82818c705f2e06503d4d1103410364650f8007f8e8d2178f47c6fa5209132e30d01b3e65b10355034923436e2505413e30d40155033040b0a02e23604d3ff0101d32f0101d3070101d3ff0101d4d1f8285005017002c858cf160101cbffc98822c8cb01f400f400cb00c97001f90074c8cb0212ca07cbffc9d01bc705f2e06526f9001aba5193be19b0f2e06607f823bef2e06f44145056f8007f8e8d2178f47c6fa5209132e30d01b3e65b110b01fa02d74cd0d31f01208210f1381e5bba8e6a82101d0cfbd3ba8e5e6c44d3070101d4217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2120c200f2e06e23c200f2e06d5330bbf2e06d01f404217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2130d155239130e2e30d0c001030d307d402fb00d1019e3806d3ff0128b38e122084ffba923024965305baf2e3f0e205a405de01d2000101d3070101d32f0101d4d1239126912ae2523078f40e6fa1f2e3ef1ec705f2e3ef20f823bef2e06f20f823a1546d700e01d4f80703830cf94130038308f94130f8075006a18127f801a070f83681120670f836a0812bec70f836a0811d9870f836a022a60622a081053926a027a070f83823a481029827a070f838a003a60658a08106e05005a05005a0430370f83759a001a01cbef2e064f82850030f02b8017002c858cf160101cbffc98822c8cb01f400f400cb00c97021f90074c8cb0212ca07cbffc9d0c882109c73fba2580a02cb1fcb3f2601cb075250cc500b01cb2f1bcc2a01ca000a951901cb07089130e2102470408980188050db3c111000928e45c85801cb055005cf165003fa0254712323ed44ed45ed479f5bc85003cf17c913775003cb6bcccced67ed65ed64747fed11987601cb6bcc01cf17ed41edf101f2ffc901fb00db060842026305a8061c856c2ccf05dcb0df5815c71475870567cab5f049e340bcf59251f3ada4ac42"; + static const String multiSigCodeTestNet = + "b5ee9c7241021201000495000114ff00f4a413f4bcf2c80b010201620802020120060302016605040159b0c9fe0a00405c00b21633c5804072fff26208b232c07d003d0032c0325fc07e401d3232c084b281f2fff274201100f1b0cafb513434ffc04074c1c0407534c1c0407d01348000407448dfdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b08548c2ebcb81b085fdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b084c08b0803cb81b8930803cb81b5490eefcb81b40648cdfe440f880e00143bf74ff6a26869ff8080e9838080ea69838080fa0269000080e8881aaf8280fc11d0c0700c2f80703830cf94130038308f94130f8075006a18127f801a07ff8368112067ff836a0812bec7ff836a0811d987ff836a022a60622a081053926a027a07ff83823a481029827a07ff838a003a60658a08106e05005a05005a043037ff83759a001a002cad033d0d3030171b0925f03e0fa403022d749c000925f03e002d31f0120c000925f04e001d33f01ed44d0d3ff0101d3070101d4d3070101f404d2000101d1288210f718510fbae30f054443c8500601cbff500401cb0712cc0101cb07f4000101ca00c9ed540d09029a363826821075097f5dba8eba068210a32c59bfba8ea9f82818c705f2e06503d4d1103410364650f8007f8e8d2178f47c6fa5209132e30d01b3e65b10355034923436e2505413e30d40155033040b0a02e23604d3ff0101d32f0101d3070101d3ff0101d4d1f8285005017002c858cf160101cbffc98822c8cb01f400f400cb00c97f01f90074c8cb0212ca07cbffc9d01bc705f2e06526f9001aba5193be19b0f2e06607f823bef2e06f44145056f8007f8e8d2178f47c6fa5209132e30d01b3e65b110b01fa02d74cd0d31f01208210f1381e5bba8e6a82101d0cfbd3ba8e5e6c44d3070101d4217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2120c200f2e06e23c200f2e06d5330bbf2e06d01f404217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2130d155239130e2e30d0c001030d307d402fb00d1019e3806d3ff0128b38e122084ffba923024965305baf2e3f0e205a405de01d2000101d3070101d32f0101d4d1239126912ae2523078f40e6fa1f2e3ef1ec705f2e3ef20f823bef2e06f20f823a1546d700e01d4f80703830cf94130038308f94130f8075006a18127f801a07ff8368112067ff836a0812bec7ff836a0811d987ff836a022a60622a081053926a027a07ff83823a481029827a07ff838a003a60658a08106e05005a05005a043037ff83759a001a01cbef2e064f82850030f02b8017002c858cf160101cbffc98822c8cb01f400f400cb00c97f21f90074c8cb0212ca07cbffc9d0c882109c73fba2580a02cb1fcb3f2601cb075250cc500b01cb2f1bcc2a01ca000a951901cb07089130e2102470408980188050db3c111000928e45c85801cb055005cf165003fa0254712323ed44ed45ed479f5bc85003cf17c913775003cb6bcccced67ed65ed64747fed11987601cb6bcc01cf17ed41edf101f2ffc901fb00db060842026305a8061c856c2ccf05dcb0df5815c71475870567cab5f049e340bcf59251f37effe17b"; + static const int orderInitOperation = 0x9c73fba2; + static const int approveOperation = 0xa762230f; + static const int sendTransferOperation = 0xf1381e5b; + static const int updateTransferOperation = 0x1d0cfbd3; + static const int executeInternalOperantion = 0xa32c59bf; + static final BigInt defaultOrderId = BigInt.parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935"); + static const int newOrderOperation = 0xf718510f; +} diff --git a/lib/src/contracts/wallet/constant/constant.dart b/lib/src/contracts/wallet_contracts/constant/constants/versioned.dart similarity index 72% rename from lib/src/contracts/wallet/constant/constant.dart rename to lib/src/contracts/wallet_contracts/constant/constants/versioned.dart index de20a0e..226b41d 100644 --- a/lib/src/contracts/wallet/constant/constant.dart +++ b/lib/src/contracts/wallet_contracts/constant/constants/versioned.dart @@ -15,6 +15,7 @@ class VersionedWalletConst { "te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0="; static const String v4R2State = "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=="; - + static const String v5R1State = + "te6cckECFAEAAoEAART/APSkE/S88sgLAQIBIAINAgFIAwQC3NAg10nBIJFbj2Mg1wsfIIIQZXh0br0hghBzaW50vbCSXwPgghBleHRuuo60gCDXIQHQdNch+kAw+kT4KPpEMFi9kVvg7UTQgQFB1yH0BYMH9A5voTGRMOGAQNchcH/bPOAxINdJgQKAuZEw4HDiEA8CASAFDAIBIAYJAgFuBwgAGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8ACAUgKCwAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAGb5fD2omhAgKDrkPoCwBAvIOAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNC01sNe"; static const int defaultSubWalletId = 698983191; } diff --git a/lib/src/contracts/wallet_contracts/contracts/contracts.dart b/lib/src/contracts/wallet_contracts/contracts/contracts.dart new file mode 100644 index 0000000..5657fec --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/contracts.dart @@ -0,0 +1,12 @@ +export 'highload/v3.dart'; +export 'multi_owner/multisig.dart'; +export 'multi_owner/order.dart'; +export 'versioned/v1r1.dart'; +export 'versioned/v1r2.dart'; +export 'versioned/v1r3.dart'; +export 'versioned/v2r1.dart'; +export 'versioned/v2r2.dart'; +export 'versioned/v3r1.dart'; +export 'versioned/v3r2.dart'; +export 'versioned/v4.dart'; +export 'versioned/v5r1.dart'; diff --git a/lib/src/contracts/wallet_contracts/contracts/highload/v3.dart b/lib/src/contracts/wallet_contracts/contracts/highload/v3.dart new file mode 100644 index 0000000..0437912 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/highload/v3.dart @@ -0,0 +1,213 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/highload.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/highload.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constants/highload.dart'; +import 'package:ton_dart/src/crypto/keypair/private_key.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/provider/highload.dart'; + +class HighloadWalletV3 extends HighloadWallets + with HighloadWalletV3ProviderImpl { + HighloadWalletV3( + {required TonAddress address, HighloadWalletV3State? stateInit}) + : super(stateInit: stateInit, address: address); + + factory HighloadWalletV3.create( + {required TonChain chain, + required List publicKey, + int? subWalletId, + int timeout = HighloadWalletConst.defaultTimeout}) { + subWalletId ??= + HighloadWalletConst.defaultHighLoadSubWallet + chain.workchain; + final stateInit = HighloadWalletV3State( + publicKey: publicKey, timeout: timeout, subWalletId: subWalletId); + final state = stateInit.initialState(); + return HighloadWalletV3( + address: TonAddress.fromState(state: state, workChain: chain.workchain), + stateInit: stateInit); + } + static Future fromAddress( + {required TonAddress address, required TonProvider rpc}) async { + final st2 = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = HighloadWalletV3State.deserialize(st2.data!.beginParse()); + final walletAddress = TonAddress.fromState( + state: state.initialState(), workChain: address.workChain); + if (walletAddress != address) { + throw TonContractException("Incorrect state address.", details: { + "excepted": walletAddress.toRawAddress(), + "address": address.toRawAddress(), + "workChain": address.workChain + }); + } + return HighloadWalletV3(address: address, stateInit: state); + } + + Cell createInternalTransferBody( + {required List acctions, required BigInt queryId}) { + final Cell actionsCell; + if (acctions.length > 254) { + throw const TonContractException( + "Max allowed action count is 254. Use packActions instead."); + } + final actionsBuilder = beginCell(); + final out = OutActionUtils.storeOutList(acctions); + actionsBuilder.storeSlice(out); + actionsCell = actionsBuilder.endCell(); + return beginCell() + .storeUint(HighloadWalletConst.transferOp, 32) + .storeUint(queryId, 64) + .storeRef(actionsCell) + .endCell(); + } + + MessageRelaxed createInternalTransfer( + {required List messages, + required BigInt value, + required BigInt queryId, + bool? bounce, + bool bounced = false}) { + return MessageRelaxed( + info: CommonMessageInfoRelaxedInternal( + ihrDisabled: true, + bounce: bounce ?? address.isBounceable, + bounced: bounced, + dest: address, + value: CurrencyCollection(coins: value), + ihrFee: BigInt.zero, + forwardFee: BigInt.zero, + createdLt: BigInt.zero, + createdAt: 0, + ), + body: createInternalTransferBody(acctions: messages, queryId: queryId)); + } + + static Message createExternalMessage({ + required TonPrivateKey signer, + required MessageRelaxed message, + required BigInt queryId, + required int subWalletId, + required int timeout, + required TonAddress account, + StateInit? state, + SendMode mode = SendMode.carryAllRemainingBalance, + int? createAt, + }) { + createAt ??= (DateTime.now().millisecondsSinceEpoch ~/ 1000) - 10; + final Cell messageCell = beginCell().store(message).endCell(); + final messageInner = beginCell() + .storeUint(subWalletId, 32) + .storeRef(messageCell) + .storeUint(mode.mode, 8) + .storeUint(queryId, 23) + .storeUint(createAt, HighloadWalletConst.highLoadTimeStampSize) + .storeUint(timeout, HighloadWalletConst.highLoadTimeOutSize) + .endCell(); + + final body = beginCell() + .storeBuffer(signer.sign(messageInner.hash())) + .storeRef(messageInner) + .endCell(); + final extMessage = Message( + init: state, + info: + CommonMessageInfoExternalIn(dest: account, importFee: BigInt.zero), + body: body); + return extMessage; + } + + MessageRelaxed packedAction( + {required List messages, + required BigInt value, + required BigInt queryId, + bool? bounce, + bool bounced = false}) { + List batch = []; + if (messages.length > 254) { + batch = messages.sublist(0, 253); + batch.add(OutActionSendMsg( + mode: value > BigInt.zero + ? SendMode.payGasSeparately.mode + : SendMode.carryAllRemainingBalance.mode, + outMessage: packedAction( + messages: messages.sublist(253), + value: value, + queryId: queryId))); + } else { + batch = messages; + } + return createInternalTransfer( + messages: batch, + value: value, + queryId: queryId, + bounce: bounce, + bounced: bounced); + } + + Message createAndSignExternalMessage( + {required TonPrivateKey signer, + required MessageRelaxed message, + required BigInt queryId, + SendMode mode = SendMode.carryAllRemainingBalance, + int? createAt, + StateInit? initState}) { + return createExternalMessage( + signer: signer, + message: message, + queryId: queryId, + subWalletId: state!.subWalletId, + timeout: state!.timeout, + account: address, + createAt: createAt, + mode: mode, + state: initState); + } + + Future sendBatchTransaction( + {required TonPrivateKey signer, + required List messages, + required BigInt queryId, + required TonProvider rpc, + required BigInt value, + SendMode mode = SendMode.carryAllRemainingBalance, + int? createAt}) async { + final active = await isActive(rpc); + if (!active && state == null) { + throw const TonContractException( + "Cannot Send Batch Transaction with watch only wallet"); + } + final extMessage = createAndSignExternalMessage( + signer: signer, + message: + packedAction(messages: messages, value: value, queryId: queryId), + queryId: queryId, + createAt: createAt, + mode: mode, + initState: active ? null : state!.initialState()); + return sendMessage(rpc: rpc, exMessage: extMessage); + } + + @override + Future sendTransfer( + {required HighloadTransferParams params, + List messages = const [], + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}) { + final actions = messages + .map((e) => OutActionSendMsg(outMessage: e, mode: sendMode)) + .toList(); + return sendBatchTransaction( + signer: params.signer, + messages: [...params.messages, ...actions], + queryId: params.queryId, + rpc: rpc, + value: params.value ?? BigInt.zero); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/multi_owner/multisig.dart b/lib/src/contracts/wallet_contracts/contracts/multi_owner/multisig.dart new file mode 100644 index 0000000..bb7f37d --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/multi_owner/multisig.dart @@ -0,0 +1,347 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/contracts/multi_owner/order.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/types.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/multi_owner.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/helper/ton_helper.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; + +class MultiOwnerContract + extends WalletContract> { + final WalletContract owner; + + MultiOwnerContract({ + required TonAddress address, + required this.owner, + MultiOwnerWalletState? stateInit, + }) : super( + address: address, + state: stateInit, + chain: TonChain.fromWorkchain(address.workChain)); + MultiOwnerContract + changeOwnerWallet( + WalletContract owner) { + return MultiOwnerContract(address: address, owner: owner, stateInit: state); + } + + factory MultiOwnerContract.create( + {required TonChain chain, + required WalletContract owner, + required int threshold, + required List signers, + required List proposers, + required bool allowArbitrarySeqno}) { + final stateInit = MultiOwnerWalletState( + threshold: threshold, + allowArbitrarySeqno: allowArbitrarySeqno, + proposers: proposers, + signers: signers); + final state = stateInit.initialState(chain: chain); + return MultiOwnerContract( + address: TonAddress.fromState( + state: state, workChain: chain.workchain, bounceable: false), + stateInit: stateInit, + owner: owner); + } + static Future fromAddress({ + required TonChain chain, + required WalletContract owner, + required TonAddress address, + required TonProvider provider, + }) async { + final stateData = + await ContractProvider.getActiveState(rpc: provider, address: address); + final state = + MultiOwnerWalletState.deserialize(stateData.data!.beginParse()); + return MultiOwnerContract(address: address, stateInit: state, owner: owner); + } + + Cell initMessageBody() { + return beginCell().storeUint32(0).storeUint64(0).endCell(); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + StateInit? state, + OnEstimateFee? onEstimateFee}) async { + return await owner.sendTransfer( + params: params, + messages: [ + TransactioUtils.internal( + destination: address, + amount: amount, + initState: state, + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ) + ], + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + Future deploy( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + final active = await isActive(rpc); + if (active) { + throw const TonContractException("Account is already active."); + } + if (state == null) { + throw const TonContractException("cannot deploy with watch only wallet."); + } + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: initMessageBody(), + bounce: bounce, + bounced: bounced, + state: state!.initialState(chain: owner.chain), + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Cell packTransferRequest( + {required OutActionSendMsg message, + int sendMode = SendModeConst.payGasSeparately}) { + final messageRef = beginCell().store(message).endCell(); + return beginCell() + .storeUint32(MultiOwnerContractConst.sendTransferOperation) + .storeUint(sendMode, 8) + .storeRef(messageRef) + .endCell(); + } + + Cell packUpdateRequest( + {required int threshold, + required List signers, + required List proposers}) { + return beginCell() + .storeUint32(MultiOwnerContractConst.updateTransferOperation) + .storeUint8(threshold) + .storeRef(beginCell() + .storeDictDirect(MultiOwnerContractUtils.signersToDict(signers)) + .endCell()) + .storeDict(dict: MultiOwnerContractUtils.signersToDict(proposers)) + .endCell(); + } + + Cell packOrder(List actions) { + final orderDict = Dictionary.empty( + key: DictionaryKey.uintCodec(8), value: DictionaryValue.cellCodec()); + if (actions.length > 255) { + throw const TonContractException( + "For action chains above 255, use packLarge method"); + } else { + // pack transfers to the order_body cell + for (int i = 0; i < actions.length; i++) { + final action = actions[i]; + orderDict[i] = beginCell().store(action).endCell(); + } + return beginCell().storeDictDirect(orderDict).endCell(); + } + } + + Cell packLarge( + {required List actions, + required TonAddress address, + required BigInt amount}) { + Cell? tailChunk; + int chunkCount = (actions.length / 254).ceil(); + int actionProcessed = 0; + int lastSz = actions.length % 254; + while (chunkCount > 0) { + chunkCount--; + int chunkSize; + if (lastSz > 0) { + chunkSize = lastSz; + lastSz = 0; + } else { + chunkSize = 254; + } + + // Processing chunks from tail to head to evade recursion + final chunk = actions.sublist( + -(chunkSize + actionProcessed), actions.length - actionProcessed); + + if (tailChunk == null) { + tailChunk = packOrder(chunk); + } else { + tailChunk = packOrder([ + ...chunk, + OutActionMultiSigSendMsg( + mode: SendModeConst.payGasSeparately, + outMessage: TransactioUtils.internal( + destination: address, + amount: amount, + body: beginCell() + .storeUint32( + MultiOwnerContractConst.executeInternalOperantion) + .storeUint64(0) + .storeRef(tailChunk) + .endCell())) + ]); + } + + actionProcessed += chunkSize; + } + + if (tailChunk == null) { + throw const TonContractException( + "Something went wrong during large order pack"); + } + + return tailChunk; + } + + Cell newOrderMessage( + {required Cell actions, + required BigInt expirationDate, + required bool isSigner, + required int addrIdx, + BigInt? orderId, + BigInt? queryId}) { + final msgBody = beginCell() + .storeUint32(MultiOwnerContractConst.newOrderOperation) + .storeUint64(queryId ?? BigInt.zero) + .storeUint256(orderId ?? MultiOwnerContractConst.defaultOrderId) + .storeBitBolean(isSigner) + .storeUint8(addrIdx) + .storeUint(expirationDate, 48); + return msgBody.storeRef(actions).endCell(); + } + + Future sendNewOrder( + {required TonProvider rpc, + required E params, + required BigInt expirationDate, + required BigInt amount, + required List messages, + BigInt? orderId, + BigInt? queryId}) async { + final active = await isActive(rpc); + if (state == null) { + throw const TonContractException( + "Cannot create new order with watch only wallet"); + } + int addrIdx = state!.signers.indexOf(owner.address); + bool isSigner; + if (addrIdx >= 0) { + isSigner = true; + } else { + addrIdx = state!.proposers.indexOf(owner.address); + if (addrIdx < 0) { + throw const TonContractException( + "the owner is not a signer or proposer."); + } + isSigner = false; + } + Cell actionCell; + if (messages.length > 255) { + actionCell = packLarge( + actions: messages, + address: address, + amount: TonHelper.toNano("0.01")); + } else { + actionCell = packOrder(messages); + } + final body = newOrderMessage( + actions: actionCell, + expirationDate: expirationDate, + isSigner: isSigner, + addrIdx: addrIdx, + orderId: orderId, + queryId: queryId, + ); + + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + body: body, + state: active ? null : state!.initialState(chain: owner.chain)); + } + + Future getStateData(TonProvider rpc) async { + final state = + await ContractProvider.getActiveState(rpc: rpc, address: address); + return MultiOwnerWalletState.deserialize(state.data!.beginParse()); + } + + Future orderAddress( + {required TonProvider rpc, required BigInt seqno}) async { + final call = + await getStateStack(rpc: rpc, method: "get_order_address", stack: [ + if (rpc.isTonCenter) ...[ + ["num", seqno.toString()] + ] else + seqno.toString() + ]); + return call.reader().readAddress(); + } + + Future> + getOrderContract( + {required TonProvider rpc, + required BigInt seqno, + required WalletContract signerWallet}) async { + final call = + await getStateStack(rpc: rpc, method: "get_order_address", stack: [ + if (rpc.isTonCenter) ...[ + ["num", seqno.toString()] + ] else + seqno.toString() + ]); + final orderAddress = call.reader().readAddress(); + final orderState = + await ContractProvider.getActiveState(rpc: rpc, address: orderAddress); + final state = OrderContractState.deserialize(orderState.data!.beginParse()); + return OrderContract( + address: orderAddress, owner: signerWallet, state: state); + } + + @override + Future sendTransfer( + {required MultiOwnerTransferParams params, + List messages = const [], + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}) { + final actions = messages + .map((e) => OutActionMultiSigSendMsg(outMessage: e, mode: sendMode)) + .toList(); + return sendNewOrder( + rpc: rpc, + params: params.params, + amount: params.amount, + expirationDate: params.expirationDate, + messages: [...params.messages, ...actions], + orderId: params.orderId, + queryId: params.queryId); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/multi_owner/order.dart b/lib/src/contracts/wallet_contracts/contracts/multi_owner/order.dart new file mode 100644 index 0000000..34d8839 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/multi_owner/order.dart @@ -0,0 +1,174 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constants/mutli_owner.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/order.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/multi_owner.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; + +class OrderContract + extends TonContract { + @override + final TonAddress address; + + @override + final OrderContractState? state; + final WalletContract owner; + const OrderContract({required this.address, required this.owner, this.state}); + + factory OrderContract.create( + {TonChain? chain, + required TonAddress multisig, + required BigInt orderSeqno, + required WalletContract owner}) { + final stateInit = + OrderContractState(multisig: multisig, orderSeqno: orderSeqno); + final state = stateInit.initialState(); + return OrderContract( + address: TonAddress.fromState( + state: state, workChain: chain?.workchain ?? owner.chain.workchain), + owner: owner, + state: stateInit); + } + OrderContract changeOwnerWallet( + WalletContract wallet) { + return OrderContract(address: address, owner: wallet, state: state); + } + + Future _sendTransaction( + {required E params, + required TonProvider rpc, + required BigInt amount, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body, + StateInit? state, + OnEstimateFee? onEstimateFee}) async { + return await owner.sendTransfer( + params: params, + messages: [ + TransactioUtils.internal( + destination: address, + amount: amount, + initState: state, + bounced: bounced, + body: body, + bounce: bounce ?? address.isBounceable, + ) + ], + rpc: rpc, + timeout: timeout, + sendMode: sendMode, + onEstimateFee: onEstimateFee); + } + + Cell initMessageBody( + {required List signers, + required BigInt expirationDate, + required Cell order, + required int threshold, + bool approveOnInit = false, + int signerIdx = 0, + BigInt? queryId}) { + final signersDict = MultiOwnerContractUtils.signersToDict(signers); + final msgBody = beginCell() + .storeUint32(MultiOwnerContractConst.orderInitOperation) + .storeUint64(queryId ?? BigInt.zero) + .storeUint8(threshold) + .storeRef(beginCell().storeDictDirect(signersDict).endCell()) + .storeUint(expirationDate, 48) + .storeRef(order) + .storeBitBolean(approveOnInit); + + if (approveOnInit) { + msgBody.storeUint8(signerIdx); + } + return msgBody.endCell(); + } + + Cell approveMessageBody({BigInt? queryId, required int signerIdx}) { + return beginCell() + .storeUint32(MultiOwnerContractConst.approveOperation) + .storeUint64(queryId ?? BigInt.zero) + .storeUint8(signerIdx) + .endCell(); + } + + Future sendApprove( + {required E params, + required TonProvider rpc, + required BigInt amount, + int? signerIdx, + BigInt? queryId, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + OnEstimateFee? onEstimateFee}) async { + final active = await isActive(rpc); + if (!active) { + throw const TonContractException( + "canoot send approve before deploying contarct. please first use deply method for deploying."); + } + if (signerIdx == null) { + if (state == null) { + throw const TonContractException( + "cannot send approve without known signer index. please first use deply method for deploying."); + } + signerIdx = state!.signers.indexOf(owner.address); + } + if (signerIdx < 0) { + throw const TonContractException("cannot find signer."); + } + return _sendTransaction( + params: params, + rpc: rpc, + amount: amount, + sendMode: sendMode, + body: approveMessageBody(signerIdx: signerIdx, queryId: queryId), + bounce: bounce, + bounced: bounced, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future getOrderData(TonProvider rpc) async { + final state = + await getStateStack(rpc: rpc, method: "get_order_data", stack: []); + final stack = state.reader(); + final multisig = stack.readAddress(); + final orderSeqno = stack.readBigNumber(); + final threshold = stack.readNumberOpt(); + final executed = stack.readBooleanOpt(); + final signers = + MultiOwnerContractUtils.signerCellToList(stack.readCellOpt()); + final approvals = stack.readBigNumberOpt(); + final approvalsNum = stack.readNumberOpt(); + final expirationDate = stack.readBigNumberOpt(); + final order = stack.readCellOpt(); + List approvalsArray = []; + if (approvals != null) { + approvalsArray = List.filled(256, false); + for (int i = 0; i < 256; i++) { + approvalsArray[i] = + ((BigInt.one << i) & approvals) == BigInt.zero ? false : true; + } + } + return OrderContractState( + multisig: multisig, + orderSeqno: orderSeqno, + threshold: threshold, + approvals: approvals, + approvalsNum: approvalsNum, + executed: executed, + signers: signers, + expirationDate: expirationDate, + order: order); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v1r1.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r1.dart new file mode 100644 index 0000000..31b1b19 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r1.dart @@ -0,0 +1,57 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This is the simplest one. It only allows you to send one transaction at +/// the time and it doesn't check anything besides your signature and seqno. +/// This version isn't even used in regular apps because it has some major issues: +/// No easy way to retrieve the seqno and public key from the contract +/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. +/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV1R1 extends VersionedWalletContract< + NoneSubWalletVersionedWalletState, VersionedTransferParams> { + WalletV1R1( + {NoneSubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v1R1, + chain: chain); + + factory WalletV1R1.create( + {required TonChain chain, + required List publicKey, + bool bounceableAddress = false}) { + final state = NoneSubWalletVersionedWalletState( + publicKey: publicKey, version: WalletVersion.v1R1); + return WalletV1R1( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress< + NoneSubWalletVersionedWalletState>( + address: address, + stateData: data.data, + type: WalletVersion.v1R1, + chain: chain); + return WalletV1R1(address: address, stateInit: state); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v1r2.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r2.dart new file mode 100644 index 0000000..1ae96f9 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r2.dart @@ -0,0 +1,60 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This is the simplest one. It only allows you to send one transaction at +/// the time and it doesn't check anything besides your signature and seqno. +/// This version isn't even used in regular apps because it has some major issues: +/// No easy way to retrieve the seqno and public key from the contract +/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. +/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV1R2 extends VersionedWalletContract< + NoneSubWalletVersionedWalletState, VersionedTransferParams> { + WalletV1R2( + {NoneSubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v1R2, + chain: chain); + + factory WalletV1R2.create( + {required TonChain chain, + required List publicKey, + bool bounceableAddress = false}) { + final state = NoneSubWalletVersionedWalletState( + publicKey: publicKey, version: WalletVersion.v1R2); + return WalletV1R2( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress< + NoneSubWalletVersionedWalletState>( + address: address, + stateData: data.data, + type: WalletVersion.v1R2, + chain: chain); + return WalletV1R2(address: address, stateInit: state); + } + + factory WalletV1R2.watch(TonAddress address) { + return WalletV1R2(address: address); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v1r3.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r3.dart new file mode 100644 index 0000000..05be116 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v1r3.dart @@ -0,0 +1,56 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This is the simplest one. It only allows you to send one transaction at +/// the time and it doesn't check anything besides your signature and seqno. +/// This version isn't even used in regular apps because it has some major issues: +/// No easy way to retrieve the seqno and public key from the contract +/// No valid_until check, so you can't be sure that the transaction won't be confirmed too late. +/// The first issue is fixed in V1R2 and V1R3. That R letter means revision. Usually revisions are just small updates which only add get-methods which allows you to retrieve seqno and public key from the contract. But this version also has a second issue, which is fixed in the next version. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV1R3 extends VersionedWalletContract< + NoneSubWalletVersionedWalletState, VersionedTransferParams> { + WalletV1R3( + {NoneSubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v1R3, + chain: chain); + + factory WalletV1R3.create( + {required TonChain chain, + required List publicKey, + bool bounceableAddress = false}) { + final state = NoneSubWalletVersionedWalletState( + publicKey: publicKey, version: WalletVersion.v1R3); + return WalletV1R3( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress< + NoneSubWalletVersionedWalletState>( + address: address, + stateData: data.data, + type: WalletVersion.v1R3, + chain: chain); + return WalletV1R3(address: address, stateInit: state); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v2r1.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v2r1.dart new file mode 100644 index 0000000..1c64c43 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v2r1.dart @@ -0,0 +1,54 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This version introduces the valid_until parameter which is used to set a time limit for a +/// transaction in case you don't want it to be confirmed too late. This version also doesn't +/// have the get-method for public key, which is added in V2R2. +/// It can be used in most cases, but it misses one cool feature, which was added in V3. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV2R1 extends VersionedWalletContract< + NoneSubWalletVersionedWalletState, VersionedTransferParams> { + WalletV2R1( + {NoneSubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v2R1, + chain: chain); + + factory WalletV2R1.create( + {required TonChain chain, + required List publicKey, + bool bounceableAddress = false}) { + final state = NoneSubWalletVersionedWalletState( + publicKey: publicKey, version: WalletVersion.v2R1); + return WalletV2R1( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress< + NoneSubWalletVersionedWalletState>( + address: address, + stateData: data.data, + type: WalletVersion.v2R1, + chain: chain); + return WalletV2R1(address: address, stateInit: state); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v2r2.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v2r2.dart new file mode 100644 index 0000000..76a3904 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v2r2.dart @@ -0,0 +1,54 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This version introduces the valid_until parameter which is used to set a time limit for a +/// transaction in case you don't want it to be confirmed too late. This version also doesn't +/// have the get-method for public key, which is added in V2R2. +/// It can be used in most cases, but it misses one cool feature, which was added in V3. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV2R2 extends VersionedWalletContract< + NoneSubWalletVersionedWalletState, VersionedTransferParams> { + WalletV2R2( + {NoneSubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v2R2, + chain: chain); + + factory WalletV2R2.create( + {required TonChain chain, + required List publicKey, + bool bounceableAddress = false}) { + final state = NoneSubWalletVersionedWalletState( + publicKey: publicKey, version: WalletVersion.v2R2); + return WalletV2R2( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress< + NoneSubWalletVersionedWalletState>( + address: address, + stateData: data.data, + type: WalletVersion.v2R2, + chain: chain); + return WalletV2R2(address: address, stateInit: state); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v3r1.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v3r1.dart new file mode 100644 index 0000000..8e2a10a --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v3r1.dart @@ -0,0 +1,64 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constant.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This version introduces the subwallet_id parameter, which allows you to create multiple wallets +/// using the same public key (so you can have only one seed phrase and lots of wallets). And, +/// as before, V3R2 only adds the get-method for public key. +/// Basically, subwallet_id is just a number which is added to the contract state when it is deployed. +/// And since the contract address in TON is a hash of its state and code, the wallet address will change with a different subwallet_id. +/// This version is the most used right now. It covers most use-cases and remains clean and simple. +class WalletV3R1 extends VersionedWalletContract { + WalletV3R1( + {SubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + type: WalletVersion.v3R1, + stateInit: stateInit, + chain: chain); + + factory WalletV3R1.create( + {required TonChain chain, + required List publicKey, + int? subWalletId, + bool bounceableAddress = false}) { + subWalletId ??= VersionedWalletConst.defaultSubWalletId + chain.workchain; + final state = SubWalletVersionedWalletState( + publicKey: publicKey, + version: WalletVersion.v3R1, + subwallet: subWalletId); + return WalletV3R1( + stateInit: state, + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + VersionedWalletUtils.buildFromAddress( + address: address, + stateData: data.data, + type: WalletVersion.v3R1, + chain: chain); + return WalletV3R1(address: address, stateInit: state); + } + + factory WalletV3R1.watch(TonAddress address) { + return WalletV3R1(address: address); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v3r2.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v3r2.dart new file mode 100644 index 0000000..848aed8 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v3r2.dart @@ -0,0 +1,65 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constant.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This version introduces the subwallet_id parameter, which allows you to create multiple wallets +/// using the same public key (so you can have only one seed phrase and lots of wallets). And, +/// as before, V3R2 only adds the get-method for public key. +/// Basically, subwallet_id is just a number which is added to the contract state when it is deployed. +/// And since the contract address in TON is a hash of its state and code, the wallet address will change with a different subwallet_id. +/// This version is the most used right now. It covers most use-cases and remains clean and simple. +/// https://docs.ton.org/participate/wallets/contracts +class WalletV3R2 extends VersionedWalletContract { + WalletV3R2( + {SubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + type: WalletVersion.v3R2, + stateInit: stateInit, + chain: chain); + + factory WalletV3R2.create( + {required TonChain chain, + required List publicKey, + int? subWalletId, + bool bounceableAddress = false}) { + subWalletId ??= VersionedWalletConst.defaultSubWalletId + chain.workchain; + final state = SubWalletVersionedWalletState( + publicKey: publicKey, + version: WalletVersion.v3R2, + subwallet: subWalletId); + return WalletV3R2( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + VersionedWalletUtils.buildFromAddress( + address: address, + stateData: data.data, + type: WalletVersion.v3R2, + chain: chain); + return WalletV3R2(address: address, stateInit: state, chain: chain); + } + + factory WalletV3R2.watch(TonAddress address) { + return WalletV3R2(address: address); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v4.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v4.dart new file mode 100644 index 0000000..e1aed58 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v4.dart @@ -0,0 +1,67 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constant.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// It is the most modern wallet version at the moment. It still has all the functionality of the previous versions, +/// but also introduces something very powerful — plugins. +/// This feature allows developers to implement complex logic that will work in tandem with a +/// user's wallet. For example, some DApp may require a user to pay a small amount of coins every day +/// to use some features, so the user will need to install the plugin on their wallet by signing a transaction. +/// This plugin will send coins to the destination address every day when it will be reqested by an external message. +/// This is a very customizable feature which is unique to TON Blockchain. +/// https://docs.ton.org/participate/wallets/contracts#wallet-v4 +class WalletV4 extends VersionedWalletContract { + WalletV4( + {SubWalletVersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + type: WalletVersion.v4, + stateInit: stateInit, + chain: chain); + + factory WalletV4.create( + {required TonChain chain, + required List publicKey, + int? subWalletId, + bool bounceableAddress = false}) { + subWalletId ??= VersionedWalletConst.defaultSubWalletId + chain.workchain; + final state = SubWalletVersionedWalletState( + publicKey: publicKey, + version: WalletVersion.v4, + subwallet: subWalletId); + return WalletV4( + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain.workchain, + bounceable: bounceableAddress), + stateInit: state, + chain: chain); + } + + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + VersionedWalletUtils.buildFromAddress( + address: address, + stateData: data.data, + type: WalletVersion.v4, + chain: chain); + return WalletV4(address: address, stateInit: state, chain: chain); + } + + factory WalletV4.watch(TonAddress address) { + return WalletV4(address: address); + } +} diff --git a/lib/src/contracts/wallet_contracts/contracts/versioned/v5r1.dart b/lib/src/contracts/wallet_contracts/contracts/versioned/v5r1.dart new file mode 100644 index 0000000..3af73c1 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/contracts/versioned/v5r1.dart @@ -0,0 +1,213 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned_v5.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/models/v5_client_id.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/crypto/crypto.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +/// This is an extensible wallet specification aimed at replacing V4 and allowing arbitrary extensions. +/// W5 has 25% lower fees, supports gasless transactions (via third party relayers) and implements a flexible extension mechanism. +/// https://github.com/tonkeeper/w5/tree/main +class WalletV5R1 extends VersionedWalletContract { + WalletV5R1( + {V5VersionedWalletState? stateInit, + required TonAddress address, + TonChain? chain}) + : super( + address: address, + stateInit: stateInit, + type: WalletVersion.v5R1, + chain: chain); + + factory WalletV5R1.create( + {required V5R1Context context, + required List publicKey, + TonChain? chain, + bool bounceableAddress = false}) { + if (context is V5R1ClientContext) { + chain = context.chain; + } + final state = V5VersionedWalletState( + publicKey: publicKey, version: WalletVersion.v5R1, context: context); + return WalletV5R1( + stateInit: state, + address: TonAddress.fromState( + state: state.initialState(), + workChain: chain?.workchain ?? 0, + bounceable: bounceableAddress), + ); + } + + static Future fromAddress( + {required TonAddress address, + required TonProvider rpc, + TonChain? chain}) async { + final data = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = VersionedWalletUtils.buildFromAddress( + address: address, + stateData: data.data, + type: WalletVersion.v5R1, + chain: chain); + return WalletV5R1(address: address, stateInit: state, chain: chain); + } + + factory WalletV5R1.watch(TonAddress address) { + return WalletV5R1(address: address); + } + + static List messageRelaxedToActions( + {required List messages, + int sendMode = SendModeConst.payGasSeparately}) { + return messages + .map((e) => OutActionSendMsg(mode: sendMode, outMessage: e)) + .toList(); + } + + Cell createRequest( + {required List actions, + WalletV5AuthType type = WalletV5AuthType.external, + int? accountSeqno, + int? timeout, + BigInt? queryId}) { + if (type != WalletV5AuthType.extension) { + if (accountSeqno == null || state?.context == null) { + throw TonContractException( + "accountSeqno and wallet context required for build wallet message v5R1 in $type type."); + } + } + return TransactioUtils.createV5( + accountSeqno: accountSeqno, + actions: OutActionsV5(actions: actions), + type: type, + timeout: timeout, + queryId: queryId, + context: state?.context); + } + + @override + Future sendTransfer( + {required VersionedV5TransferParams params, + required TonProvider rpc, + List messages = const [], + List v5Messages = const [], + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}) async { + final VersionedWalletState? state = await getContractState(rpc); + if (state == null && this.state == null) { + throw const TonContractException( + "cannot send transaction with watch only wallet"); + } + final List actions = [ + ...messages.map((e) => OutActionSendMsg(mode: sendMode, outMessage: e)), + ...params.messages, + ...v5Messages + ]; + final hasDeploy = actions.whereType().any( + (e) => e.outMessage.info.dest == address && e.outMessage.init != null); + if (state != null && hasDeploy) { + throw TonContractException( + "Account is already active. should not add init state to message with with destination address $address"); + } + final message = createRequest( + actions: actions, + type: params.type, + accountSeqno: (state?.seqno) ?? 0, + queryId: params.queryId + // queryId: params. + ); + if (params.type == WalletV5AuthType.extension) { + return message.toBase64(); + } + final body = beginCell() + .storeSlice(message.beginParse()) + .storeBuffer(params.privateKey!.sign(message.hash())) + .endCell(); + final ext = Message( + init: (state == null ? this.state!.initialState() : null), + info: + CommonMessageInfoExternalIn(dest: address, importFee: BigInt.zero), + body: body); + if (onEstimateFee != null) { + await onEstimateFee(ext); + } + return sendMessage(rpc: rpc, exMessage: ext); + } + + Future sendAddExtention( + {required VersionedV5TransferParams params, + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + WalletV5AuthType type = WalletV5AuthType.external, + int? timeout, + OnEstimateFee? onEstimateFee}) async { + return sendActionRequest( + actions: [OutActionAddExtension(address)], + params: params, + rpc: rpc, + sendMode: sendMode, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future sendRemoveExtention( + {required VersionedV5TransferParams params, + required TonPrivateKey privateKey, + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + WalletV5AuthType type = WalletV5AuthType.external, + int? timeout, + OnEstimateFee? onEstimateFee}) async { + return sendActionRequest( + actions: [OutActionRemoveExtension(address)], + params: params, + rpc: rpc, + sendMode: sendMode, + timeout: timeout, + onEstimateFee: onEstimateFee); + } + + Future sendActionRequest( + {required VersionedV5TransferParams params, + required TonProvider rpc, + List actions = const [], + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}) async { + if (params.type == WalletV5AuthType.extension) { + throw const TonContractException( + "use create request instead sendActionRequest for build message body."); + } + if (state == null) { + throw const TonContractException( + "cannot create request with watch only wallet."); + } + return sendTransfer( + params: params, + rpc: rpc, + messages: const [], + v5Messages: actions, + onEstimateFee: onEstimateFee, + timeout: timeout, + sendMode: sendMode); + } + + Future> getExtensions(TonProvider rpc) async { + final state = await readState(rpc); + return state.extensionPubKeys; + } + + Future setPubKeyEnabled(TonProvider rpc) async { + final state = await readState(rpc); + return state.setPubKeyEnabled; + } +} diff --git a/lib/src/contracts/wallet_contracts/core/core.dart b/lib/src/contracts/wallet_contracts/core/core.dart new file mode 100644 index 0000000..61977f3 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/core/core.dart @@ -0,0 +1,3 @@ +export 'core/core.dart'; +export 'versioned/versioned_wallet.dart'; +export 'highload/highload_wallet.dart'; diff --git a/lib/src/contracts/wallet_contracts/core/core/core.dart b/lib/src/contracts/wallet_contracts/core/core/core.dart new file mode 100644 index 0000000..2065d60 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/core/core/core.dart @@ -0,0 +1,26 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider/provider.dart'; + +typedef OnEstimateFee = Future Function(Message message); + +abstract class WalletContract extends TonContract { + const WalletContract( + {required this.state, required this.address, required this.chain}); + @override + final C? state; + + @override + final TonAddress address; + final TonChain chain; + + Future sendTransfer( + {required T params, + required List messages, + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}); +} diff --git a/lib/src/contracts/wallet_contracts/core/highload/highload_wallet.dart b/lib/src/contracts/wallet_contracts/core/highload/highload_wallet.dart new file mode 100644 index 0000000..be22212 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/core/highload/highload_wallet.dart @@ -0,0 +1,15 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/highload.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core/core.dart'; + +abstract class HighloadWallets + extends WalletContract { + HighloadWallets({ + required T? stateInit, + required TonAddress address, + }) : super( + address: address, + chain: TonChain.fromWorkchain(address.workChain), + state: stateInit); +} diff --git a/lib/src/contracts/wallet/core/version.dart b/lib/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart similarity index 61% rename from lib/src/contracts/wallet/core/version.dart rename to lib/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart index 9733409..ae7bd73 100644 --- a/lib/src/contracts/wallet/core/version.dart +++ b/lib/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart @@ -1,6 +1,11 @@ +import 'package:ton_dart/src/address/address.dart'; import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; import 'package:ton_dart/src/contracts/exception/exception.dart'; -import 'package:ton_dart/src/contracts/wallet/constant/constant.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constant.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/provider/versioned.dart'; class WalletVersion { final String name; @@ -14,6 +19,7 @@ class WalletVersion { static const WalletVersion v3R1 = WalletVersion._("v3R1", 3); static const WalletVersion v3R2 = WalletVersion._("v3R2", 3); static const WalletVersion v4 = WalletVersion._("v4", 4); + static const WalletVersion v5R1 = WalletVersion._("v5R1", 5); static const List values = [ v1R1, @@ -23,7 +29,8 @@ class WalletVersion { v2R2, v3R1, v3R2, - v4 + v4, + v5R1 ]; bool get isVersionedWallet { @@ -31,6 +38,12 @@ class WalletVersion { } bool get hasSubwalletId => version > 2; + int get maxMessageLength { + if (version == 1) { + return 1; + } + return 4; + } String get state { switch (this) { @@ -50,6 +63,8 @@ class WalletVersion { return VersionedWalletConst.v3R2State; case WalletVersion.v4: return VersionedWalletConst.v4R2State; + case WalletVersion.v5R1: + return VersionedWalletConst.v5R1State; default: throw UnimplementedError(); } @@ -71,3 +86,21 @@ class WalletVersion { return "WalletVersion.$name"; } } + +abstract class VersionedWalletContract + extends WalletContract + with VerionedProviderImpl { + @override + final WalletVersion type; + + VersionedWalletContract( + {required STATE? stateInit, + required TonChain? chain, + required TonAddress address, + required this.type}) + : super( + state: stateInit, + address: address, + chain: chain ?? TonChain.fromWorkchain(address.workChain)); +} diff --git a/lib/src/contracts/highload/provider/v3.dart b/lib/src/contracts/wallet_contracts/provider/highload.dart similarity index 67% rename from lib/src/contracts/highload/provider/v3.dart rename to lib/src/contracts/wallet_contracts/provider/highload.dart index c634e37..da807af 100644 --- a/lib/src/contracts/highload/provider/v3.dart +++ b/lib/src/contracts/wallet_contracts/provider/highload.dart @@ -1,18 +1,29 @@ -import 'package:ton_dart/src/contracts/highload/core/core.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/highload/highload_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/types.dart'; import 'package:ton_dart/src/provider/provider.dart'; -mixin HighloadWalletV3ProviderImpl on HighloadWallets { - Future getPublicKey({required TonProvider rpc}) async { +mixin HighloadWalletV3ProviderImpl + on HighloadWallets { + Future getPublicKey(TonProvider rpc) async { final result = await getStateStack(rpc: rpc, method: "get_public_key"); final reader = result.reader(); return reader.readBigNumberAsHex(); } - Future getBalance({required TonProvider rpc}) async { + Future getBalance(TonProvider rpc) async { final result = await getState(rpc: rpc); return result.balance; } + Future readState(TonProvider rpc) async { + final stateData = + await ContractProvider.getActiveState(rpc: rpc, address: address); + final state = + HighloadWalletV3State.deserialize(stateData.data!.beginParse()); + return state as T; + } + Future getSubwalletId({required TonProvider rpc}) async { final result = await getStateStack(rpc: rpc, method: "get_subwallet_id"); final reader = result.reader(); diff --git a/lib/src/contracts/models/account_state.dart b/lib/src/contracts/wallet_contracts/provider/models/account_state.dart similarity index 75% rename from lib/src/contracts/models/account_state.dart rename to lib/src/contracts/wallet_contracts/provider/models/account_state.dart index af7688b..4e89815 100644 --- a/lib/src/contracts/models/account_state.dart +++ b/lib/src/contracts/wallet_contracts/provider/models/account_state.dart @@ -1,5 +1,3 @@ -// BlockRawResponse - import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/provider/provider.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; @@ -9,12 +7,11 @@ class AccountStateResponse with JsonSerialization { final Cell? code; final Cell? data; final AccountStatusResponse state; - const AccountStateResponse({ - required this.balance, - required this.code, - required this.data, - required this.state, - }); + const AccountStateResponse( + {required this.balance, + required this.code, + required this.data, + required this.state}); @override Map toJson() { diff --git a/lib/src/contracts/models/run_method_response.dart b/lib/src/contracts/wallet_contracts/provider/models/run_method_response.dart similarity index 77% rename from lib/src/contracts/models/run_method_response.dart rename to lib/src/contracts/wallet_contracts/provider/models/run_method_response.dart index 392a0f3..4696b7a 100644 --- a/lib/src/contracts/models/run_method_response.dart +++ b/lib/src/contracts/wallet_contracts/provider/models/run_method_response.dart @@ -1,12 +1,12 @@ import 'package:ton_dart/src/tuple/tuple/tuple.dart'; import 'package:ton_dart/src/tuple/tuple/tuple_reader.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class RunMethodResponse { final List items; final int exitCode; RunMethodResponse({required List items, required this.exitCode}) - : items = items.mutabl; + : items = items.immutable; TupleReader reader() => TupleReader(items); } diff --git a/lib/src/contracts/wallet_contracts/provider/versioned.dart b/lib/src/contracts/wallet_contracts/provider/versioned.dart new file mode 100644 index 0000000..a96eb80 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/provider/versioned.dart @@ -0,0 +1,115 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core/transfer_params.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/utils/serialization_utils.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/transfer_params/versioned.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/provider/provider.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/utils/versioned.dart'; + +mixin VerionedProviderImpl + on WalletContract { + WalletVersion get type; + Future getSeqno(TonProvider rpc) async { + try { + final state = await readState(rpc); + return state.seqno; + } on TonContractException catch (e) { + if (e == TonContractExceptionConst.stateIsInactive) { + return 0; + } + rethrow; + } + } + + Future getBalance(TonProvider rpc) async { + final state = await getState(rpc: rpc); + return state.balance; + } + + Future getPublicKey(TonProvider rpc) async { + final state = await readState(rpc); + return BytesUtils.toHexString(state.publicKey); + } + + Future readState(TonProvider rpc) async { + final state = await getState(rpc: rpc); + final stateData = VersionedWalletUtils.readState( + stateData: state.data, type: type, chain: chain); + if (stateData is! C) { + throw TonContractException("Incorrect state data.", + details: {"excepted": "$C", "got": "${stateData.runtimeType}"}); + } + return stateData; + } + + Future getContractState(TonProvider rpc) async { + final state = await getState(rpc: rpc); + if (!state.state.isActive) return null; + final stateData = VersionedWalletUtils.readState( + stateData: state.data, type: type, chain: chain); + if (stateData is! C) { + throw TonContractException("Incorrect state data.", + details: {"excepted": "$C", "got": "${stateData.runtimeType}"}); + } + return stateData; + } + + @override + Future sendTransfer( + {required TRANSFERPARAMS params, + required TonProvider rpc, + List messages = const [], + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + OnEstimateFee? onEstimateFee}) async { + if (params is! VersionedTransferParams) { + throw TonContractException("Invalid transaction params", details: { + "excepted": "VersionedTransferParams", + "got": "${params.runtimeType}" + }); + } + final VersionedWalletState? state = await getContractState(rpc); + if (this.state == null && state == null) { + throw const TonContractException( + "cannot send transaction with watch only wallet"); + } + final List actions = [ + ...messages.map((e) => OutActionSendMsg(mode: sendMode, outMessage: e)), + ...params.messages + ]; + + final message = TonSerializationUtils.serializeMessage( + actions: actions, + state: state ?? this.state!, + seqno: state?.seqno ?? 0); + final body = beginCell() + .storeBuffer(params.privateKey.sign(message.hash())) + .storeSlice(message.beginParse()) + .endCell(); + final ext = Message( + init: (state == null ? this.state!.initialState() : null), + info: + CommonMessageInfoExternalIn(dest: address, importFee: BigInt.zero), + body: body); + if (onEstimateFee != null) { + await onEstimateFee(ext); + } + return sendMessage(rpc: rpc, exMessage: ext); + } + + Future deploy( + {required TRANSFERPARAMS params, + required TonProvider rpc, + int sendMode = SendModeConst.payGasSeparately, + int? timeout, + bool? bounce, + bool bounced = false, + Cell? body}) async { + return sendTransfer(params: params, rpc: rpc, messages: []); + } +} diff --git a/lib/src/contracts/wallet_contracts/types/models/v5_client_id.dart b/lib/src/contracts/wallet_contracts/types/models/v5_client_id.dart new file mode 100644 index 0000000..8052ad3 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/models/v5_client_id.dart @@ -0,0 +1,79 @@ +import 'package:ton_dart/src/boc/bit/builder.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/serialization/serialization.dart'; + +abstract class V5R1Context extends TonSerialization { + const V5R1Context({required this.chain}); + final TonChain chain; + abstract final int contextID; + @override + void store(Builder builder) { + final id = (BigInt.from(chain.id) ^ BigInt.from(contextID)).toInt(); + builder.storeInt(id, 32); + } +} + +class V5R1CustomContext extends V5R1Context { + final int context; + const V5R1CustomContext({required this.context, required TonChain chain}) + : super(chain: chain); + + @override + Map toJson() { + return {"context": context, "networkGlobalId": chain.id}; + } + + @override + int get contextID => beginCell() + .storeUint(0, 1) + .storeUint(context, 31) + .endCell() + .beginParse() + .loadInt(32); + + @override + operator ==(other) { + if (other is! V5R1CustomContext) return false; + return other.context == context && other.chain == chain; + } + + @override + int get hashCode => Object.hashAll([context, chain.id]); +} + +class V5R1ClientContext extends V5R1Context { + final int walletVersion = 0; + // final int workchain; + final int subwalletNumber; + + V5R1ClientContext({required this.subwalletNumber, required TonChain chain}) + : super(chain: chain); + + @override + Map toJson() { + return { + "networkGlobalId": chain.id, + "subwalletNumber": subwalletNumber, + "workchain": chain.workchain + }; + } + + @override + int get contextID => beginCell() + .storeUint(1, 1) + .storeInt(chain.workchain, 8) + .storeUint(walletVersion, 8) + .storeUint(subwalletNumber, 15) + .endCell() + .beginParse() + .loadInt(32); + + @override + operator ==(other) { + if (other is! V5R1ClientContext) return false; + return other.subwalletNumber == subwalletNumber && other.chain == chain; + } + + @override + int get hashCode => Object.hashAll([subwalletNumber, chain.id]); +} diff --git a/lib/src/contracts/wallet_contracts/types/state/highload.dart b/lib/src/contracts/wallet_contracts/types/state/highload.dart new file mode 100644 index 0000000..32a56fd --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/state/highload.dart @@ -0,0 +1,38 @@ +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core/state.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/constant/constant.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class HighloadWalletV3State implements ContractState { + final List publicKey; + final int timeout; + final int subWalletId; + const HighloadWalletV3State( + {required this.publicKey, + required this.timeout, + required this.subWalletId}); + + factory HighloadWalletV3State.deserialize(Slice slice) { + return HighloadWalletV3State( + publicKey: slice.loadBuffer(32), + subWalletId: slice.loadUint(32), + timeout: slice + .skip(1 + 1 + HighloadWalletConst.highLoadTimeStampSize) + .loadUint(HighloadWalletConst.highLoadTimeOutSize)); + } + + @override + StateInit initialState() { + return StateInit(code: HighloadWalletConst.code(), data: initialData()); + } + + @override + Cell initialData() { + return beginCell() + .storeBuffer(publicKey) + .storeUint(subWalletId, 32) + .storeUint(0, 1 + 1 + HighloadWalletConst.highLoadTimeStampSize) + .storeUint(timeout, HighloadWalletConst.highLoadTimeOutSize) + .endCell(); + } +} diff --git a/lib/src/contracts/wallet_contracts/types/state/multisig.dart b/lib/src/contracts/wallet_contracts/types/state/multisig.dart new file mode 100644 index 0000000..9e92884 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/state/multisig.dart @@ -0,0 +1,90 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/models/models.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class MultiOwnerWalletState extends ContractState { + final int threshold; + final List signers; + final List proposers; + final bool allowArbitrarySeqno; + final BigInt nextOrderSeqno; + final int signersNum; + + factory MultiOwnerWalletState.deserialize(Slice slice) { + final next = slice.loadUint256(); + final threshHold = slice.loadUint8(); + final signers = Dictionary.loadDirect( + key: DictionaryKey.uintCodec(8), + value: DictionaryValue.addressCodec(), + slice: slice.loadRef().beginParse()) + .asMap + .values + .toList(); + final int signerNum = slice.loadUint8(); + final proposers = Dictionary.load( + DictionaryKey.uintCodec(8), DictionaryValue.addressCodec(), slice); + return MultiOwnerWalletState( + nextOrderSeqno: next, + threshold: threshHold, + signers: signers, + signersNum: signerNum, + proposers: proposers.values.toList(), + allowArbitrarySeqno: slice.loadBoolean(), + ); + } + MultiOwnerWalletState({ + required this.threshold, + required this.allowArbitrarySeqno, + BigInt? nextOrderSeqno, + int? signersNum, + List signers = const [], + List proposers = const [], + }) : signers = List.unmodifiable(signers), + proposers = List.unmodifiable(proposers), + nextOrderSeqno = nextOrderSeqno ?? BigInt.zero, + signersNum = signersNum ?? signers.length; + + @override + StateInit initialState({TonChain? chain}) { + Cell? code; + if (chain == TonChain.testnet) { + code = Cell.fromHex(MultiOwnerContractConst.multiSigCodeTestNet); + } + code ??= Cell.fromHex(MultiOwnerContractConst.multisigCode); + return StateInit(code: code, data: initialData()); + } + + @override + Cell initialData() { + final signersDict = Dictionary.fromEnteries( + key: DictionaryKey.uintCodec(8), + value: DictionaryValue.addressCodec(), + map: {for (int i = 0; i < signers.length; i++) i: signers[i]}); + final proposersDict = Dictionary.fromEnteries( + key: DictionaryKey.uintCodec(8), + value: DictionaryValue.addressCodec(), + map: {for (int i = 0; i < proposers.length; i++) i: proposers[i]}); + return beginCell() + .storeUint256(0) + .storeUint8(threshold) + .storeRef(beginCell().storeDictDirect(signersDict).endCell()) + .storeUint8(signers.length) + .storeDict(dict: proposersDict) + .storeBitBolean(allowArbitrarySeqno) + .endCell(); + } + + Map toJson() { + return { + "threshold": threshold, + "signers": signers.map((e) => e.toFriendlyAddress()).toList(), + "proposers": proposers.map((e) => e.toFriendlyAddress()).toList(), + "nextOrderSeqno": nextOrderSeqno.toString(), + "allowArbitrarySeqno": allowArbitrarySeqno, + "signersNum": signersNum + }; + } +} diff --git a/lib/src/contracts/wallet_contracts/types/state/order.dart b/lib/src/contracts/wallet_contracts/types/state/order.dart new file mode 100644 index 0000000..1b6f1df --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/state/order.dart @@ -0,0 +1,86 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/contracts.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; + +class OrderContractState extends ContractState { + final TonAddress multisig; + final BigInt orderSeqno; + final int? threshold; + final bool? executed; + final List signers; + final BigInt? approvals; + final int? approvalsNum; + final BigInt? expirationDate; + final Cell? order; + + factory OrderContractState.deserialize(Slice slice) { + final multisig = slice.loadAddress(); + final BigInt orderSeqno = slice.loadUint256(); + final threshold = slice.loadUint8(); + final executed = slice.loadBoolean(); + final signers = Dictionary.loadDirect( + key: DictionaryKey.uintCodec(8), + value: DictionaryValue.addressCodec(), + slice: slice.loadRef().beginParse()) + .asMap + .values + .toList(); + final BigInt approvalsMask = slice.loadUint256(); + final int approvalsNum = slice.loadUint8(); + final BigInt expirationDate = slice.loadUintBig(48); + final order = slice.loadRef(); + return OrderContractState( + multisig: multisig, + orderSeqno: orderSeqno, + threshold: threshold, + executed: executed, + signers: signers, + approvals: approvalsMask, + approvalsNum: approvalsNum, + expirationDate: expirationDate, + order: order); + } + + OrderContractState({ + required this.multisig, + required this.orderSeqno, + this.threshold, + this.executed, + List signers = const [], + this.approvals, + this.approvalsNum, + this.expirationDate, + this.order, + }) : signers = List.unmodifiable(signers); + + @override + StateInit initialState() { + return StateInit( + code: Cell.fromHex(MultiOwnerContractConst.orderHashCode), + data: initialData()); + } + + @override + Cell initialData() { + return beginCell() + .storeAddress(multisig) + .storeUint256(orderSeqno) + .endCell(); + } + + Map toJson() { + return { + "multisig": multisig.toFriendlyAddress(), + "orderSeqno": orderSeqno.toString(), + "threshold": threshold, + "executed": executed, + "signers": signers.map((e) => e.toFriendlyAddress()).toList(), + "approvals": approvals, + "approvalsNum": approvalsNum, + "expirationDate": expirationDate, + "order": order?.toBase64() + }; + } +} diff --git a/lib/src/contracts/wallet_contracts/types/state/versioned.dart b/lib/src/contracts/wallet_contracts/types/state/versioned.dart new file mode 100644 index 0000000..cc6457c --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/state/versioned.dart @@ -0,0 +1,97 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/versioned/versioned_wallet.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/models/v5_client_id.dart'; +import 'package:ton_dart/src/models/models.dart'; + +abstract class VersionedWalletState implements ContractState { + final List publicKey; + final int seqno; + final WalletVersion version; + + @override + StateInit initialState() { + return StateInit(code: version.getCode(), data: initialData()); + } + + const VersionedWalletState( + {required this.publicKey, required this.seqno, required this.version}); + + @override + Cell initialData(); +} + +class NoneSubWalletVersionedWalletState extends VersionedWalletState { + const NoneSubWalletVersionedWalletState( + {required List publicKey, + int? seqno, + required WalletVersion version}) + : super(publicKey: publicKey, seqno: seqno ?? 0, version: version); + + @override + Cell initialData() { + return beginCell().storeUint(0, 32).storeBuffer(publicKey).endCell(); + } +} + +class SubWalletVersionedWalletState extends VersionedWalletState { + final int subwallet; + const SubWalletVersionedWalletState({ + required List publicKey, + int? seqno, + required WalletVersion version, + required this.subwallet, + }) : super(publicKey: publicKey, seqno: seqno ?? 0, version: version); + + @override + Cell initialData() { + switch (version) { + case WalletVersion.v3R1: + case WalletVersion.v3R2: + return beginCell() + .storeUint(0, 32) + .storeUint(subwallet, 32) + .storeBuffer(publicKey) + .endCell(); + case WalletVersion.v4: + return beginCell() + .storeUint(0, 32) + .storeUint(subwallet, 32) + .storeBuffer(publicKey) + .storeBit(0) + .endCell(); + default: + throw const TonContractException( + "SubWalletVersionedWalletState only accept version 3 and 4"); + } + } +} + +class V5VersionedWalletState extends VersionedWalletState { + final V5R1Context context; + final bool setPubKeyEnabled; + final List extensionPubKeys; + + V5VersionedWalletState( + {required List publicKey, + int? seqno, + required this.context, + required WalletVersion version, + this.setPubKeyEnabled = true, + List extensionPubKeys = const []}) + : extensionPubKeys = List.unmodifiable(extensionPubKeys), + super(publicKey: publicKey, seqno: seqno ?? 0, version: version); + + @override + Cell initialData() { + return beginCell() + .storeUint(1, 1) + .storeUint(0, 32) + .store(context) + .storeBuffer(publicKey) + .storeBit(0) + .endCell(); + } +} diff --git a/lib/src/contracts/wallet_contracts/types/transfer_params/highload.dart b/lib/src/contracts/wallet_contracts/types/transfer_params/highload.dart new file mode 100644 index 0000000..e9189cf --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/transfer_params/highload.dart @@ -0,0 +1,16 @@ +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/crypto/crypto.dart'; +import 'package:ton_dart/src/models/models.dart'; + +class HighloadTransferParams implements WalletContractTransferParams { + final TonPrivateKey signer; + @override + final List messages; + final BigInt queryId; + final BigInt? value; + const HighloadTransferParams( + {required this.messages, + required this.queryId, + required this.signer, + this.value}); +} diff --git a/lib/src/contracts/wallet_contracts/types/transfer_params/multi_owner.dart b/lib/src/contracts/wallet_contracts/types/transfer_params/multi_owner.dart new file mode 100644 index 0000000..929aafc --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/transfer_params/multi_owner.dart @@ -0,0 +1,19 @@ +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/models/models/out_action.dart'; + +class MultiOwnerTransferParams + extends WalletContractTransferParams { + final E params; + final BigInt expirationDate; + final BigInt amount; + final BigInt? orderId; + final BigInt? queryId; + MultiOwnerTransferParams( + {required this.params, + required this.expirationDate, + required this.amount, + this.orderId, + this.queryId, + List messages = const []}) + : super(messages: messages); +} diff --git a/lib/src/contracts/wallet_contracts/types/transfer_params/versioned.dart b/lib/src/contracts/wallet_contracts/types/transfer_params/versioned.dart new file mode 100644 index 0000000..a6d38f5 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/transfer_params/versioned.dart @@ -0,0 +1,11 @@ +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/crypto/crypto.dart'; +import 'package:ton_dart/src/models/models/out_action.dart'; + +class VersionedTransferParams + extends WalletContractTransferParams { + final TonPrivateKey privateKey; + VersionedTransferParams( + {List messages = const [], required this.privateKey}) + : super(messages: messages); +} diff --git a/lib/src/contracts/wallet_contracts/types/transfer_params/versioned_v5.dart b/lib/src/contracts/wallet_contracts/types/transfer_params/versioned_v5.dart new file mode 100644 index 0000000..de35b90 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/transfer_params/versioned_v5.dart @@ -0,0 +1,42 @@ +import 'package:ton_dart/src/contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/utils/transaction_utils.dart'; +import 'package:ton_dart/src/crypto/crypto.dart'; +import 'package:ton_dart/src/models/models.dart'; + +class VersionedV5TransferParams + extends WalletContractTransferParams { + final TonPrivateKey? privateKey; + final WalletV5AuthType type; + final BigInt? queryId; + VersionedV5TransferParams._({ + this.type = WalletV5AuthType.external, + List messages = const [], + this.privateKey, + this.queryId, + }) : super(messages: messages); + factory VersionedV5TransferParams.external({ + required TonPrivateKey signer, + List messages = const [], + }) { + return VersionedV5TransferParams._( + privateKey: signer, + messages: messages, + type: WalletV5AuthType.external); + } + factory VersionedV5TransferParams.internal({ + required TonPrivateKey signer, + List messages = const [], + }) { + return VersionedV5TransferParams._( + privateKey: signer, + messages: messages, + type: WalletV5AuthType.internal); + } + factory VersionedV5TransferParams.extension({ + required BigInt queryId, + List messages = const [], + }) { + return VersionedV5TransferParams._( + queryId: queryId, messages: messages, type: WalletV5AuthType.extension); + } +} diff --git a/lib/src/contracts/wallet_contracts/types/types.dart b/lib/src/contracts/wallet_contracts/types/types.dart new file mode 100644 index 0000000..58f5859 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/types/types.dart @@ -0,0 +1,9 @@ +export 'models/v5_client_id.dart'; +export 'state/highload.dart'; +export 'state/multisig.dart'; +export 'state/order.dart'; +export 'state/versioned.dart'; +export 'transfer_params/highload.dart'; +export 'transfer_params/multi_owner.dart'; +export 'transfer_params/versioned.dart'; +export 'transfer_params/versioned_v5.dart'; diff --git a/lib/src/contracts/highload/utils/query_id.dart b/lib/src/contracts/wallet_contracts/utils/highload_query_id.dart similarity index 95% rename from lib/src/contracts/highload/utils/query_id.dart rename to lib/src/contracts/wallet_contracts/utils/highload_query_id.dart index f20003a..10af8b0 100644 --- a/lib/src/contracts/highload/utils/query_id.dart +++ b/lib/src/contracts/wallet_contracts/utils/highload_query_id.dart @@ -48,14 +48,14 @@ class HighloadQueryId { if (newShift == BigInt.from(_HighloadQueryIdConst.maxShift) && newBitNumber > BigInt.from(_HighloadQueryIdConst.maxBitNumber - 1)) { - throw TonContractException("Overload"); + throw const TonContractException("Overload"); } if (newBitNumber > BigInt.from(_HighloadQueryIdConst.maxBitNumber)) { newBitNumber = BigInt.zero; newShift += BigInt.one; if (newShift > BigInt.from(_HighloadQueryIdConst.maxShift)) { - throw TonContractException("Overload"); + throw const TonContractException("Overload"); } } diff --git a/lib/src/contracts/wallet_contracts/utils/multi_owner.dart b/lib/src/contracts/wallet_contracts/utils/multi_owner.dart new file mode 100644 index 0000000..6400a18 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/utils/multi_owner.dart @@ -0,0 +1,22 @@ +import 'package:ton_dart/src/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; + +class MultiOwnerContractUtils { + static Dictionary signersToDict(List obj) { + final dict = Dictionary.empty( + key: DictionaryKey.uintCodec(8), value: DictionaryValue.addressCodec()); + for (int i = 0; i < obj.length; i++) { + dict[i] = obj[i]; + } + return dict; + } + + static List signerCellToList(Cell? cell) { + if (cell == null) return []; + final dict = Dictionary.empty( + key: DictionaryKey.uintCodec(8), value: DictionaryValue.addressCodec()); + dict.loadFromClice(cell.beginParse()); + return dict.asMap.values.whereType().toList(); + } +} diff --git a/lib/src/contracts/wallet_contracts/utils/versioned.dart b/lib/src/contracts/wallet_contracts/utils/versioned.dart new file mode 100644 index 0000000..fb4b206 --- /dev/null +++ b/lib/src/contracts/wallet_contracts/utils/versioned.dart @@ -0,0 +1,131 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/contracts/core/core/chain.dart'; +import 'package:ton_dart/src/contracts/exception/exception.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/core/core.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/models/v5_client_id.dart'; +import 'package:ton_dart/src/dict/dictionary.dart'; +import 'package:ton_dart/src/models/models/state_init.dart'; +import 'package:ton_dart/src/contracts/wallet_contracts/types/state/versioned.dart'; + +class VersionedWalletUtils { + static VersionedWalletState readState( + {required Cell? stateData, + required WalletVersion type, + required TonChain chain}) { + if (stateData == null) { + throw TonContractExceptionConst.stateIsInactive; + } + try { + final cell = stateData.beginParse(); + int seqno; + List pubkeyBytes; + int? subWallet; + switch (type) { + case WalletVersion.v1R1: + case WalletVersion.v1R2: + case WalletVersion.v1R3: + case WalletVersion.v2R1: + case WalletVersion.v2R2: + seqno = cell.loadUint32(); + pubkeyBytes = cell.loadBuffer(32); + break; + case WalletVersion.v3R1: + case WalletVersion.v3R2: + case WalletVersion.v4: + seqno = cell.loadUint32(); + subWallet = cell.loadUint32(); + pubkeyBytes = cell.loadBuffer(32); + break; + case WalletVersion.v5R1: + final pubKeyEnabled = cell.loadBoolean(); + seqno = cell.loadUint32(); + final context = + loadV5Context(contextBytes: cell.loadBuffer(4), chain: chain); + pubkeyBytes = cell.loadBuffer(32); + List extentionPubkeys = []; + final pubRefs = cell.loadMaybeRef(); + if (pubRefs != null) { + final Dictionary, BigInt> items = Dictionary.loadDirect( + key: DictionaryKey.bufferCodec(32), + value: DictionaryValue.bigIntValueCodec(1), + slice: pubRefs.beginParse()); + extentionPubkeys = items.keys + .map((e) => TonAddress.fromBytes(chain.workchain, e)) + .toList(); + } + + return V5VersionedWalletState( + publicKey: pubkeyBytes, + seqno: seqno, + context: context, + version: type, + setPubKeyEnabled: pubKeyEnabled, + extensionPubKeys: extentionPubkeys); + default: + throw UnimplementedError(); + } + if (subWallet == null) { + return NoneSubWalletVersionedWalletState( + publicKey: pubkeyBytes, seqno: seqno, version: type); + } + return SubWalletVersionedWalletState( + subwallet: subWallet, + publicKey: pubkeyBytes, + seqno: seqno, + version: type); + } catch (e) { + throw TonContractException("Invalid ${type.name} state account data."); + } + } + + static V5R1Context loadV5Context( + {required List contextBytes, required TonChain chain}) { + final contextId = BitReader(BitString(contextBytes, 0, 32)).loadInt(32); + final context = BigInt.from(contextId) ^ BigInt.from(chain.id); + final slice = beginCell().storeInt(context, 32).endCell().beginParse(); + final isClientContext = slice.loadBoolean(); + if (isClientContext) { + final workchain = slice.loadInt(8); + final walletVersionRaw = slice.loadUint(8); + if (walletVersionRaw != 0) { + throw const TonContractException("Invalid wallet contract v5 version."); + } + final subwalletNumber = slice.loadUint(15); + if (chain.workchain != workchain) { + throw TonContractException("Incorrect workchain.", + details: {"excepted": workchain, "got": chain.workchain}); + } + return V5R1ClientContext(chain: chain, subwalletNumber: subwalletNumber); + } + return V5R1CustomContext(context: slice.loadUint(31), chain: chain); + } + + static T buildFromAddress({ + required Cell? stateData, + required WalletVersion type, + required TonAddress address, + required TonChain? chain, + }) { + final state = readState( + stateData: stateData, + type: type, + chain: chain ?? TonChain.fromWorkchain(address.workChain)); + StateInit currentState = state.initialState(); + final currentAddress = + TonAddress.fromState(state: currentState, workChain: address.workChain); + if (currentAddress.toRawAddress() != address.toRawAddress()) { + throw TonContractException( + "Invalid wallet address. state gives a different address", + details: { + "excepted": currentAddress.toRawAddress(), + "address": address.toRawAddress() + }); + } + if (state is! T) { + throw TonContractException("Incurrect state casting.", + details: {"excepted": state.toString(), "got": "$T"}); + } + return state; + } +} diff --git a/lib/src/contracts/wallet_contracts/wallet_contracts.dart b/lib/src/contracts/wallet_contracts/wallet_contracts.dart new file mode 100644 index 0000000..0b108bd --- /dev/null +++ b/lib/src/contracts/wallet_contracts/wallet_contracts.dart @@ -0,0 +1,9 @@ +export 'constant/constant.dart'; +export 'contracts/contracts.dart'; +export 'core/core.dart'; +export 'provider/highload.dart'; +export 'provider/versioned.dart'; +export 'types/types.dart'; +export 'utils/highload_query_id.dart'; +export 'utils/multi_owner.dart'; +export 'utils/versioned.dart'; diff --git a/lib/src/dict/codecs/codecs.dart b/lib/src/dict/codecs/codecs.dart index 644ede6..b934501 100644 --- a/lib/src/dict/codecs/codecs.dart +++ b/lib/src/dict/codecs/codecs.dart @@ -5,7 +5,15 @@ import 'package:ton_dart/src/dict/dictionary/key.dart'; import 'package:ton_dart/src/dict/dictionary/value.dart'; import 'package:ton_dart/src/dict/exception/exception.dart'; +/// Provides factory methods for creating `DictionaryKey` and `DictionaryValue` +/// instances for various data types, used in serialization and deserialization +/// within dictionaries. class DictionaryCodecs { + /// Creates a `DictionaryKey` for `TonBaseAddress` with a fixed bit length of 267. + /// + /// The `serialize` function stores the address in a cell and encodes it as a + /// big integer. The `parse` function decodes the big integer from a cell and + /// retrieves the address. static DictionaryKey createAddressKey() { return DictionaryKey( bits: 267, @@ -26,6 +34,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for `BigInt` with a specified bit length. + /// + /// The `serialize` function stores the big integer in a cell and encodes it as + /// a big integer. The `parse` function decodes the big integer from a cell and + /// retrieves the big integer. static DictionaryKey createBigIntKey(int bits) { return DictionaryKey( bits: bits, @@ -46,6 +59,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for `int` with a specified bit length. + /// + /// The `serialize` function stores the integer in a cell and encodes it as + /// a big integer. The `parse` function decodes the big integer from a cell and + /// retrieves the integer. static DictionaryKey createIntKey(int bits) { return DictionaryKey( bits: bits, @@ -69,6 +87,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for `BigInt` with a specified bit length for unsigned integers. + /// + /// The `serialize` function checks if the big integer is non-negative, stores it in a + /// cell, and encodes it as a big integer. The `parse` function decodes the big integer + /// from a cell and retrieves it. static DictionaryKey createBigUintKey(int bits) { return DictionaryKey( bits: bits, @@ -92,6 +115,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for `int` with a specified bit length for unsigned integers. + /// + /// The `serialize` function checks if the integer is finite and non-negative, + /// stores it in a cell, and encodes it as a big integer. The `parse` function decodes + /// the big integer from a cell and retrieves it as an `int`. static DictionaryKey createUintKey(int bits) { return DictionaryKey( bits: bits, @@ -119,6 +147,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for a `List` with a specified byte length. + /// + /// The `serialize` function stores the buffer in a cell and encodes it as a + /// big integer. The `parse` function decodes the big integer from a cell and + /// retrieves the buffer. static DictionaryKey> createBufferKey(int bytes) { return DictionaryKey>( bits: bytes * 8, @@ -139,6 +172,11 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryKey` for `BitString` with a specified bit length. + /// + /// The `serialize` function stores the bit string in a cell and encodes it as + /// a big integer. The `parse` function decodes the big integer from a cell and + /// retrieves the bit string. static DictionaryKey createBitStringKey(int bits) { return DictionaryKey( bits: bits, @@ -157,6 +195,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `int` with a specified bit length. + /// + /// The `serialize` function stores the integer in a builder with the specified bit length. + /// The `parse` function retrieves the integer from the source with the specified bit length. static DictionaryValue createIntValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -168,6 +210,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `BigInt` with a specified bit length. + /// + /// The `serialize` function stores the big integer in a builder with the specified bit length. + /// The `parse` function retrieves the big integer from the source with the specified bit length. static DictionaryValue createBigIntValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -179,6 +225,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `BigInt` with a specified bit length using variable-length encoding. + /// + /// The `serialize` function stores the big integer in a builder with variable-length encoding. + /// The `parse` function retrieves the big integer from the source using variable-length encoding. static DictionaryValue createBigVarIntValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -190,6 +240,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `BigInt` with a specified bit length using variable-length encoding. + /// + /// The `serialize` function stores the big integer in a builder with variable-length encoding. + /// The `parse` function retrieves the big integer from the source using variable-length encoding. static DictionaryValue createBigVarUintValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -201,6 +255,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `int` with a specified bit length for unsigned integers. + /// + /// The `serialize` function stores the integer in a builder with the specified bit length. + /// The `parse` function retrieves the integer from the source with the specified bit length. static DictionaryValue createUintValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -212,6 +270,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `BigInt` with a specified bit length for unsigned integers. + /// + /// The `serialize` function stores the big integer in a builder with the specified bit length. + /// The `parse` function retrieves the big integer from the source with the specified bit length. static DictionaryValue createBigUintValue(int bits) { return DictionaryValue( serialize: (src, builder) { @@ -223,6 +285,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `bool`. + /// + /// The `serialize` function stores the boolean value as a single bit (0 or 1). + /// The `parse` function retrieves the boolean value from the source. static DictionaryValue createBooleanValue() { return DictionaryValue( serialize: (src, builder) { @@ -234,7 +300,26 @@ class DictionaryCodecs { ); } - static DictionaryValue createAddressValue() { + /// Creates a `DictionaryValue` for `TonAddress`. + /// + /// The `serialize` function stores the `TonAddress` in a builder. + /// The `parse` function retrieves the `TonAddress` from the source. + static DictionaryValue createAddressValue() { + return DictionaryValue( + serialize: (src, builder) { + builder.storeAddress(src); + }, + parse: (src) { + return src.loadAddress(); + }, + ); + } + + /// Creates a `DictionaryValue` for `TonBaseAddress`. + /// + /// The `serialize` function stores the `TonBaseAddress` in a builder. + /// The `parse` function retrieves the `TonBaseAddress` from the source. + static DictionaryValue createBaseAddressValue() { return DictionaryValue( serialize: (src, builder) { builder.storeAddress(src); @@ -245,6 +330,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `Cell`. + /// + /// The `serialize` function stores the `Cell` as a reference in the builder. + /// The `parse` function retrieves the `Cell` from the source. static DictionaryValue createCellValue() { return DictionaryValue( serialize: (src, builder) { @@ -256,6 +345,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for a `Dictionary` with specified key and value codecs. + /// + /// The `serialize` function stores the dictionary in the builder using the provided key and value codecs. + /// The `parse` function retrieves the dictionary from the source using the provided key and value codecs. static DictionaryValue> createDictionaryValue( DictionaryKey key, DictionaryValue value) { @@ -269,6 +362,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for a `List` with a specified size. + /// + /// The `serialize` function stores the buffer in the builder and validates its size. + /// The `parse` function retrieves the buffer from the source with the specified size. static DictionaryValue> createBufferValue(int size) { return DictionaryValue>( serialize: (src, builder) { @@ -284,6 +381,10 @@ class DictionaryCodecs { ); } + /// Creates a `DictionaryValue` for `BitString` with a specified bit length. + /// + /// The `serialize` function stores the bit string in the builder and validates its length. + /// The `parse` function retrieves the bit string from the source with the specified bit length. static DictionaryValue createBitStringValue(int bits) { return DictionaryValue( serialize: (src, builder) { diff --git a/lib/src/dict/dictionary/dictionary.dart b/lib/src/dict/dictionary/dictionary.dart index 30c5686..f4f5a20 100644 --- a/lib/src/dict/dictionary/dictionary.dart +++ b/lib/src/dict/dictionary/dictionary.dart @@ -5,14 +5,20 @@ import 'package:ton_dart/src/dict/utils/utils.dart'; import 'key.dart'; import 'value.dart'; +/// Represents a dictionary where keys and values can be serialized and deserialized +/// using the provided key and value codecs. class Dictionary { final DictionaryKey? _key; final DictionaryValue? _value; final Map _map; + + /// Gets the number of entries in the dictionary. int get size => _map.length; + /// Constructs a `Dictionary` with the specified map, key codec, and value codec. Dictionary(this._map, this._key, this._value); + /// Creates an empty dictionary with optional key and value codecs. static Dictionary empty( {DictionaryKey? key, DictionaryValue? value}) { if (key != null && value != null) { @@ -22,6 +28,7 @@ class Dictionary { } } + /// Creates a dictionary from the given key, value codecs, and map of entries. static Dictionary fromEnteries( {required DictionaryKey key, required DictionaryValue value, @@ -33,18 +40,24 @@ class Dictionary { return dict; } + /// Loads a dictionary from the given `Slice`. Returns an empty dictionary if no + /// reference is found or if the cell is exotic. static Dictionary load( DictionaryKey key, DictionaryValue value, Slice slice) { final cell = slice.loadMaybeRef(); if (cell != null && !cell.isExotic) { - return loadDirect(key, value, cell.beginParse()); + return loadDirect(key: key, value: value, slice: cell.beginParse()); } else { return empty(key: key, value: value); } } + /// Loads a dictionary directly from the given `Slice`. If the `Slice` is null, + /// returns an empty dictionary. static Dictionary loadDirect( - DictionaryKey key, DictionaryValue value, Slice? slice) { + {required DictionaryKey key, + required DictionaryValue value, + required Slice? slice}) { if (slice == null) { return empty(key: key, value: value); } @@ -58,22 +71,29 @@ class Dictionary { return Dictionary(prepared, key, value); } + /// Gets an iterable of the dictionary's values. Iterable get values => _map.values; + /// Retrieves the value for the given key, or `null` if the key is not present. V? operator [](K key) => _map[DictionaryUtils.serializeInternalKey(key)]; + /// Checks if the dictionary contains the given key. bool containsKey(K key) => _map.containsKey(DictionaryUtils.serializeInternalKey(key)); + /// Adds or updates the value for the given key. void operator []=(K key, V value) { _map[DictionaryUtils.serializeInternalKey(key)] = value; } + /// Removes the entry for the given key. Returns `true` if the key was removed. bool remove(K key) => _map.remove(DictionaryUtils.serializeInternalKey(key)) != null; + /// Clears all entries in the dictionary. void clear() => _map.clear(); + /// Gets an iterable of the dictionary's entries as `MapEntry`. Iterable> get entries sync* { for (final entry in _map.entries) { yield MapEntry( @@ -81,12 +101,15 @@ class Dictionary { } } + /// Gets an iterable of the dictionary's keys. Iterable get keys sync* { for (final key in _map.keys) { yield DictionaryUtils.deserializeInternalKey(key); } } + /// Serializes the dictionary and stores it in the given `Builder`. + /// Throws `DictException` if the key or value serializers are not defined. void store(Builder builder, {DictionaryKey? key, DictionaryValue? value}) { if (_map.isEmpty) { @@ -115,6 +138,8 @@ class Dictionary { } } + /// Serializes the dictionary directly into the `Builder`. + /// Throws `DictException` if the dictionary is empty or if key/value serializers are not defined. void storeDirect(Builder builder, {DictionaryKey? key, DictionaryValue? value}) { if (_map.isEmpty) { @@ -134,17 +159,20 @@ class Dictionary { resolvedKey .serialize(DictionaryUtils.deserializeInternalKey(entry.key)), entry.value))); - DictSerialization.serialize( prepared, resolvedKey.bits, resolvedValue.serialize, builder); } + /// Generates a Merkle proof for the given key. Cell generateMerkleProof(K key) => DictionaryUtils.generateMerkleProof(this, key, _key!); + /// Generates a Merkle update for the given key and new value. Cell generateMerkleUpdate(K key, V newValue) => DictionaryUtils.generateMerkleUpdate(this, key, _key!, newValue); + /// Loads dictionary entries from a `Slice` and updates the dictionary. + /// Throws `DictException` if key or value serializers are not defined. void loadFromClice(Slice slice, {DictionaryKey? key, DictionaryValue? value}) { final resolvedKey = key ?? _key; @@ -161,7 +189,6 @@ class Dictionary { } } + /// Converts the dictionary to a `Map`. Map get asMap => Map.fromEntries(entries); } - -/// 000000000000000 diff --git a/lib/src/dict/dictionary/key.dart b/lib/src/dict/dictionary/key.dart index 1aadba6..14cd49a 100644 --- a/lib/src/dict/dictionary/key.dart +++ b/lib/src/dict/dictionary/key.dart @@ -2,25 +2,48 @@ import 'package:ton_dart/src/address/address.dart'; import 'package:ton_dart/src/boc/bit/bit_string.dart'; import 'package:ton_dart/src/dict/codecs/codecs.dart'; +/// Represents a key used in a dictionary, defining how to serialize and parse +/// the key value, as well as the bit length used for encoding. class DictionaryKey { + /// The number of bits used to encode the key. final int bits; + + /// Function to serialize the key value into a `BigInt`. final BigInt Function(K) serialize; + + /// Function to parse a `BigInt` into the key value. final K Function(BigInt) parse; + + /// Constructs a `DictionaryKey` with the specified bit length, serialization, + /// and parsing functions. const DictionaryKey( {required this.bits, required this.serialize, required this.parse}); + /// Returns a `DictionaryKey` for `TonBaseAddress` with a bit length of 267. static DictionaryKey addressCodec() => DictionaryCodecs.createAddressKey(); + + /// Returns a `DictionaryKey` for `BigInt` with the specified bit length. static DictionaryKey bigIntCodec(int bits) => DictionaryCodecs.createBigIntKey(bits); + + /// Returns a `DictionaryKey` for `int` with the specified bit length. static DictionaryKey intCodec(int bits) => DictionaryCodecs.createIntKey(bits); + + /// Returns a `DictionaryKey` for `BigInt` with the specified bit length for unsigned integers. static DictionaryKey bigUintCodec(int bits) => DictionaryCodecs.createBigUintKey(bits); + + /// Returns a `DictionaryKey` for `int` with the specified bit length for unsigned integers. static DictionaryKey uintCodec(int bits) => DictionaryCodecs.createUintKey(bits); + + /// Returns a `DictionaryKey` for a `List` with the specified byte length. static DictionaryKey> bufferCodec(int bytes) => DictionaryCodecs.createBufferKey(bytes); + + /// Returns a `DictionaryKey` for `BitString` with the specified bit length. static DictionaryKey bitStringCodec(int bits) => DictionaryCodecs.createBitStringKey(bits); } diff --git a/lib/src/dict/dictionary/value.dart b/lib/src/dict/dictionary/value.dart index 22bad34..4b9b6c8 100644 --- a/lib/src/dict/dictionary/value.dart +++ b/lib/src/dict/dictionary/value.dart @@ -5,33 +5,67 @@ import 'package:ton_dart/src/dict/codecs/codecs.dart'; import 'dictionary.dart'; import 'key.dart'; +/// Represents a value in a dictionary, defining how to serialize and parse +/// the value. class DictionaryValue { + /// Function to serialize the value into a `Builder`. final void Function(V source, Builder builder) serialize; + + /// Function to parse a value from a `Slice`. final V Function(Slice slice) parse; + + /// Constructs a `DictionaryValue` with the specified serialization and parsing functions. const DictionaryValue({required this.serialize, required this.parse}); + /// Returns a `DictionaryValue` for `BigInt` with the specified bit length. static DictionaryValue bigIntValueCodec(int bits) => DictionaryCodecs.createBigIntValue(bits); + + /// Returns a `DictionaryValue` for `int` with the specified bit length. static DictionaryValue intValueCodec(int bits) => DictionaryCodecs.createIntValue(bits); + + /// Returns a `DictionaryValue` for `BigInt` with the specified bit length for variable-length integers. static DictionaryValue bigVarIntCodec(int bits) => DictionaryCodecs.createBigVarIntValue(bits); + + /// Returns a `DictionaryValue` for `BigInt` with the specified bit length for unsigned integers. static DictionaryValue bigUintCodec(int bits) => DictionaryCodecs.createBigUintValue(bits); + + /// Returns a `DictionaryValue` for `int` with the specified bit length for unsigned integers. static DictionaryValue uintCodec(int bits) => DictionaryCodecs.createUintValue(bits); + + /// Returns a `DictionaryValue` for `BigInt` with the specified bit length for variable-length unsigned integers. static DictionaryValue bigVarUintCodec(int bits) => DictionaryCodecs.createBigVarUintValue(bits); + + /// Returns a `DictionaryValue` for `bool`. static DictionaryValue boolCodec() => DictionaryCodecs.createBooleanValue(); - static DictionaryValue addressCodec() => + + /// Returns a `DictionaryValue` for `TonAddress`. + static DictionaryValue addressCodec() => DictionaryCodecs.createAddressValue(); + + /// Returns a `DictionaryValue` for `TonBaseAddress`. + static DictionaryValue addressBaseCodec() => + DictionaryCodecs.createBaseAddressValue(); + + /// Returns a `DictionaryValue` for `Cell`. static DictionaryValue cellCodec() => DictionaryCodecs.createCellValue(); + + /// Returns a `DictionaryValue` for a `List` with the specified byte length. static DictionaryValue> bytesCodec(int bytes) => DictionaryCodecs.createBufferValue(bytes); + + /// Returns a `DictionaryValue` for `BitString` with the specified bit length. static DictionaryValue bitStringCodec(int bits) => DictionaryCodecs.createBitStringValue(bits); + + /// Returns a `DictionaryValue` for a dictionary with the specified key and value codecs. static DictionaryValue> dictionaryCodec( DictionaryKey key, DictionaryValue value) { return DictionaryCodecs.createDictionaryValue(key, value); diff --git a/lib/src/dict/serialization/serialization.dart b/lib/src/dict/serialization/serialization.dart index 60787f4..170ce59 100644 --- a/lib/src/dict/serialization/serialization.dart +++ b/lib/src/dict/serialization/serialization.dart @@ -2,7 +2,7 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/dict/exception/exception.dart'; import 'package:ton_dart/src/dict/utils/utils.dart'; -import 'package:ton_dart/src/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; class _Node { late _Edge left; @@ -12,6 +12,17 @@ class _Node { _Node.leaf(this.value) : isLeaf = true; bool isLeaf; late final T value; + + Map toJson() { + if (isLeaf) { + return {"value": value, "type": "leaf"}; + } + return { + "left": left.toJson(), + "right": right.toJson(), + "type": "fork", + }; + } } class _Edge { @@ -19,6 +30,9 @@ class _Edge { final _Node node; _Edge(this.label, this.node); + Map toJson() { + return {"label": label, "node": node.toJson()}; + } } class _DictSerializationUtils { @@ -160,7 +174,6 @@ class _DictSerializationUtils { static int detectLabelType(String src, int keyLength) { int kind = 0; int kindLength = labelShortLength(src); - final longLength = labelLongLength(src, keyLength); if (longLength < kindLength) { kindLength = longLength; diff --git a/lib/src/dict/utils/utils.dart b/lib/src/dict/utils/utils.dart index dd3b42e..05710c5 100644 --- a/lib/src/dict/utils/utils.dart +++ b/lib/src/dict/utils/utils.dart @@ -4,7 +4,7 @@ import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/dict/dictionary/dictionary.dart'; import 'package:ton_dart/src/dict/dictionary/key.dart'; import 'package:ton_dart/src/dict/exception/exception.dart'; -import 'package:ton_dart/src/utils/math.dart'; +import 'package:ton_dart/src/utils/utils/math.dart'; class DictionaryUtils { static String findCommonPrefix(List src, [int startPos = 0]) { @@ -263,14 +263,11 @@ class DictionaryUtils { } } } - // pp: 00000000000000000000000000000000 32 Map(0) {} [Function: parse] 32 - // console.log("pp: ",pp,n,res,extractor,prefixLength) if (n - prefixLength == 0) { res[BigInt.parse(pp, radix: 2)] = extractor(slice); } else { Cell left = slice.loadRef(); Cell right = slice.loadRef(); - // NOTE: Left and right branches are implicitly contain prefixes '0' and '1' if (!left.isExotic) { doParse( '${pp}0', left.beginParse(), n - prefixLength - 1, res, extractor); diff --git a/lib/src/helper/ton_helper.dart b/lib/src/helper/ton_helper.dart index 6b3aea0..599acc2 100644 --- a/lib/src/helper/ton_helper.dart +++ b/lib/src/helper/ton_helper.dart @@ -2,28 +2,48 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/exception/exception.dart'; +/// A utility class for handling TON (The Open Network) specific operations. +/// +/// This class provides methods for converting between nanoTONs and decimal strings, +/// parsing cell data from strings, and converting strings to cells. class TonHelper { + /// The number of decimal places for nanoTON values. static const int nanoDecimalPlaces = 9; + + /// A constant representing the scaling factor for converting between nanoTONs and decimal strings. static final BigRational _nanoDecimal = BigRational(BigInt.from(10).pow(nanoDecimalPlaces)); - /// Example toNano("1.0") = 1000000000 + /// Converts a decimal string representation of a TON value to a BigInt in nanoTONs. + /// + /// [ton] - The decimal string representing the TON value. + /// + /// Returns the equivalent value in nanoTONs as a [BigInt]. static BigInt toNano(String ton) { final parse = BigRational.parseDecimal(ton); return (parse * _nanoDecimal).toBigInt(); } - /// Example fromNano(BigInt.from(1000000000)) = "1" + /// Converts a value in nanoTONs to a decimal string representation. + /// + /// [nanotons] - The value in nanoTONs as a [BigInt]. + /// + /// Returns the equivalent decimal string representation. static String fromNano(BigInt nanotons) { final parse = BigRational(nanotons); return (parse / _nanoDecimal).toDecimal(digits: nanoDecimalPlaces); } + /// Attempts to parse a string into a [Cell] object. + /// + /// [data] - The string data that might represent a cell in hex or base64 format. + /// + /// Returns the parsed [Cell] if successful, or `null` if the data is invalid or parsing fails. static Cell? tryToCell(String? data) { try { if (data?.trim().isEmpty ?? true) return null; if (StringUtils.isHexBytes(data!)) { - return Cell.fromBytes(BytesUtils.fromHexString(data)); + return Cell.fromHex(data); } return Cell.fromBase64(data); } catch (e) { @@ -31,6 +51,12 @@ class TonHelper { } } + /// Converts a string into a [Cell] object, throwing an exception if the conversion fails. + /// + /// [data] - The string data that represents a cell in hex or base64 format. + /// + /// Returns the parsed [Cell]. + /// Throws a [TonDartPluginException] if the data is invalid or cannot be parsed. static Cell toCell(String? data) { final toCell = tryToCell(data); if (toCell == null) { diff --git a/lib/src/models/models/common_message_info.dart b/lib/src/models/models/common_message_info.dart index 1d3d554..437e16c 100644 --- a/lib/src/models/models/common_message_info.dart +++ b/lib/src/models/models/common_message_info.dart @@ -3,7 +3,7 @@ import 'package:ton_dart/src/address/address.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/exception/exception.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'currency_collection.dart'; class CommonMessageInfoType { @@ -174,7 +174,7 @@ class CommonMessageInfoExternalIn extends CommonMessageInfo { return CommonMessageInfoExternalIn( dest: TonAddress(json["dest"]), importFee: BigintUtils.parse(json["importFee"]), - src: (json["src"] as Object?)?.to((p0) { + src: (json["src"] as Object?)?.convertTo((p0) { return ExternalAddress.fromJson(p0.cast()); })); } @@ -226,7 +226,7 @@ class CommonMessageInfoExternalOut extends CommonMessageInfo { src: TonAddress(json["src"]), createdLt: BigintUtils.parse(json["createdLt"]), createdAt: json["createdAt"], - dest: (json["dest"] as Object?)?.to( + dest: (json["dest"] as Object?)?.convertTo( (p0) => ExternalAddress.fromJson(p0.cast()))); } diff --git a/lib/src/models/models/common_message_info_relaxed.dart b/lib/src/models/models/common_message_info_relaxed.dart index f8375f0..39d8835 100644 --- a/lib/src/models/models/common_message_info_relaxed.dart +++ b/lib/src/models/models/common_message_info_relaxed.dart @@ -1,10 +1,9 @@ -import 'package:blockchain_utils/exception/exceptions.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/address/address.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/exception/exception.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'currency_collection.dart'; /// Source: https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L132 @@ -43,12 +42,14 @@ class CommonMessageInfoRelaxedType { abstract class CommonMessageInfoRelaxed extends TonSerialization { const CommonMessageInfoRelaxed._(); abstract final CommonMessageInfoRelaxedType type; + TonBaseAddress? get dest; factory CommonMessageInfoRelaxed.deserialize(Slice slice) { if (!slice.loadBit()) { return CommonMessageInfoRelaxedInternal.deserialize(slice); } if (!slice.loadBit()) { - throw const MessageException("Invalid CommonMessageInfoRelaxed Slice"); + throw const TonDartPluginException( + "Invalid CommonMessageInfoRelaxed Slice"); } return CommonMessageInfoRelaxedExternalOut.deserialize(slice); } @@ -68,6 +69,7 @@ class CommonMessageInfoRelaxedInternal extends CommonMessageInfoRelaxed { final bool bounce; final bool bounced; final TonAddress? src; + @override final TonAddress dest; final CurrencyCollection value; final BigInt ihrFee; @@ -116,7 +118,7 @@ class CommonMessageInfoRelaxedInternal extends CommonMessageInfoRelaxed { bounce: json["bounce"], bounced: json["bounced"], src: (json["src"] as Object?) - ?.to((result) => TonAddress(result)), + ?.convertTo((result) => TonAddress(result)), dest: TonAddress(json["dest"]), value: CurrencyCollection.fromJson(json["value"]), ihrFee: BigintUtils.parse(json["ihrFee"]), @@ -163,6 +165,7 @@ class CommonMessageInfoRelaxedInternal extends CommonMessageInfoRelaxed { class CommonMessageInfoRelaxedExternalOut extends CommonMessageInfoRelaxed { final TonAddress? src; + @override final ExternalAddress? dest; final BigInt createdLt; final int createdAt; @@ -182,10 +185,10 @@ class CommonMessageInfoRelaxedExternalOut extends CommonMessageInfoRelaxed { Map json) { return CommonMessageInfoRelaxedExternalOut( src: (json["src"] as Object?) - ?.to((result) => TonAddress(result)), + ?.convertTo((result) => TonAddress(result)), createdLt: BigintUtils.parse(json["createdLt"]), createdAt: json["createdAt"], - dest: (json["dest"] as Object?)?.to( + dest: (json["dest"] as Object?)?.convertTo( (p0) => ExternalAddress.fromJson(p0.cast()))); } diff --git a/lib/src/models/models/currency_collection.dart b/lib/src/models/models/currency_collection.dart index dc2b3e5..6119b59 100644 --- a/lib/src/models/models/currency_collection.dart +++ b/lib/src/models/models/currency_collection.dart @@ -2,7 +2,7 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/dict/dictionary.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class CurrencyCollection extends TonSerialization { final Map? other; @@ -17,7 +17,7 @@ class CurrencyCollection extends TonSerialization { } factory CurrencyCollection.fromJson(Map json) { return CurrencyCollection( - other: (json["other"] as Object?)?.to, Map>((p0) { + other: (json["other"] as Object?)?.convertTo, Map>((p0) { final Map result = p0.cast(); return result.map( (key, value) => MapEntry(key, BigintUtils.parse(value))); diff --git a/lib/src/models/models/master_chain_state_extra.dart b/lib/src/models/models/master_chain_state_extra.dart index 405a92f..0c6d4db 100644 --- a/lib/src/models/models/master_chain_state_extra.dart +++ b/lib/src/models/models/master_chain_state_extra.dart @@ -4,7 +4,7 @@ import 'package:ton_dart/src/dict/dictionary.dart'; import 'package:ton_dart/src/exception/exception.dart'; import 'package:ton_dart/src/models/models/currency_collection.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class _MasterchainStateExtraUtils { static Dictionary dict({Map? config}) { diff --git a/lib/src/models/models/message.dart b/lib/src/models/models/message.dart index 76a096e..07c45e6 100644 --- a/lib/src/models/models/message.dart +++ b/lib/src/models/models/message.dart @@ -6,7 +6,7 @@ import 'package:ton_dart/src/dict/dictionary.dart'; import 'package:ton_dart/src/models/models/common_message_info.dart'; import 'package:ton_dart/src/models/models/state_init.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class MessageCodec { static final DictionaryValue codec = DictionaryValue( @@ -42,7 +42,7 @@ class Message extends TonSerialization { return Message( info: CommonMessageInfo.fromJson(json["info"]), body: json["body"], - init: (json["init"] as Object?)?.to( + init: (json["init"] as Object?)?.convertTo( (result) => StateInit.fromJson(result.cast()))); } diff --git a/lib/src/models/models/message_relaxed.dart b/lib/src/models/models/message_relaxed.dart index 679880c..517f558 100644 --- a/lib/src/models/models/message_relaxed.dart +++ b/lib/src/models/models/message_relaxed.dart @@ -4,7 +4,7 @@ import 'package:ton_dart/src/boc/cell/slice.dart'; import 'package:ton_dart/src/models/models/common_message_info_relaxed.dart'; import 'package:ton_dart/src/models/models/state_init.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; /// Source: https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L151 /// message$_ {X:Type} info:CommonMsgInfoRelaxed @@ -32,7 +32,7 @@ class MessageRelaxed extends TonSerialization { return MessageRelaxed( info: CommonMessageInfoRelaxed.fromJson(json["info"]), body: Cell.fromBase64(json["body"]), - init: (json["init"] as Object?)?.to( + init: (json["init"] as Object?)?.convertTo( (result) => StateInit.fromJson(result.cast()))); } diff --git a/lib/src/models/models/out_action.dart b/lib/src/models/models/out_action.dart index 25af3db..ccf7c30 100644 --- a/lib/src/models/models/out_action.dart +++ b/lib/src/models/models/out_action.dart @@ -1,27 +1,53 @@ +import 'package:ton_dart/src/address/address/address.dart'; +import 'package:ton_dart/src/address/core/ton_address.dart'; import 'package:ton_dart/src/boc/boc.dart'; +import 'package:ton_dart/src/dict/dictionary/dictionary.dart'; +import 'package:ton_dart/src/dict/dictionary/key.dart'; +import 'package:ton_dart/src/dict/dictionary/value.dart'; import 'package:ton_dart/src/exception/exception.dart'; +import 'package:ton_dart/src/models/models/send_mode.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; import 'message_relaxed.dart'; -import 'send_mode.dart'; - -class _OutActionTypeConst { - static const outActionSendMsgTag = 0x0ec3c86d; - static const outActionSetCodeTag = 0xad4de08e; -} class OutActionType { final String name; - const OutActionType._(this.name); - static const OutActionType sendMsg = OutActionType._("sendMsg"); - static const OutActionType setCode = OutActionType._("setCode"); - static const List values = [sendMsg, setCode]; + final int tag; + const OutActionType._({required this.name, required this.tag}); + static const OutActionType sendMsg = + OutActionType._(name: "sendMsg", tag: 0x0ec3c86d); + static const OutActionType multiSigSendMessage = + OutActionType._(name: "multiSigSendMsg", tag: 0xf1381e5b); + static const OutActionType updateMiltiSig = + OutActionType._(name: "updateMultiSig", tag: 0x1d0cfbd3); + + static const OutActionType setCode = + OutActionType._(name: "setCode", tag: 0xad4de08e); + static const OutActionType addExtension = + OutActionType._(name: "AddExtension", tag: 0x02); + static const OutActionType removeExtension = + OutActionType._(tag: 0x03, name: "RemoveExtension"); + static const OutActionType setIsPublicKeyEnabled = + OutActionType._(tag: 0x04, name: "SetIsPublicKeyEnabled"); + static const List values = [ + sendMsg, + setCode, + addExtension, + removeExtension, + setIsPublicKeyEnabled, + multiSigSendMessage, + updateMiltiSig + ]; factory OutActionType.fromValue(String? name) { - return values.firstWhere( - (element) => element.name == name, - orElse: () => throw TonDartPluginException( - "Cannot find OutActionType from provided name", - details: {"name": name}), - ); + return values.firstWhere((element) => element.name == name, + orElse: () => throw TonDartPluginException( + "Cannot find OutActionType from provided name", + details: {"name": name})); + } + factory OutActionType.fromTag(int? tag) { + return values.firstWhere((element) => element.tag == tag, + orElse: () => throw TonDartPluginException( + "Cannot find OutActionType from provided tag", + details: {"tag": tag})); } @override String toString() { @@ -29,7 +55,98 @@ class OutActionType { } } +class OutActionsV5 extends TonSerialization { + final List actions; + OutActionsV5({required List actions}) + : actions = List.unmodifiable(actions); + factory OutActionsV5.deserialize(Slice slice) { + final List actions = []; + final outListPacked = slice.loadMaybeRef(); + if (outListPacked != null) { + final sendMsgActions = + OutActionUtils.loadOutList(outListPacked.beginParse()); + if (sendMsgActions.any((e) => e.type != OutActionType.sendMsg)) { + throw const TonDartPluginException( + "Can't deserialize actions list: only sendMsg actions are allowed for wallet v5r1"); + } + actions.addAll(sendMsgActions.cast()); + } + if (slice.loadBoolean()) { + actions.add(OutActionExtended.deserialize(slice)); + } + while (slice.remainingRefs > 0) { + slice = slice.loadRef().beginParse(); + actions.add(OutActionExtended.deserialize(slice)); + } + return OutActionsV5(actions: actions); + } + + Cell? get _msgOutList { + final basicActions = + actions.whereType().toList().reversed.toList(); + if (basicActions.isEmpty) return null; + + Cell cell = basicActions.fold( + beginCell().endCell(), + (cell, action) { + return beginCell().storeRef(cell).store(action).endCell(); + }, + ); + + return cell; + } + + @override + void store(Builder builder) { + final extendedActions = actions.whereType().toList(); + builder.storeMaybeRef(cell: _msgOutList); + if (extendedActions.isEmpty) { + builder.storeUint(0, 1); + } else { + final first = extendedActions.first; + final rest = extendedActions.sublist(1); + builder.storeUint(1, 1).store(first); + if (rest.isNotEmpty) { + builder.storeRef(OutActionUtils.packExtendedActionsRec(rest)); + } + } + } + + @override + Map toJson() { + return {"actions": actions.map((e) => e.toJson()).toList()}; + } +} + class OutActionUtils { + static List loadOutList(Slice slice) { + List actions = []; + while (slice.remainingRefs != 0) { + final nextCell = slice.loadRef(); + actions.add(OutAction.deserialize(slice)); + slice = nextCell.beginParse(); + } + return actions.reversed.toList(); + } + + static Dictionary signersToDict( + List obj) { + final dict = Dictionary.empty( + key: DictionaryKey.uintCodec(8), value: DictionaryValue.addressCodec()); + for (int i = 0; i < obj.length; i++) { + dict[i] = obj[i]; + } + return dict; + } + + static List signerCellToList(Cell? cell) { + if (cell == null) return []; + final dict = Dictionary.empty( + key: DictionaryKey.uintCodec(8), value: DictionaryValue.addressCodec()); + dict.loadFromClice(cell.beginParse()); + return dict.asMap.values.whereType().toList(); + } + static Slice storeOutList(List actions) { Cell cell = actions.fold( beginCell().endCell(), @@ -40,24 +157,53 @@ class OutActionUtils { return cell.beginParse(); } + + static Cell packExtendedActionsRec(List extendedActions) { + final first = extendedActions.first; + final rest = extendedActions.sublist(1); + + Builder builder = beginCell().store(first); + if (rest.isNotEmpty) { + builder = builder.storeRef(packExtendedActionsRec(rest)); + } + return builder.endCell(); + } +} + +abstract class OutActionWalletV5 extends OutAction { + const OutActionWalletV5(); } abstract class OutAction extends TonSerialization { abstract final OutActionType type; const OutAction(); factory OutAction.deserialize(Slice slice) { - final tag = slice.loadUint(32); - switch (tag) { - case _OutActionTypeConst.outActionSendMsgTag: + int? tag = slice.tryPreloadUint32(); + OutActionType type; + try { + type = OutActionType.fromTag(tag); + } on TonDartPluginException { + tag = slice.tryPreLoadUint8(); + type = OutActionType.fromTag(tag); + } + switch (type) { + case OutActionType.sendMsg: return OutActionSendMsg.deserialize(slice); - case _OutActionTypeConst.outActionSetCodeTag: + case OutActionType.setCode: return OutActionSetCode.deserialize(slice); + case OutActionType.addExtension: + return OutActionAddExtension.deserialize(slice); + case OutActionType.removeExtension: + return OutActionRemoveExtension.deserialize(slice); + case OutActionType.setIsPublicKeyEnabled: + return OutActionSetIsPublicKeyEnabled.deserialize(slice); + case OutActionType.updateMiltiSig: + return OutActionUpdateMultiSig.deserialize(slice); + case OutActionType.multiSigSendMessage: + return OutActionMultiSigSendMsg.deserialize(slice); default: throw TonDartPluginException("Invalid OutAction tag.", details: { - "excepted": [ - _OutActionTypeConst.outActionSendMsgTag, - _OutActionTypeConst.outActionSetCodeTag - ].join(", "), + "excepted": OutActionType.values.map((e) => e.tag).join(", "), "tag": tag }); } @@ -67,32 +213,49 @@ abstract class OutAction extends TonSerialization { switch (type) { case OutActionType.sendMsg: return OutActionSendMsg.fromJson(json); - default: + case OutActionType.addExtension: + return OutActionAddExtension.fromJson(json); + case OutActionType.removeExtension: + return OutActionRemoveExtension.fromJson(json); + case OutActionType.setIsPublicKeyEnabled: + return OutActionSetIsPublicKeyEnabled.fromJson(json); + case OutActionType.setCode: return OutActionSetCode.fromJson(json); + default: + throw UnimplementedError("Invalid or unsupported OutActionType."); } } } -class OutActionSendMsg extends OutAction { - final SendMode mode; +class OutActionSendMsg extends OutActionWalletV5 { + final int mode; final MessageRelaxed outMessage; - const OutActionSendMsg({required this.mode, required this.outMessage}); + const OutActionSendMsg( + {this.mode = SendModeConst.payGasSeparately, required this.outMessage}); factory OutActionSendMsg.deserialize(Slice slice) { + final tag = slice.tryLoadUint32(); + if (tag != OutActionType.sendMsg.tag) { + throw const TonDartPluginException("Invalid OutActionSendMsg tag"); + } return OutActionSendMsg( - mode: SendMode.fromMode(slice.loadUint(8)), - outMessage: MessageRelaxed.deserialize(slice.loadRef().beginParse()), - ); + mode: slice.loadUint(8), + outMessage: MessageRelaxed.deserialize(slice.loadRef().beginParse())); } factory OutActionSendMsg.fromJson(Map json) { return OutActionSendMsg( - mode: SendMode.fromValue(json["mode"]), + mode: json["mode"], outMessage: MessageRelaxed.fromJson(json["out_message"])); } + OutActionSendMsg copyWith({int? mode, MessageRelaxed? outMessage}) { + return OutActionSendMsg( + mode: mode ?? this.mode, outMessage: outMessage ?? this.outMessage); + } + @override void store(Builder builder) { - builder.storeUint(_OutActionTypeConst.outActionSendMsgTag, 32); - builder.storeUint(mode.mode, 8); + builder.storeUint32(type.tag); + builder.storeUint(mode, 8); final messageCell = beginCell(); outMessage.store(messageCell); builder.storeRef(messageCell.endCell()); @@ -101,7 +264,7 @@ class OutActionSendMsg extends OutAction { @override Map toJson() { return { - "mode": mode.name, + "mode": mode, "out_message": outMessage.toJson(), "type": type.name }; @@ -115,6 +278,10 @@ class OutActionSetCode extends OutAction { final Cell newCode; const OutActionSetCode(this.newCode); factory OutActionSetCode.deserialize(Slice slice) { + final tag = slice.tryLoadUint32(); + if (tag != OutActionType.setCode.tag) { + throw const TonDartPluginException("Invalid OutActionSetCode tag"); + } return OutActionSetCode(slice.loadRef()); } factory OutActionSetCode.fromJson(Map json) { @@ -123,7 +290,7 @@ class OutActionSetCode extends OutAction { @override void store(Builder builder) { - builder.storeUint(_OutActionTypeConst.outActionSetCodeTag, 32); + builder.storeUint32(type.tag); builder.storeRef(newCode); } @@ -135,3 +302,217 @@ class OutActionSetCode extends OutAction { @override OutActionType get type => OutActionType.setCode; } + +abstract class OutActionExtended extends OutActionWalletV5 { + const OutActionExtended(); + factory OutActionExtended.deserialize(Slice slice) { + final tag = slice.tryPreLoadUint8(); + final type = OutActionType.fromTag(tag); + switch (type) { + case OutActionType.addExtension: + return OutActionAddExtension.deserialize(slice); + case OutActionType.removeExtension: + return OutActionRemoveExtension.deserialize(slice); + case OutActionType.setIsPublicKeyEnabled: + return OutActionSetIsPublicKeyEnabled.deserialize(slice); + default: + throw TonDartPluginException("Invalid OutAction extended tag.", + details: {"tag": tag}); + } + } +} + +class OutActionAddExtension extends OutActionExtended { + final TonAddress address; + const OutActionAddExtension(this.address); + factory OutActionAddExtension.deserialize(Slice slice) { + final tag = slice.tryLoadUint8(); + if (tag != OutActionType.addExtension.tag) { + throw const TonDartPluginException("Invalid OutActionAddExtension tag"); + } + return OutActionAddExtension(slice.loadAddress()); + } + factory OutActionAddExtension.fromJson(Map json) { + return OutActionAddExtension(TonAddress(json["address"])); + } + @override + void store(Builder builder) { + builder.storeUint(type.tag, 8); + builder.storeAddress(address); + } + + @override + Map toJson() { + return {"address": address.toFriendlyAddress()}; + } + + @override + OutActionType get type => OutActionType.addExtension; +} + +class OutActionRemoveExtension extends OutActionExtended { + final TonAddress address; + const OutActionRemoveExtension(this.address); + factory OutActionRemoveExtension.deserialize(Slice slice) { + final tag = slice.tryLoadUint8(); + if (tag != OutActionType.removeExtension.tag) { + throw const TonDartPluginException( + "Invalid OutActionRemoveExtension tag"); + } + return OutActionRemoveExtension(slice.loadAddress()); + } + factory OutActionRemoveExtension.fromJson(Map json) { + return OutActionRemoveExtension(TonAddress(json["address"])); + } + @override + void store(Builder builder) { + builder.storeUint(type.tag, 8); + builder.storeAddress(address); + } + + @override + Map toJson() { + return {"address": address.toFriendlyAddress()}; + } + + @override + OutActionType get type => OutActionType.removeExtension; +} + +class OutActionSetIsPublicKeyEnabled extends OutActionExtended { + final bool isEnabled; + const OutActionSetIsPublicKeyEnabled(this.isEnabled); + factory OutActionSetIsPublicKeyEnabled.fromJson(Map json) { + return OutActionSetIsPublicKeyEnabled(json["isEnabled"]); + } + factory OutActionSetIsPublicKeyEnabled.deserialize(Slice slice) { + final tag = slice.tryLoadUint8(); + if (tag != OutActionType.setIsPublicKeyEnabled.tag) { + throw const TonDartPluginException( + "Invalid OutActionSetIsPublicKeyEnabled tag"); + } + return OutActionSetIsPublicKeyEnabled(slice.loadBoolean()); + } + @override + void store(Builder builder) { + builder.storeUint(type.tag, 8); + builder.storeBitBolean(isEnabled); + } + + @override + OutActionType get type => OutActionType.setIsPublicKeyEnabled; + @override + Map toJson() { + return {"isEnabled": isEnabled}; + } +} + +abstract class OutActionMultiSig extends OutAction { + const OutActionMultiSig(); +} + +class OutActionMultiSigSendMsg extends OutActionMultiSig { + final int mode; + final MessageRelaxed outMessage; + const OutActionMultiSigSendMsg( + {this.mode = SendModeConst.payGasSeparately, required this.outMessage}); + factory OutActionMultiSigSendMsg.deserialize(Slice slice) { + final tag = slice.tryLoadUint32(); + if (tag != OutActionType.multiSigSendMessage.tag) { + throw const TonDartPluginException( + "Invalid OutActionMultiSigSendMsg tag"); + } + return OutActionMultiSigSendMsg( + mode: slice.loadUint(8), + outMessage: MessageRelaxed.deserialize(slice.loadRef().beginParse())); + } + factory OutActionMultiSigSendMsg.fromJson(Map json) { + return OutActionMultiSigSendMsg( + mode: json["mode"], + outMessage: MessageRelaxed.fromJson(json["out_message"])); + } + + OutActionMultiSigSendMsg copyWith({int? mode, MessageRelaxed? outMessage}) { + return OutActionMultiSigSendMsg( + mode: mode ?? this.mode, outMessage: outMessage ?? this.outMessage); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.tag); + builder.storeUint8(mode); + final messageCell = beginCell(); + outMessage.store(messageCell); + builder.storeRef(messageCell.endCell()); + } + + @override + Map toJson() { + return { + "mode": mode, + "out_message": outMessage.toJson(), + "type": type.name + }; + } + + @override + OutActionType get type => OutActionType.multiSigSendMessage; +} + +class OutActionUpdateMultiSig extends OutActionMultiSig { + final int threshold; + final List signers; + final List proposers; + + OutActionUpdateMultiSig({ + required this.threshold, + required List signers, + required List proposers, + }) : signers = List.unmodifiable(signers), + proposers = List.unmodifiable(proposers); + factory OutActionUpdateMultiSig.deserialize(Slice slice) { + final tag = slice.tryLoadUint32(); + if (tag != OutActionType.updateMiltiSig.tag) { + throw const TonDartPluginException("Invalid OutActionUpdateMultiSig tag"); + } + final threshhold = slice.loadUint8(); + final signers = OutActionUtils.signerCellToList(slice.loadMaybeRef()); + final proposers = slice.loadDict( + DictionaryKey.uintCodec(8), DictionaryValue.addressCodec()); + + return OutActionUpdateMultiSig( + threshold: threshhold, + signers: signers, + proposers: proposers.asMap.values.whereType().toList()); + } + factory OutActionUpdateMultiSig.fromJson(Map json) { + return OutActionUpdateMultiSig( + signers: (json["signers"] as List).map((e) => TonAddress(e)).toList(), + proposers: + (json["proposers"] as List).map((e) => TonAddress(e)).toList(), + threshold: json["threshold"]); + } + + @override + void store(Builder builder) { + builder.storeUint32(type.tag); + builder.storeUint8(threshold); + builder.storeRef(beginCell() + .storeDictDirect(OutActionUtils.signersToDict(signers)) + .endCell()); + builder.storeDict(dict: OutActionUtils.signersToDict(proposers)); + } + + @override + Map toJson() { + return { + "type": type.name, + "threshold": threshold, + "signers": signers.map((e) => e.toFriendlyAddress()).toList(), + "proposers": proposers.map((e) => e.toFriendlyAddress()).toList() + }; + } + + @override + OutActionType get type => OutActionType.updateMiltiSig; +} diff --git a/lib/src/models/models/send_mode.dart b/lib/src/models/models/send_mode.dart index 92e6a5b..db2a9ce 100644 --- a/lib/src/models/models/send_mode.dart +++ b/lib/src/models/models/send_mode.dart @@ -1,19 +1,31 @@ import 'package:ton_dart/src/exception/exception.dart'; +class SendModeConst { + static const int carryAllRemainingBalance = 128; + static const int carryAllRemainingIncomingValue = 64; + static const int destroyAccountIfZero = 32; + static const int payGasSeparately = 1; + static const int ignoreErrors = 2; + static const int none = 0; +} + class SendMode { final String name; final int mode; const SendMode._(this.mode, this.name); - static const SendMode carryAllRemainingBalance = - SendMode._(128, "carryAllRemainingBalance"); - static const SendMode carryAllRemainingIncomingValue = - SendMode._(64, "carryAllRemainingIncomingValue"); + static const SendMode carryAllRemainingBalance = SendMode._( + SendModeConst.carryAllRemainingBalance, "carryAllRemainingBalance"); + static const SendMode carryAllRemainingIncomingValue = SendMode._( + SendModeConst.carryAllRemainingIncomingValue, + "carryAllRemainingIncomingValue"); static const SendMode destroyAccountIfZero = - SendMode._(32, "destroyAccountIfZero"); - static const SendMode payGasSeparately = SendMode._(1, "payGasSeparately"); - static const SendMode ignoreErrors = SendMode._(2, "ignoreErrors"); - static const SendMode none = SendMode._(0, "none"); + SendMode._(SendModeConst.destroyAccountIfZero, "destroyAccountIfZero"); + static const SendMode payGasSeparately = + SendMode._(SendModeConst.payGasSeparately, "payGasSeparately"); + static const SendMode ignoreErrors = + SendMode._(SendModeConst.ignoreErrors, "ignoreErrors"); + static const SendMode none = SendMode._(SendModeConst.none, "none"); static const List values = [ carryAllRemainingBalance, carryAllRemainingIncomingValue, diff --git a/lib/src/models/models/shard_account.dart b/lib/src/models/models/shard_account.dart index a9244e7..8bae26f 100644 --- a/lib/src/models/models/shard_account.dart +++ b/lib/src/models/models/shard_account.dart @@ -2,7 +2,7 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/models/models/account.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; /// Source: https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L256 /// account_descr$_ account:^Account last_trans_hash:bits256 @@ -32,8 +32,8 @@ class ShardAccount extends TonSerialization { } factory ShardAccount.fromJson(Map json) { return ShardAccount( - account: (json["account"] as Object?) - ?.to((result) => TonAccount.fromJson(result.cast())), + account: (json["account"] as Object?)?.convertTo( + (result) => TonAccount.fromJson(result.cast())), lastTransactionHash: BigintUtils.parse(json["last_transaction_hash"]), lastTransactionLt: BigintUtils.parse(json["last_transaction_lt"]), ); diff --git a/lib/src/models/models/shard_accounts.dart b/lib/src/models/models/shard_accounts.dart index 25e7a43..7f32e11 100644 --- a/lib/src/models/models/shard_accounts.dart +++ b/lib/src/models/models/shard_accounts.dart @@ -4,7 +4,7 @@ import 'package:ton_dart/src/dict/dictionary.dart'; import 'package:ton_dart/src/models/models/depth_balance_info.dart'; import 'package:ton_dart/src/models/models/shard_account.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class ShardAccountsCodec { static final DictionaryValue codec = diff --git a/lib/src/models/models/shard_state_unsplit.dart b/lib/src/models/models/shard_state_unsplit.dart index 59139fa..0e55b4a 100644 --- a/lib/src/models/models/shard_state_unsplit.dart +++ b/lib/src/models/models/shard_state_unsplit.dart @@ -3,7 +3,7 @@ import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/exception/exception.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'master_chain_state_extra.dart'; import 'shard_accounts.dart'; import 'shard_ident.dart'; @@ -109,10 +109,11 @@ class ShardStateUnsplit extends TonSerialization { genLt: BigintUtils.parse(json["gen_lt"]), minRefMcSeqno: json["min_ref_mc_seqno"], beforeSplit: json["before_split"], - accounts: ((json["accounts"] as Object?)?.to( + accounts: ((json["accounts"] as Object?)?.convertTo( (result) => ShardAccounts.fromJson(result.cast()))), - extras: (json["extras"] as Object?)?.to( - (result) => MasterchainStateExtra.fromJson(result.cast()))); + extras: (json["extras"] as Object?) + ?.convertTo( + (result) => MasterchainStateExtra.fromJson(result.cast()))); } @override diff --git a/lib/src/models/models/state_init.dart b/lib/src/models/models/state_init.dart index 65d4220..d8e8df3 100644 --- a/lib/src/models/models/state_init.dart +++ b/lib/src/models/models/state_init.dart @@ -6,7 +6,7 @@ import 'package:ton_dart/src/dict/dictionary.dart'; import 'package:ton_dart/src/models/models/simple_library.dart'; import 'package:ton_dart/src/models/models/tick_tock.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; class _StateInitUtils { static Dictionary libraryDict( @@ -50,13 +50,15 @@ class StateInit extends TonSerialization { return StateInit( splitDepth: json["splitDepth"], special: (json["special"] as Object?) - ?.to>((p0) => TickTock.fromJson(p0)), + ?.convertTo>( + (p0) => TickTock.fromJson(p0)), code: (json["code"] as Object?) - ?.to((result) => Cell.fromBase64(result)), + ?.convertTo((result) => Cell.fromBase64(result)), data: (json["data"] as Object?) - ?.to((result) => Cell.fromBase64(result)), + ?.convertTo((result) => Cell.fromBase64(result)), libraries: (json["libraries"] as Object?) - ?.to, Map>((result) { + ?.convertTo, Map>( + (result) { return result.map((key, value) => MapEntry(BigintUtils.parse(key), SimpleLibrary.fromJson(value))); })); @@ -78,9 +80,9 @@ class StateInit extends TonSerialization { } builder.storeMaybeRef(cell: code); builder.storeMaybeRef(cell: data); - final dict = libraries - ?.to, Map>( - (p0) => _StateInitUtils.libraryDict(map: p0)); + final dict = libraries?.convertTo, + Map>( + (p0) => _StateInitUtils.libraryDict(map: p0)); builder.storeDict(dict: dict); } diff --git a/lib/src/models/models/transaction.dart b/lib/src/models/models/transaction.dart index 2296793..a2a5068 100644 --- a/lib/src/models/models/transaction.dart +++ b/lib/src/models/models/transaction.dart @@ -7,7 +7,7 @@ import 'package:ton_dart/src/models/models/currency_collection.dart'; import 'package:ton_dart/src/models/models/message.dart'; import 'package:ton_dart/src/models/models/transaction_description.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'hash_update.dart'; @@ -113,8 +113,8 @@ class TonTransaction extends TonSerialization { outMessagesCount: json["out_meessages_count"], oldStatus: AccountStatus.fromJson(json["old_status"]), endStatus: AccountStatus.fromJson(json["end_status"]), - inMessage: (json["in_message"] as Object?) - ?.to((result) => Message.fromJson(result.cast())), + inMessage: (json["in_message"] as Object?)?.convertTo( + (result) => Message.fromJson(result.cast())), outMessages: (json["out_messages"] as Map).map( (key, value) => MapEntry(key, Message.fromJson((value as Map).cast()))), diff --git a/lib/src/models/models/transaction_credit_phase.dart b/lib/src/models/models/transaction_credit_phase.dart index f29f7db..fea88bd 100644 --- a/lib/src/models/models/transaction_credit_phase.dart +++ b/lib/src/models/models/transaction_credit_phase.dart @@ -2,7 +2,7 @@ import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/src/boc/bit/builder.dart'; import 'package:ton_dart/src/boc/cell/slice.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'currency_collection.dart'; diff --git a/lib/src/models/models/transaction_description.dart b/lib/src/models/models/transaction_description.dart index 2c675da..e99df13 100644 --- a/lib/src/models/models/transaction_description.dart +++ b/lib/src/models/models/transaction_description.dart @@ -5,7 +5,7 @@ import 'package:ton_dart/src/models/models/split_merge_info.dart'; import 'package:ton_dart/src/models/models/transaction.dart'; import 'package:ton_dart/src/models/models/transaction_bounce_phase.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; import 'transaction_action_phase.dart'; import 'transaction_compute_phase.dart'; import 'transaction_credit_phase.dart'; @@ -164,17 +164,17 @@ class TransactionDescriptionGeneric extends TransactionDescription { return TransactionDescriptionGeneric( creditFirst: json["credit_first"], storagePhase: (json["storage_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionStoragePhase.fromJson(result.cast())), creditPhase: (json["credit_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionCreditPhase.fromJson(result.cast())), computePhase: TransactionComputePhase.fromJson(json["compute_phase"]), actionPhase: (json["action_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionActionPhase.fromJson(result.cast())), bouncePhase: (json["bounce_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionBouncePhase.fromJson(result.cast())), aborted: json["aborted"], destroyed: json["destroyed"]); @@ -285,7 +285,7 @@ class TransactionDescriptionTickTock extends TransactionDescription { storagePhase: TransactionStoragePhase.fromJson(json["storage_phase"]), computePhase: TransactionComputePhase.fromJson(json["compute_phase"]), actionPhase: (json["action_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionActionPhase.fromJson(result.cast())), aborted: json["aborted"], destroyed: json["destroyed"], @@ -366,11 +366,11 @@ class TransactionDescriptionSplitPrepare extends TransactionDescription { return TransactionDescriptionSplitPrepare( splitInfo: SplitMergeInfo.fromJson(json["split_info"]), storagePhase: (json["storage_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionStoragePhase.fromJson(result.cast())), computePhase: TransactionComputePhase.fromJson(json["compute_phase"]), actionPhase: (json["action_phase"] as Object?) - ?.to( + ?.convertTo( (result) => TransactionActionPhase.fromJson(result.cast())), aborted: json["aborted"], destroyed: json["destroyed"]); diff --git a/lib/src/models/models/transaction_storage_phase.dart b/lib/src/models/models/transaction_storage_phase.dart index 2be9154..dea0a1d 100644 --- a/lib/src/models/models/transaction_storage_phase.dart +++ b/lib/src/models/models/transaction_storage_phase.dart @@ -3,7 +3,7 @@ import 'package:ton_dart/src/boc/bit/builder.dart'; import 'package:ton_dart/src/boc/cell/slice.dart'; import 'package:ton_dart/src/models/models/account_status_change.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/src/utils/extentions.dart'; +import 'package:ton_dart/src/utils/utils/extentions.dart'; /// Source: https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L284 /// tr_phase_storage$_ storage_fees_collected:Grams diff --git a/lib/src/provider/core/ton_center_v3_methods.dart b/lib/src/provider/core/ton_center_v3_methods.dart index 24c1994..8db796a 100644 --- a/lib/src/provider/core/ton_center_v3_methods.dart +++ b/lib/src/provider/core/ton_center_v3_methods.dart @@ -13,4 +13,6 @@ class TonCenterV3Methods { static const TonCenterV3Methods estimateFee = TonCenterV3Methods._(name: "estimateFee"); + static const TonCenterV3Methods account = + TonCenterV3Methods._(name: "account"); } diff --git a/lib/src/provider/methods/ton_center_v3/account/account.dart b/lib/src/provider/methods/ton_center_v3/account/account.dart new file mode 100644 index 0000000..962237b --- /dev/null +++ b/lib/src/provider/methods/ton_center_v3/account/account.dart @@ -0,0 +1,14 @@ +import 'package:ton_dart/src/provider/core/core.dart'; +import 'package:ton_dart/src/provider/core/ton_center_v3_methods.dart'; + +class TonCenterV3Account extends TonCenterV3RequestParam, + Map> { + final String address; + TonCenterV3Account({required this.address}); + + @override + Map get queryParameters => {"address": address}; + + @override + String get method => TonCenterV3Methods.account.uri; +} diff --git a/lib/src/provider/methods/ton_center_v3/models.dart b/lib/src/provider/methods/ton_center_v3/models.dart index 92ef53d..c968fb9 100644 --- a/lib/src/provider/methods/ton_center_v3/models.dart +++ b/lib/src/provider/methods/ton_center_v3/models.dart @@ -2,3 +2,4 @@ export 'default/master_chain_info.dart'; export 'jetton/get_jetton_masters.dart'; export 'jetton/get_jetton_wallet.dart'; export 'default/estimate_fee.dart'; +export 'account/account.dart'; diff --git a/lib/src/provider/models/response/account_address.dart b/lib/src/provider/models/response/account_address.dart index 69c1529..6945673 100644 --- a/lib/src/provider/models/response/account_address.dart +++ b/lib/src/provider/models/response/account_address.dart @@ -1,4 +1,3 @@ -import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:ton_dart/ton_dart.dart'; class AccountAddressResponse with JsonSerialization { diff --git a/lib/src/provider/models/response/account_status.dart b/lib/src/provider/models/response/account_status.dart index d94b3e5..f7f9898 100644 --- a/lib/src/provider/models/response/account_status.dart +++ b/lib/src/provider/models/response/account_status.dart @@ -20,7 +20,7 @@ class AccountStatusResponse { String get value => _value; - bool get isActive => this != uninit; + bool get isActive => this == active || this == frozen; static AccountStatusResponse fromName(String? name) { if (name == "uninitialized") return AccountStatusResponse.uninit; diff --git a/lib/src/provider/models/response/gas_limit_prices.dart b/lib/src/provider/models/response/gas_limit_prices.dart index 0038b13..9f0c1fb 100644 --- a/lib/src/provider/models/response/gas_limit_prices.dart +++ b/lib/src/provider/models/response/gas_limit_prices.dart @@ -1,5 +1,4 @@ import 'package:ton_dart/src/exception/exception.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/ton_dart.dart'; diff --git a/lib/src/provider/models/response/msg_forward_prices.dart b/lib/src/provider/models/response/msg_forward_prices.dart index a4d5123..6edbedb 100644 --- a/lib/src/provider/models/response/msg_forward_prices.dart +++ b/lib/src/provider/models/response/msg_forward_prices.dart @@ -1,5 +1,4 @@ import 'package:ton_dart/src/exception/exception.dart'; -import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:ton_dart/ton_dart.dart'; diff --git a/lib/src/provider/models/response/trace.dart b/lib/src/provider/models/response/trace.dart index 7abcdb9..b13dd6e 100644 --- a/lib/src/provider/models/response/trace.dart +++ b/lib/src/provider/models/response/trace.dart @@ -1,5 +1,4 @@ import 'package:ton_dart/src/serialization/serialization.dart'; -import 'package:ton_dart/ton_dart.dart'; import 'transaction.dart'; class TraceResponse with JsonSerialization { diff --git a/lib/src/tuple/exception/exception.dart b/lib/src/tuple/exception/exception.dart index cbfaadc..17e86f6 100644 --- a/lib/src/tuple/exception/exception.dart +++ b/lib/src/tuple/exception/exception.dart @@ -1,6 +1,11 @@ import 'package:ton_dart/src/exception/exception.dart'; +/// Exception class for errors related to tuple processing. class TupleException extends TonDartPluginException { + /// Creates a new instance of TupleException. + /// + /// [message] - The error message associated with the exception. + /// [details] - Optional additional details about the error. This can include any context-specific information. TupleException(String message, {Map? details}) : super(message, details: details); } diff --git a/lib/src/tuple/tuple/builder.dart b/lib/src/tuple/tuple/builder.dart index 4382167..a06c5fd 100644 --- a/lib/src/tuple/tuple/builder.dart +++ b/lib/src/tuple/tuple/builder.dart @@ -3,8 +3,12 @@ import 'package:ton_dart/src/boc/bit/builder.dart'; import 'package:ton_dart/src/boc/cell/cell.dart'; import 'tuple.dart'; +/// A class for building a list of tuple items. class TupleBuilder { final List _tuple = []; + + /// Checks if the provided value is null and adds a TupleItemNull if true. + /// Returns true if the value is null. bool _isNull(Object? v) { if (v == null) { _tuple.add(const TupleItemNull()); @@ -12,51 +16,75 @@ class TupleBuilder { return v == null; } + /// Adds a BigInt value to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. void writeNumber(BigInt? v) { if (_isNull(v)) return; _tuple.add(TupleItemInt(v!)); } + /// Adds a boolean value to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. + /// Converts true to -1 and false to 0. void writeBoolean(bool? v) { if (_isNull(v)) return; _tuple.add(TupleItemInt(v! ? -BigInt.one : BigInt.zero)); } + /// Adds a buffer (list of bytes) to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. + /// Encodes the buffer into a cell and stores it as a TupleItemSlice. void writeBuffer(List? v) { if (_isNull(v)) return; _tuple.add(TupleItemSlice(beginCell().storeBuffer(v!).endCell())); } + /// Adds a string value to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. + /// Encodes the string into a cell and stores it as a TupleItemSlice. void writeString(String? v) { if (_isNull(v)) return; _tuple.add(TupleItemSlice(beginCell().storeStringTail(v!).endCell())); } + /// Adds a cell to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. void writeCell(Cell? v) { if (_isNull(v)) return; _tuple.add(TupleItemCell(v!)); } + /// Adds a slice to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. void writeSlice(Cell? v) { if (_isNull(v)) return; _tuple.add(TupleItemSlice(v!)); } + /// Adds a builder (cell) to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. void writeBuilder(Cell? v) { if (_isNull(v)) return; _tuple.add(TupleItemBuilder(v!)); } + /// Adds a list of tuple items to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. void writeTuple(List? v) { if (_isNull(v)) return; _tuple.add(TupleItemTuple(v!)); } + /// Adds an address to the tuple list. + /// If the value is null, it adds a TupleItemNull instead. + /// Encodes the address into a cell and stores it as a TupleItemSlice. void writeAddress(TonBaseAddress? v) { if (_isNull(v)) return; _tuple.add(TupleItemSlice(beginCell().storeAddress(v!).endCell())); } + /// Returns the final list of tuple items. + /// Creates a new list from the internal _tuple list. List build() { return List.from(_tuple); } diff --git a/lib/src/tuple/tuple/tuple.dart b/lib/src/tuple/tuple/tuple.dart index 9b3f465..f95a93f 100644 --- a/lib/src/tuple/tuple/tuple.dart +++ b/lib/src/tuple/tuple/tuple.dart @@ -4,6 +4,7 @@ import 'package:ton_dart/src/helper/ton_helper.dart'; import 'package:ton_dart/src/serialization/serialization.dart'; import 'package:ton_dart/src/tuple/exception/exception.dart'; +/// Represents the types of tuple items with associated string names. class TupleItemTypes { final String name; const TupleItemTypes._(this.name); @@ -44,6 +45,7 @@ class TupleItemTypes { } } +/// Abstract base class for all tuple items. abstract class TupleItem with JsonSerialization { abstract final TupleItemTypes type; const TupleItem(); @@ -98,6 +100,7 @@ abstract class TupleItem with JsonSerialization { int get hashCode => type.hashCode; } +/// Represents a tuple item containing a list of other tuple items. class TupleItemTuple extends TupleItem { final List items; const TupleItemTuple(this.items); @@ -131,6 +134,7 @@ class TupleItemTuple extends TupleItem { } } +/// Represents a tuple item with a null value. class TupleItemNull extends TupleItem { const TupleItemNull(); factory TupleItemNull.fromJson(Map json) { @@ -150,6 +154,7 @@ class TupleItemNull extends TupleItem { } } +/// Represents a tuple item containing an integer value. class TupleItemInt extends TupleItem { final BigInt value; const TupleItemInt(this.value); @@ -186,6 +191,7 @@ class TupleItemInt extends TupleItem { } } +/// // Represents a tuple item with a NaN value. class TupleItemNaN extends TupleItem { const TupleItemNaN(); factory TupleItemNaN.fromJson(Map json) { @@ -205,6 +211,7 @@ class TupleItemNaN extends TupleItem { } } +/// Represents a tuple item containing a cell. class TupleItemCell extends TupleItem { final Cell cell; const TupleItemCell(this.cell); @@ -245,6 +252,7 @@ class TupleItemCell extends TupleItem { } } +/// Represents a tuple item containing a slice. class TupleItemSlice extends TupleItemCell { const TupleItemSlice(Cell cell) : super(cell); factory TupleItemSlice.fromJson(Map json) { @@ -278,6 +286,7 @@ class TupleItemSlice extends TupleItemCell { int get hashCode => super.hashCode ^ cell.hashCode; } +/// Represents a tuple item containing a builder. class TupleItemBuilder extends TupleItemCell { const TupleItemBuilder(Cell cell) : super(cell); factory TupleItemBuilder.fromJson(Map json) { diff --git a/lib/src/tuple/tuple/tuple_reader.dart b/lib/src/tuple/tuple/tuple_reader.dart index ae3f887..e138d5f 100644 --- a/lib/src/tuple/tuple/tuple_reader.dart +++ b/lib/src/tuple/tuple/tuple_reader.dart @@ -4,15 +4,23 @@ import 'package:ton_dart/src/boc/boc.dart'; import 'package:ton_dart/src/tuple/exception/exception.dart'; import 'tuple.dart'; +/// A class for reading and manipulating tuple items from a list. class TupleReader { final List _items; + /// Constructor that initializes the reader with a list of tuple items. TupleReader(List items) : _items = List.from(items); + + /// Creates a clone of the current TupleReader. TupleReader clone() => TupleReader(_items); + + /// Returns the number of remaining tuple items. int get remaining { return _items.length; } + /// Peeks at the next tuple item without removing it from the list. + /// Throws a TupleException if there are no more items. TupleItem peek() { if (_items.isEmpty) { throw TupleException('EOF'); @@ -20,6 +28,8 @@ class TupleReader { return _items[0]; } + /// Removes and returns the next tuple item from the list. + /// Throws a TupleException if there are no more items. TupleItem pop() { if (_items.isEmpty) { throw TupleException('EOF'); @@ -29,6 +39,8 @@ class TupleReader { return res; } + /// Skips the specified number of tuple items. + /// Returns the updated TupleReader. TupleReader skip({int num = 1}) { for (int i = 0; i < num; i++) { pop(); @@ -36,6 +48,8 @@ class TupleReader { return this; } + /// Reads a BigInt from the next tuple item. + /// Throws a TupleException if the item is not an integer. BigInt readBigNumber() { TupleItem popped = pop(); if (popped is! TupleItemInt) { @@ -45,6 +59,7 @@ class TupleReader { return popped.value; } + /// Reads a BigInt from the next tuple item and returns it as a hexadecimal string. String readBigNumberAsHex() { final BigInt value = readBigNumber(); final List toBytes = @@ -52,6 +67,8 @@ class TupleReader { return BytesUtils.toHexString(toBytes); } + /// Reads an optional BigInt from the next tuple item. + /// Returns null if the item is null, otherwise throws an exception if the item is not an integer. BigInt? readBigNumberOpt() { TupleItem popped = pop(); if (popped is TupleItemNull) { @@ -64,29 +81,40 @@ class TupleReader { return popped.value; } + /// Reads an integer from the next tuple item. + /// Converts the BigInt to an integer. int readNumber() { return readBigNumber().toInt(); } + /// Reads an optional integer from the next tuple item. + /// Converts the BigInt to an integer if present. int? readNumberOpt() { BigInt? r = readBigNumberOpt(); return r?.toInt(); } + /// Reads a boolean value from the next tuple item. + /// Interprets zero as false and non-zero as true. bool readBoolean() { int res = readNumber(); return res == 0 ? false : true; } + /// Reads an optional boolean value from the next tuple item. + /// Interprets zero as false, non-zero as true, and returns null if the item is null. bool? readBooleanOpt() { int? res = readNumberOpt(); return res != null ? (res == 0 ? false : true) : null; } + /// Reads an address from the next tuple item, which is expected to be a cell. TonAddress readAddress() { return readCell().beginParse().loadAddress(); } + /// Reads an optional address from the next tuple item. + /// Returns null if the item is null, otherwise parses the cell to load an address. TonAddress? readAddressOpt() { Cell? r = readCellOpt(); if (r != null) { @@ -96,12 +124,16 @@ class TupleReader { } } + /// Reads a cell from the next tuple item. + /// Throws a TupleException if the item is not a cell. Cell readCell() { TupleItem popped = pop(); if (popped is TupleItemCell) return popped.cell; throw TupleException("Invalid tuple cell.", details: {"value": popped}); } + /// Reads an optional cell from the next tuple item. + /// Returns null if the item is null, otherwise throws an exception if the item is not a cell. Cell? readCellOpt() { TupleItem popped = pop(); if (popped is TupleItemNull) { @@ -111,6 +143,8 @@ class TupleReader { throw TupleException("Invalid tuple cell.", details: {"value": popped}); } + /// Reads a tuple from the next tuple item and returns a new TupleReader for the tuple. + /// Throws a TupleException if the item is not a tuple. TupleReader readTuple() { TupleItem popped = pop(); if (popped is! TupleItemTuple) { @@ -119,6 +153,8 @@ class TupleReader { return TupleReader(popped.items); } + /// Reads an optional tuple from the next tuple item. + /// Returns null if the item is null, otherwise returns a new TupleReader for the tuple. TupleReader? readTupleOpt() { TupleItem popped = pop(); if (popped is TupleItemNull) { @@ -130,6 +166,8 @@ class TupleReader { return TupleReader(popped.items); } + /// Reads a Lisp-style list from the given TupleReader. + /// Each item in the list must be a tuple or null, and the list ends with null. static List readLispListStatics(TupleReader? reader) { List result = []; TupleReader? tail = reader; @@ -147,6 +185,8 @@ class TupleReader { return result; } + /// Reads a Lisp-style list directly from the current TupleReader. + /// Returns an empty list if there is only a null item. List readLispListDirect() { if (_items.length == 1 && _items[0] is TupleItemNull) { return []; @@ -154,10 +194,14 @@ class TupleReader { return TupleReader.readLispListStatics(this); } + /// Reads a Lisp-style list from the next tuple item. + /// Uses the static method to handle the list reading. List readLispList() { return TupleReader.readLispListStatics(readTupleOpt()); } + /// Reads a buffer of bytes from the next cell item. + /// Throws a TupleException if the buffer length or bit alignment is invalid. List readBuffer() { Slice s = readCell().beginParse(); if (s.remainingRefs != 0) { @@ -169,6 +213,8 @@ class TupleReader { return s.loadBuffer(s.remainingBits ~/ 8); } + /// Reads an optional buffer of bytes from the next cell item. + /// Returns null if the item is null, otherwise reads the buffer. List? readBufferOpt() { TupleItem popped = peek(); if (popped is TupleItemNull) { @@ -184,11 +230,14 @@ class TupleReader { return s.loadBuffer(s.remainingBits ~/ 8); } + /// Reads a string from the next cell item. String readString() { Slice s = readCell().beginParse(); return s.loadStringTail(); } + /// Reads an optional string from the next cell item. + /// Returns null if the item is null, otherwise reads the string. String? readStringOpt() { TupleItem popped = peek(); if (popped is TupleItemNull) { diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 218a7cb..a919ba6 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,26 +1,5 @@ -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:ton_dart/src/exception/exception.dart'; - -class Base64Utils { - static String encodeBase64(List bytes, {bool urlSafe = false}) { - final encode = StringUtils.decode(bytes, type: StringEncoding.base64); - if (urlSafe) { - return encode.replaceAll('+', '-').replaceAll('/', '_'); - } - return encode; - } - - static List decodeBase64(String base64) { - try { - String b64 = base64; - final int reminder = b64.length % 4; - if (reminder != 0 && !b64.endsWith("=")) { - b64 += "=" * (4 - reminder); - } - return StringUtils.encode(b64, type: StringEncoding.base64); - } catch (e) { - throw TonDartPluginException("Invalid base64 string.", - details: {"value": base64}); - } - } -} +export 'utils/base64.dart'; +export 'utils/crypto.dart'; +export 'utils/extentions.dart'; +export 'utils/fee.dart'; +export 'utils/math.dart'; diff --git a/lib/src/utils/utils/base64.dart b/lib/src/utils/utils/base64.dart new file mode 100644 index 0000000..218a7cb --- /dev/null +++ b/lib/src/utils/utils/base64.dart @@ -0,0 +1,26 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:ton_dart/src/exception/exception.dart'; + +class Base64Utils { + static String encodeBase64(List bytes, {bool urlSafe = false}) { + final encode = StringUtils.decode(bytes, type: StringEncoding.base64); + if (urlSafe) { + return encode.replaceAll('+', '-').replaceAll('/', '_'); + } + return encode; + } + + static List decodeBase64(String base64) { + try { + String b64 = base64; + final int reminder = b64.length % 4; + if (reminder != 0 && !b64.endsWith("=")) { + b64 += "=" * (4 - reminder); + } + return StringUtils.encode(b64, type: StringEncoding.base64); + } catch (e) { + throw TonDartPluginException("Invalid base64 string.", + details: {"value": base64}); + } + } +} diff --git a/lib/src/utils/crypto.dart b/lib/src/utils/utils/crypto.dart similarity index 100% rename from lib/src/utils/crypto.dart rename to lib/src/utils/utils/crypto.dart diff --git a/lib/src/utils/extentions.dart b/lib/src/utils/utils/extentions.dart similarity index 88% rename from lib/src/utils/extentions.dart rename to lib/src/utils/utils/extentions.dart index a7b1531..0b9432e 100644 --- a/lib/src/utils/extentions.dart +++ b/lib/src/utils/utils/extentions.dart @@ -1,5 +1,5 @@ extension QuickOnList on List { - List get mutabl { + List get immutable { return List.unmodifiable(this); } } @@ -18,7 +18,7 @@ extension QuickOnMap on Map { typedef ObjectConvertable = T Function(E result); extension QuickTo on Object { - T to(ObjectConvertable fun) { + T convertTo(ObjectConvertable fun) { return fun(this as E); } } diff --git a/lib/src/utils/fee.dart b/lib/src/utils/utils/fee.dart similarity index 100% rename from lib/src/utils/fee.dart rename to lib/src/utils/utils/fee.dart diff --git a/lib/src/utils/math.dart b/lib/src/utils/utils/math.dart similarity index 100% rename from lib/src/utils/math.dart rename to lib/src/utils/utils/math.dart diff --git a/lib/ton_dart.dart b/lib/ton_dart.dart index 9fcd8d1..a3beb13 100644 --- a/lib/ton_dart.dart +++ b/lib/ton_dart.dart @@ -9,4 +9,5 @@ export 'src/helper/ton_helper.dart'; export 'src/tuple/tuple.dart'; export 'src/provider/provider.dart'; export 'src/models/models.dart'; -export 'src/utils/fee.dart'; +export 'src/utils/utils/fee.dart'; +export 'src/serialization/serialization.dart'; diff --git a/test/tests/contract_v5/auction_test.dart b/test/tests/contract_v5/auction_test.dart new file mode 100644 index 0000000..5a3bd64 --- /dev/null +++ b/test/tests/contract_v5/auction_test.dart @@ -0,0 +1,145 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + _v5R1Actions(); +} + +final _mockMessageRelaxed1 = MessageRelaxed( + info: CommonMessageInfoRelaxedExternalOut( + src: null, createdLt: BigInt.zero, createdAt: 0, dest: null), + body: beginCell().storeUint(0, 8).endCell()); +final _mockMessageRelaxed2 = MessageRelaxed( + info: CommonMessageInfoRelaxedInternal( + src: null, + createdLt: BigInt.from(12345), + bounce: false, + bounced: false, + ihrDisabled: true, + value: CurrencyCollection(coins: BigInt.one), + createdAt: 123456, + ihrFee: BigInt.one, + forwardFee: BigInt.one, + dest: TonAddress("0:${'2' * 64}")), + body: beginCell().storeUint(0, 8).endCell()); + +final mockAddress = TonAddress("0:${'1' * 64}"); + +void _v5R1Actions() { + const outActionSetIsPublicKeyEnabledTag = 0x04; + const outActionAddExtensionTag = 0x02; + const outActionRemoveExtensionTag = 0x03; + test('Should serialise setIsPublicKeyEnabled action with true flag', () { + const action = OutActionSetIsPublicKeyEnabled(true); + + final actual = beginCell().store(action).endCell(); + + final expected = beginCell() + .storeUint(outActionSetIsPublicKeyEnabledTag, 8) + .storeBit(1) + .endCell(); + + expect(expected, actual); + expect(expected.toBase64(), "te6cckEBAQEABAAAAwTAorSwPA=="); + }); + test('Should serialise setIsPublicKeyEnabled action with false flag', () { + const action = OutActionSetIsPublicKeyEnabled(false); + + final actual = beginCell().store(action).endCell(); + + final expected = beginCell() + .storeUint(outActionSetIsPublicKeyEnabledTag, 8) + .storeBit(0) + .endCell(); + + expect(expected, actual); + expect(expected.toBase64(), "te6cckEBAQEABAAAAwRA2o9Gvg=="); + }); + test('Should serialise add extension action', () { + final action = OutActionAddExtension(mockAddress); + + final actual = beginCell().store(action).endCell(); + + final expected = beginCell() + .storeUint(outActionAddExtensionTag, 8) + .storeAddress(mockAddress) + .endCell(); + expect(expected, actual); + expect(expected.toBase64(), + "te6cckEBAQEAJQAARQKAAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIwCCDtAg=="); + }); + test('Should serialise remove extension action', () { + final action = OutActionRemoveExtension(mockAddress); + + final actual = beginCell().store(action).endCell(); + + final expected = beginCell() + .storeUint(outActionRemoveExtensionTag, 8) + .storeAddress(mockAddress) + .endCell(); + expect(expected, actual); + expect(expected.toBase64(), + "te6cckEBAQEAJQAARQOAAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIwK7YrIw=="); + }); + + test("Should serialize extended out list", () { + final List v5Actions = [ + OutActionAddExtension(mockAddress), + const OutActionSetIsPublicKeyEnabled(false), + OutActionSendMsg( + mode: SendMode.payGasSeparately.mode, + outMessage: _mockMessageRelaxed1) + ]; + final actions = OutActionsV5(actions: v5Actions); + final actual = beginCell().store(actions).endCell(); + expect(actual.toBase64(), + "te6cckEBBQEARgACRcCgAIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiMAQQCCg7DyG0BAgMAAAAcwAAAAAAAAAAAAAAAAAAAAwRAppFI0w=="); + }); + test("Should serialize extended out list and produce the expected boc", () { + final List v5Actions = [ + OutActionAddExtension(mockAddress), + const OutActionSetIsPublicKeyEnabled(false), + OutActionSendMsg( + mode: SendMode.payGasSeparately.mode + SendMode.ignoreErrors.mode, + outMessage: _mockMessageRelaxed1) + ]; + final actions = OutActionsV5(actions: v5Actions); + final actual = beginCell().store(actions).endCell(); + expect(actual.toBase64(), + "te6cckEBBQEARgACRcCgAIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiMAQQCCg7DyG0DAgMAAAAcwAAAAAAAAAAAAAAAAAAAAwRAnAYhjw=="); + }); + test( + "Should serialize extended out list and produce the expected boc for complex structures", + () { + final List v5Actions = [ + OutActionAddExtension(mockAddress), + const OutActionSetIsPublicKeyEnabled(false), + OutActionRemoveExtension(mockAddress), + OutActionSendMsg( + mode: SendMode.payGasSeparately.mode + SendMode.ignoreErrors.mode, + outMessage: _mockMessageRelaxed1), + OutActionSendMsg( + mode: SendMode.none.mode, outMessage: _mockMessageRelaxed2) + ]; + final actions = OutActionsV5(actions: v5Actions); + final actual = beginCell().store(actions).endCell(); + expect(actual.toBase64(), + "te6cckEBCAEAqwACRcCgAIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiMAQYCCg7DyG0DAgUCCg7DyG0AAwQAAABoQgAREREREREREREREREREREREREREREREREREREREREREQgIQEQEAAAAAAAAwOQAB4kAAAAcwAAAAAAAAAAAAAAAAAABAwRABwBFA4ACIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIjA3zHHW"); + }); + test("Should deserialize extended out list", () { + final List v5Actions = [ + OutActionSendMsg( + mode: SendMode.payGasSeparately.mode, + outMessage: _mockMessageRelaxed1), + OutActionAddExtension(mockAddress), + const OutActionSetIsPublicKeyEnabled(true), + ]; + final actions = OutActionsV5(actions: v5Actions); + final actual = beginCell().store(actions).endCell(); + final boc = Cell.fromBase64( + "te6cckEBBQEARgACRcCgAIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiMAQQCCg7DyG0BAgMAAAAcwAAAAAAAAAAAAAAAAAAAAwTA3qq+UQ=="); + expect(actual.toBase64(), boc.toBase64()); + final deserialize = OutActionsV5.deserialize(boc.beginParse()); + expect(deserialize.serialize().toBase64(), boc.toBase64()); + }); +} diff --git a/test/tests/contract_v5/wallet_context_test.dart b/test/tests/contract_v5/wallet_context_test.dart new file mode 100644 index 0000000..7d4e74c --- /dev/null +++ b/test/tests/contract_v5/wallet_context_test.dart @@ -0,0 +1,102 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + group("Wallet V5 context", () { + _walletV5ClientContext(); + _walletV5ClientContextDeserialize(); + _deserializeWalletCustomWalletContext(); + _serializeWalletCustomWalletContext(); + }); +} + +void _walletV5ClientContext() { + test("client context", () { + final walletContext = + V5R1ClientContext(chain: TonChain.mainnet, subwalletNumber: 0); + + final actual = beginCell().store(walletContext).endCell(); + + final context = beginCell() + .storeUint(1, 1) + .storeInt(0, 8) + .storeUint(0, 8) + .storeUint(0, 15) + .endCell() + .beginParse() + .loadInt(32); + + final expected = beginCell() + .storeInt(BigInt.from(context) ^ BigInt.from(-239), 32) + .endCell(); + expect(actual, expected); + expect(actual.toBase64(), "te6cckEBAQEABgAACH///xHZat+l"); + }); +} + +void _walletV5ClientContextDeserialize() { + test("deserialize client wallet context", () { + final walletContext = + V5R1ClientContext(chain: TonChain.mainnet, subwalletNumber: 0); + final context = beginCell() + .storeUint(1, 1) + .storeInt(walletContext.chain.workchain, 8) + .storeUint(0, 8) + .storeUint(walletContext.subwalletNumber, 15) + .endCell() + .beginParse() + .loadInt(32); + final actual = beginCell() + .storeInt( + BigInt.from(context) ^ BigInt.from(walletContext.chain.id), 32) + .endCell() + .beginParse(); + final load = VersionedWalletUtils.loadV5Context( + contextBytes: actual.loadBuffer(4), chain: walletContext.chain); + expect(load, walletContext); + }); +} + +void _serializeWalletCustomWalletContext() { + test("serialize custom wallet context", () { + const walletContext = V5R1CustomContext( + context: 239239239, + chain: TonChain.testnet, + ); + final context = beginCell() + .storeUint(0, 1) + .storeUint(walletContext.context, 31) + .endCell() + .beginParse() + .loadInt(32); + final actual = beginCell().store(walletContext).endCell(); + final expected = beginCell() + .storeInt( + BigInt.from(context) ^ BigInt.from(walletContext.chain.id), 32) + .endCell(); + expect(actual, expected); + expect(expected.toBase64(), "te6cckEBAQEABgAACPG9f7rC5HWN"); + }); +} + +void _deserializeWalletCustomWalletContext() { + test("deserialize custom wallet context", () { + const walletContext = + V5R1CustomContext(context: 239239239, chain: TonChain.testnet); + final context = beginCell() + .storeUint(0, 1) + .storeUint(walletContext.context, 31) + .endCell() + .beginParse() + .loadInt(32); + + final actual = beginCell() + .storeInt( + BigInt.from(context) ^ BigInt.from(walletContext.chain.id), 32) + .endCell() + .beginParse(); + final load = VersionedWalletUtils.loadV5Context( + contextBytes: actual.loadBuffer(4), chain: walletContext.chain); + expect(walletContext, load); + }); +} diff --git a/test/tests/dict/dict_test.dart b/test/tests/dict/dict_test.dart index 935d253..5a64e4a 100644 --- a/test/tests/dict/dict_test.dart +++ b/test/tests/dict/dict_test.dart @@ -39,7 +39,9 @@ void _test() { // Unpack final dict = Dictionary.loadDirect( - DictionaryKey.uintCodec(16), DictionaryValue.uintCodec(16), parse); + key: DictionaryKey.uintCodec(16), + value: DictionaryValue.uintCodec(16), + slice: parse); // return; expect(dict[13], 169); expect(dict[17], 289); @@ -135,7 +137,8 @@ void _test() { expect(testDict[testKey], testVal); final serialized = beginCell().storeDictDirect(testDict).endCell(); - final dictDs = Dictionary.loadDirect(keys, values, serialized.beginParse()); + final dictDs = Dictionary.loadDirect( + key: keys, value: values, slice: serialized.beginParse()); expect(dictDs[testKey], testVal); }); @@ -180,8 +183,10 @@ void _test() { test('should parse dictionary with empty values', () { final cell = Cell.fromBoc(BytesUtils.fromHexString( "b5ee9c72010101010024000043a0000000000000000000000000000000000000000000000000000000000000000f70"))[0]; - final testDict = Dictionary.loadDirect(DictionaryKey.bigUintCodec(256), - DictionaryValue.bitStringCodec(0), cell.beginParse()); + final testDict = Dictionary.loadDirect( + key: DictionaryKey.bigUintCodec(256), + value: DictionaryValue.bitStringCodec(0), + slice: cell.beginParse()); expect(testDict.keys.first, BigInt.from(123)); expect(testDict[BigInt.from(123)]?.length, 0); }); diff --git a/test/tests/metadata/metadata_test.dart b/test/tests/metadata/metadata_test.dart index 5707a47..4ab7b18 100644 --- a/test/tests/metadata/metadata_test.dart +++ b/test/tests/metadata/metadata_test.dart @@ -15,7 +15,7 @@ void _test() { expect(decode, isA()); expect((decode as JettonOffChainMetadata).uri, "https://ton.cx/address/EQD0vdSA_NedR9uvbgN9EikRX-suesDxGeFg69XQMavfLqIw"); - expect(decode.encode().toBase64(), cotent); + expect(decode.toContent().toBase64(), cotent); }); test("onChain", () { const cotent = @@ -27,7 +27,7 @@ void _test() { expect(jetton.name, "DERE"); expect(jetton.decimals, 4); expect(jetton.image, "https://postav.su/logo2.png"); - expect(decode.encode().toBase64(), cotent); + expect(decode.toContent().toBase64(), cotent); }); test("offchain", () { diff --git a/test/tests/models/jetton_minter_data_test.dart b/test/tests/models/jetton_minter_data_test.dart new file mode 100644 index 0000000..0168ea7 --- /dev/null +++ b/test/tests/models/jetton_minter_data_test.dart @@ -0,0 +1,56 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + group("Token data", () { + test("Mint", () { + const body = + "b5ee9c724101020100790001730000001500000000000000009ff4610d333687fab6317886baba916f1e908c55a7470e72bec9eb202058e52e2d082faf0800e071afd498d00010010073178d451900000000000000007038d7ea4c6800027ff59f4c0f3039140d56bb3bef5b17e9ca0118e2007174a0a6d36640edfa923c6da08f0d1802a38d780f"; + final decode = + JettonMinterOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, JettonMinterOperationType.mint); + final mint = decode.cast(); + final forwardAmount = TonHelper.toNano("0.3"); + final totalAmount = TonHelper.toNano("0.4"); + final jettonAmountForMint = BigInt.parse("1${"0" * 15}"); + + expect(mint.transfer.forwardTonAmount, forwardAmount); + expect(mint.totalTonAmount, totalAmount); + expect(mint.transfer.jettonAmount, jettonAmountForMint); + expect(decode.serialize().toHex(), body); + }); + test("Change content OFFChain", () { + const body = + "b5ee9c7241010201002f00011800000004000000000000000001003c0168747470733a2f2f6769746875622e636f6d2f6d72746e6574776f726bf9b4eb62"; + final decode = + JettonMinterOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, JettonMinterOperationType.changeContent); + final mint = decode.cast(); + + expect(mint.contentMetaData.type, TokenContentType.offchain); + final metadata = mint.contentMetaData.cast(); + expect(metadata.uri, "https://github.com/mrtnetwork"); + expect(decode.serialize().toHex(), body); + }); + test("Change content OnChain", () { + const body = + "b5ee9c724102100100015a00011800000004000000000000000001010300c00202012003050143bff082eb663b57a00192f4a6ac467288df2dfeddb9da1bee28f6521c8bebd21f1ec00400740068747470733a2f2f617661746172732e67697468756275736572636f6e74656e742e636f6d2f752f35363737393138323f733d393626763d34020120060b02012007090141bf4546a6ffe1b79cfdd86bad3db874313dcde2fb05e6a74aa7f3552d9617c79d13080018004d5254204e4554574f524b0141bf6ed4f942a7848ce2cb066b77a1128c6a1ff8c43f438a2dce24612ba9ffab8b030a0008004d52540201200c0e0141bf5208def46f5a1d4f9dce66ab309f4a851305f166f91ef79d923ef58e34f9a2090d004e0068747470733a2f2f6769746875622e636f6d2f6d72746e6574776f726b2f746f6e5f646172740141bf5d01fa5e3c06901c45046c6b2ddcea5af764fea0eed72a10d404f2312ceb247d0f00040039a21978ee"; + final decode = + JettonMinterOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, JettonMinterOperationType.changeContent); + final mint = decode.cast(); + + expect(mint.contentMetaData.type, TokenContentType.onchain); + final metadata = mint.contentMetaData.cast(); + expect(metadata.image, + "https://avatars.githubusercontent.com/u/56779182?s=96&v=4"); + expect(metadata.symbol, "MRT"); + expect(metadata.description, "https://github.com/mrtnetwork/ton_dart"); + expect(metadata.decimals, 9); + expect(metadata.name, "MRT NETWORK"); + expect(decode.serialize().toHex(), body); + }); + }); +} + +/// diff --git a/test/tests/models/jetton_wallet_test.dart b/test/tests/models/jetton_wallet_test.dart new file mode 100644 index 0000000..53ce158 --- /dev/null +++ b/test/tests/models/jetton_wallet_test.dart @@ -0,0 +1,18 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + group("Jetton wallet", () { + test("Transfer", () { + const body = + "b5ee9c724101010100570000aa0f8a7ea500000000000000005367b7ca4009ff8de0d1830272cc1610f05953efb58f63050e0b0d05563edda5b8fe57ff21bc293fe8c21a666d0ff56c62f10d757522de3d2118ab4e8e1ce57d93d64040b1ca5c5a006d52c616"; + final decode = + JettonWalletOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, JettonWalletOperationType.transfer); + final transfer = decode.cast(); + expect(transfer.amount, TonHelper.toNano("234")); + expect(transfer.forwardTonAmount, BigInt.zero); + expect(decode.serialize().toHex(), body); + }); + }); +} diff --git a/test/tests/models/nft_data_test.dart b/test/tests/models/nft_data_test.dart new file mode 100644 index 0000000..ee85983 --- /dev/null +++ b/test/tests/models/nft_data_test.dart @@ -0,0 +1,74 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + group("NFT body", () { + test("edit content", () { + const body = + "b5ee9c724101050100dd00021800000004000000000000000001040200020300b00168747470733a2f2f697066732e696f2f697066732f516d536b317145594e517a4e5853664437705852586a6571574d3974457133766d64424d3367616b4132374d794e3f66696c656e616d653d69706632332e6a736f6e008a68747470733a2f2f697066732e696f2f6970667333322f516d536b317145594e517a4e5853664437705852586a6571574d3974457133766d64424d3367616b4132374d794e004b000a03e89fedf42aa27b4f307df0a993d16f13df5c7e208f2a988bf683ed116fa98202a6c3905c229b88"; + final decode = + NFTCollectionOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, NFTCollectionOperationType.changeContent); + final changeContent = decode.cast(); + expect(changeContent.royaltyParams.address, + TonAddress("Uf9voVUT2nmD74VMnot4nvrj8QR5VMRftB9oi31MEBU2HKBJ")); + expect(changeContent.royaltyParams.royaltyFactor, 10); + expect(changeContent.royaltyParams.royaltyBase, 1000); + final metadata = changeContent.metadata.cast(); + expect(metadata.collectionBase, + "https://ipfs.io/ipfs32/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN"); + expect(metadata.collectionMetadataUri, + "https://ipfs.io/ipfs/QmSk1qEYNQzNXSfD7pXRXjeqWM9tEq3vmdBM3gakA27MyN?filename=ipf23.json"); + expect(decode.serialize().toHex(), body); + }); + test("mint", () { + const body = + "b5ee9c724101030100550001310000000100000000000000000000000000000000405f5e10080101439feaa7a02bc8ca97445291f8e9d6bf9d38798006431a3bec06776fed5ae44bdd9bb00200243f66696c656e616d653d6970662e6a736f6ea5dcee93"; + final decode = + NFTCollectionOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, NFTCollectionOperationType.mint); + final mint = decode.cast(); + expect(mint.mint.initAmount, BigInt.from(100000000)); + expect(mint.mint.ownerAddress, + TonAddress("Ef9VPQFeRlS6IpSPx061_OnDzAAyGNHfYDO7f2rXIl7s3a_i")); + expect(mint.mint.itemIndex, BigInt.zero); + expect(decode.serialize().toHex(), body); + }); + test("batch mint", () { + const body = + "b5ee9c7241021001000129000119000000020000000000000000c0010203cf60020d0201200306010a5405f5e1000401439feaa7a02bc8ca97445291f8e9d6bf9d38798006431a3bec06776fed5ae44bdd9bb00500263f66696c656e616d653d697066342e6a736f6e020120070a01091017d784020801439feaa7a02bc8ca97445291f8e9d6bf9d38798006431a3bec06776fed5ae44bdd9bb00900263f66696c656e616d653d697066332e6a736f6e01091017d784020b01439feaa7a02bc8ca97445291f8e9d6bf9d38798006431a3bec06776fed5ae44bdd9bb00c00263f66696c656e616d653d697066322e6a736f6e010bd202faf080400e01439feaa7a02bc8ca97445291f8e9d6bf9d38798006431a3bec06776fed5ae44bdd9bb00f00263f66696c656e616d653d697066312e6a736f6e15cdb08a"; + final decode = + NFTCollectionOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, NFTCollectionOperationType.batchMint); + final mint = decode.cast(); + expect(mint.nfts.length, 4); + for (final i in mint.nfts) { + expect(i.ownerAddress, + TonAddress("Ef9VPQFeRlS6IpSPx061_OnDzAAyGNHfYDO7f2rXIl7s3a_i")); + } + expect(decode.serialize().toHex(), body); + }); + test("change owner", () { + const body = + "te6cckEBAQEAMAAAWwAAAAMAAAAAAAAAAJ/7kMXko5pxtt1KlYWEiM4O6Y7LIpcDMOFLKZcmWfYVIJD/sb1u"; + final decode = NFTCollectionOperation.deserialize( + Cell.fromBase64(body).beginParse()); + expect(decode.type, NFTCollectionOperationType.changeOwner); + final changeOwner = decode.cast(); + expect(changeOwner.newOwnerAddress, + TonAddress("Uf_chi8lHNONtupUrCwkRnB3THZZFLgZhwpZTLkyz7CpBHwX")); + expect(decode.serialize().toBase64(), body); + }); + test("transfer item", () { + const body = + "b5ee9c7241010101003100005d5fcc3d1400000000000000009ffb90c5e4a39a71b6dd4a95858488ce0ee98ecb22970330e14b29972659f615208010e499cb22"; + final decode = + NFTItemOperation.deserialize(Cell.fromHex(body).beginParse()); + expect(decode.type, NFTItemOperationType.transfer); + final changeOwner = decode.cast(); + expect(changeOwner.newOwnerAddress, + TonAddress("Uf_chi8lHNONtupUrCwkRnB3THZZFLgZhwpZTLkyz7CpBHwX")); + expect(decode.serialize().toHex(), body); + }); + }); +} diff --git a/test/tests/operations/jetton_test.dart b/test/tests/operations/jetton_test.dart new file mode 100644 index 0000000..cdb10ae --- /dev/null +++ b/test/tests/operations/jetton_test.dart @@ -0,0 +1,83 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + _mint(); +} + +/// te6cckEBAQEAFAAAI1lfB7wAAAAAAAAAAFTYhYwgAQaKm+g= +void _mint() { + test("Burn", () { + final body = + Cell.fromBase64("te6cckEBAQEAFAAAI1lfB7wAAAAAAAAAAFTYhYwgAQaKm+g="); + final deserializeOperation = + JettonWalletOperation.deserialize(body.beginParse()); + final operation = + JettonWalletOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, JettonWalletOperationType.burn); + final mint = operation.cast(); + expect(mint.burnAmount, TonHelper.toNano("333")); + }); + test("Transfer", () { + final body = Cell.fromBase64( + "te6cckEBAQEANQAAZQ+KfqUAAAAAAAAAAEstBeAJ/3kLd3xG5gu8XU9A1ha2yyrilzi3s1NfAAcsUGoIYs2wAZVfpzA="); + final deserializeOperation = + JettonWalletOperation.deserialize(body.beginParse()); + final operation = + JettonWalletOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, JettonWalletOperationType.transfer); + final mint = operation.cast(); + expect(mint.destination, + TonAddress("kf-8hbu-I3MF3i6noGsLW2WVcUucW9mpr4ADlig1BDFm2HQz")); + expect(mint.amount, TonHelper.toNano("3")); + expect(mint.forwardTonAmount, BigInt.zero); + }); + test("Mint", () { + final body = Cell.fromBase64( + "te6cckEBAgEAVQABcQAAABUAAAAAAAAAAJ/meF1TIFWCIICf/RbdYtFYCDZV1dhbGEPQD81v5MreL4g7msoAy15iD0gAEAEALReNRRkAAAAAAAAAAGWvMQekAAA5iWgE93fVgg=="); + final deserializeOperation = + JettonMinterOperation.deserialize(body.beginParse()); + final operation = + JettonMinterOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, JettonMinterOperationType.mint); + final mint = operation.cast(); + expect(mint.to, + TonAddress("kf8zwuqZAqwRBAT_6LbrForAQbKursLYwh6Afmt_JlbxfGJm")); + expect(mint.totalTonAmount, TonHelper.toNano("0.5")); + expect(mint.jettonAmount, TonHelper.toNano("100000")); + expect(mint.transfer.forwardTonAmount, TonHelper.toNano("0.01")); + }); + test("ChangeContent", () { + final body = Cell.fromHex( + "b5ee9c724102100100015a00011800000004000000000000000001010300c00202012003050143bff082eb663b57a00192f4a6ac467288df2dfeddb9da1bee28f6521c8bebd21f1ec00400740068747470733a2f2f617661746172732e67697468756275736572636f6e74656e742e636f6d2f752f35363737393138323f733d393626763d34020120060b02012007090141bf4546a6ffe1b79cfdd86bad3db874313dcde2fb05e6a74aa7f3552d9617c79d13080016004d5254204a4554544f4e0141bf6ed4f942a7848ce2cb066b77a1128c6a1ff8c43f438a2dce24612ba9ffab8b030a0008004d52540201200c0e0141bf5208def46f5a1d4f9dce66ab309f4a851305f166f91ef79d923ef58e34f9a2090d004e0068747470733a2f2f6769746875622e636f6d2f6d72746e6574776f726b2f746f6e5f646172740141bf5d01fa5e3c06901c45046c6b2ddcea5af764fea0eed72a10d404f2312ceb247d0f000600313204d8a8f0"); + final deserializeOperation = + JettonMinterOperation.deserialize(body.beginParse()); + final operation = + JettonMinterOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, JettonMinterOperationType.changeContent); + final changeContent = operation.cast(); + expect(changeContent.contentMetaData.type, TokenContentType.onchain); + final metadata = + changeContent.contentMetaData.cast(); + expect(metadata.image, + "https://avatars.githubusercontent.com/u/56779182?s=96&v=4"); + expect(metadata.name, "MRT JETTON"); + expect(metadata.decimals, 12); + expect(metadata.description, "https://github.com/mrtnetwork/ton_dart"); + expect(metadata.symbol, "MRT"); + }); + test("ChangeOwner", () { + final body = Cell.fromHex( + "b5ee9c7241010101003000005b0000000300000000000000009ff790b777c46e60bbc5d4f40d616b6cb2ae29738b7b3535f00072c506a0862cdb10e94d71d9"); + final deserializeOperation = + JettonMinterOperation.deserialize(body.beginParse()); + final operation = + JettonMinterOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, JettonMinterOperationType.changeAdmin); + final changeOwner = operation.cast(); + expect(changeOwner.newOwner, + TonAddress("kf-8hbu-I3MF3i6noGsLW2WVcUucW9mpr4ADlig1BDFm2HQz")); + }); +} + +/// b5ee9c7241010101003000005b0000000300000000000000009fe6785d5320558220809ffd16dd62d158083655d5d85b1843d00fcd6fe4cade2f90f7aa6ca5 diff --git a/test/tests/operations/nft_test.dart b/test/tests/operations/nft_test.dart new file mode 100644 index 0000000..abd6263 --- /dev/null +++ b/test/tests/operations/nft_test.dart @@ -0,0 +1,93 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + _collection(); + _item(); +} + +/// b5ee9c724101010100550000a55fcc3d1400000000000000009ff790b777c46e60bbc5d4f40d616b6cb2ae29738b7b3535f00072c506a0862cdb13fccf0baa640ab0441013ffa2dbac5a2b0106cababb0b63087a01f9adfc995bc5f07312d008e6f0dfe0 +void _item() { + test("transfer nft", () { + final body = Cell.fromHex( + "b5ee9c7241010101003100005d5fcc3d1400000000000000009ff790b777c46e60bbc5d4f40d616b6cb2ae29738b7b3535f00072c506a0862cdb00109f758314"); + final deserializeOperation = + NFTItemOperation.deserialize(body.beginParse()); + final operation = NFTItemOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, NFTItemOperationType.transfer); + final mint = operation.cast(); + expect(mint.newOwnerAddress, + TonAddress("kf-8hbu-I3MF3i6noGsLW2WVcUucW9mpr4ADlig1BDFm2HQz")); + }); +} + +/// b5ee9c7241010101003100005d5fcc3d1400000000000000009ff790b777c46e60bbc5d4f40d616b6cb2ae29738b7b3535f00072c506a0862cdb00109f758314 +void _collection() { + test("mint", () { + final body = Cell.fromBase64( + "te6cckEBAwEAVwABMQAAAAEAAAAAAAAAAAAAAAAAAAAAQdzWUAgBAUOf5nhdUyBVgiCAn/0W3WLRWAg2VdXYWxhD0A/Nb+TK3i+QAgAoLz9maWxlbmFtZT1uZnQxLmpzb24j2HoI"); + final deserializeOperation = + NFTCollectionOperation.deserialize(body.beginParse()); + final operation = + NFTCollectionOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, NFTCollectionOperationType.mint); + final mint = operation.cast(); + expect(mint.mint.itemIndex, BigInt.zero); + expect(mint.mint.initAmount, TonHelper.toNano("0.5")); + expect(mint.mint.ownerAddress, + TonAddress("kf8zwuqZAqwRBAT_6LbrForAQbKursLYwh6Afmt_JlbxfGJm")); + final metadata = mint.mint.metadata.cast(); + expect(metadata.uri, "/?filename=nft1.json"); + }); + test("mint2", () { + final body = Cell.fromBase64( + "te6cckEBAwEAVwABMQAAAAEAAAAAAAAAAAAAAAAAAAABQL68IAgBAUOf5nhdUyBVgiCAn/0W3WLRWAg2VdXYWxhD0A/Nb+TK3i+QAgAoLz9maWxlbmFtZT1uZnQyLmpzb26MmMBk"); + final deserializeOperation = + NFTCollectionOperation.deserialize(body.beginParse()); + final operation = + NFTCollectionOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, NFTCollectionOperationType.mint); + final mint = operation.cast(); + expect(mint.mint.itemIndex, BigInt.one); + expect(mint.mint.initAmount, TonHelper.toNano("0.2")); + expect(mint.mint.ownerAddress, + TonAddress("kf8zwuqZAqwRBAT_6LbrForAQbKursLYwh6Afmt_JlbxfGJm")); + final metadata = mint.mint.metadata.cast(); + expect(metadata.uri, "/?filename=nft2.json"); + }); + test("batch mint", () { + final body = Cell.fromBase64( + "te6cckEBDAEA5QABGQAAAAIAAAAAAAAAAMABAgPPYAIJAgFYAwYBCRAvrwgCBAFDn+Z4XVMgVYIggJ/9Ft1i0VgINlXV2FsYQ9APzW/kyt4vkAUAKC8/ZmlsZW5hbWU9bmZ0Mi5qc29uAQkQL68IAgcBQ5/meF1TIFWCIICf/RbdYtFYCDZV1dhbGEPQD81v5MreL5AIACgvP2ZpbGVuYW1lPW5mdDMuanNvbgEL0gX14QBACgFDn+Z4XVMgVYIggJ/9Ft1i0VgINlXV2FsYQ9APzW/kyt4vkAsAKC8/ZmlsZW5hbWU9bmZ0NC5qc29uHm0KPQ=="); + final deserializeOperation = + NFTCollectionOperation.deserialize(body.beginParse()); + final operation = + NFTCollectionOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, NFTCollectionOperationType.batchMint); + final mint = operation.cast(); + expect(mint.nfts.length, 3); + for (int i = 0; i < mint.nfts.length; i++) { + final nft = mint.nfts[i]; + final itemIndex = BigInt.two + BigInt.from(i); + expect(nft.itemIndex, itemIndex); + expect(nft.initAmount, TonHelper.toNano("0.2")); + expect(nft.ownerAddress, + TonAddress("kf8zwuqZAqwRBAT_6LbrForAQbKursLYwh6Afmt_JlbxfGJm")); + final metadata = nft.metadata.cast(); + expect(metadata.uri, "/?filename=nft$itemIndex.json"); + } + }); + test("change owner", () { + final body = Cell.fromBase64( + "te6cckEBAQEAMAAAWwAAAAMAAAAAAAAAAJ/3kLd3xG5gu8XU9A1ha2yyrilzi3s1NfAAcsUGoIYs2xDpTXHZ"); + final deserializeOperation = + NFTCollectionOperation.deserialize(body.beginParse()); + final operation = + NFTCollectionOperation.fromJson(deserializeOperation.toJson()); + expect(operation.type, NFTCollectionOperationType.changeOwner); + final changeOwner = operation.cast(); + expect(changeOwner.newOwnerAddress, + TonAddress("kf-8hbu-I3MF3i6noGsLW2WVcUucW9mpr4ADlig1BDFm2HQz")); + }); +} + +/// te6cckEBDAEA5QABGQAAAAIAAAAAAAAAAMABAgPPYAIJAgFYAwYBCRAvrwgCBAFDn+Z4XVMgVYIggJ/9Ft1i0VgINlXV2FsYQ9APzW/kyt4vkAUAKC8/ZmlsZW5hbWU9bmZ0Mi5qc29uAQkQL68IAgcBQ5/meF1TIFWCIICf/RbdYtFYCDZV1dhbGEPQD81v5MreL5AIACgvP2ZpbGVuYW1lPW5mdDMuanNvbgEL0gX14QBACgFDn+Z4XVMgVYIggJ/9Ft1i0VgINlXV2FsYQ9APzW/kyt4vkAsAKC8/ZmlsZW5hbWU9bmZ0NC5qc29uHm0KPQ== diff --git a/test/tests/operations/stable_token_test.dart b/test/tests/operations/stable_token_test.dart new file mode 100644 index 0000000..eb691b0 --- /dev/null +++ b/test/tests/operations/stable_token_test.dart @@ -0,0 +1,63 @@ +import 'package:test/test.dart'; +import 'package:ton_dart/ton_dart.dart'; + +void main() { + _mint(); +} + +void _mint() { + test("mint", () { + final Cell cell = Cell.fromBase64( + "te6cckEBAgEAbgABY2QrfQcAAAAAAAAAAIAOg31rlDkfhXhgiiol6SbgW7dPz5sztQKZnz8lclUPsah3NZQBAQBtF41FGQAAAAAAAAAAgDWVoaDoC+AIAOg31rlDkfhXhgiiol6SbgW7dPz5sztQKZnz8lclUPsaAlN4lsk="); + final operation = + StableJettonMinterOperation.deserialize(cell.beginParse()); + final fromJson = StableJettonMinterOperation.fromJson(operation.toJson()); + expect(fromJson.type, StableJettonMinterOperationType.mint); + final mint = fromJson as StableJettonMinterMint; + expect(mint.totalTonAmount, TonHelper.toNano("1")); + expect(mint.transfer.jettonAmount, TonHelper.toNano("241323123")); + expect(mint.transfer.forwardTonAmount, BigInt.zero); + }); + test("mint", () { + final Cell cell = Cell.fromHex( + "b5ee9c7241010201004f000163642b7d070000000000000000800e837d6b94391f8578608a2a25e926e05bb74fcf9b33b502999f3f2572550fb1a82faf080101002f178d4519000000000000000060a1afb3546000405f5e10046f7b6a90"); + final operation = + StableJettonMinterOperation.deserialize(cell.beginParse()); + final fromJson = StableJettonMinterOperation.fromJson(operation.toJson()); + expect(fromJson.type, StableJettonMinterOperationType.mint); + final mint = fromJson as StableJettonMinterMint; + expect(mint.totalTonAmount, TonHelper.toNano("0.4")); + expect(mint.transfer.jettonAmount, TonHelper.toNano("11111")); + expect(mint.transfer.forwardTonAmount, TonHelper.toNano("0.1")); + }); + + test("burn wallet token from minter", () { + final Cell cell = Cell.fromHex( + "b5ee9c72410102010049000163235caf520000000000000000800e837d6b94391f8578608a2a25e926e05bb74fcf9b33b502999f3f2572550fb1a80bebc201010023595f07bc0000000000000000519d81d96001f12d17af"); + final operation = + StableJettonMinterOperation.deserialize(cell.beginParse()); + final fromJson = StableJettonMinterOperation.fromJson(operation.toJson()); + expect(fromJson.type, StableJettonMinterOperationType.callTo); + final callTo = fromJson.cast(); + expect(callTo.address, + TonAddress("EQB0G-tcocj8K8MEUVEvSTcC3bp-fNmdqBTM-fkrkqh9jeck")); + expect(callTo.operation.type, StableJettonWalletOperationType.burn); + final callToOperation = callTo.operation.cast(); + expect(callToOperation.jettonAmount, TonHelper.toNano("111")); + }); + test("transfer", () { + final Cell cell = Cell.fromBase64( + "te6cckEBAQEAOgAAbw+KfqUAAAAAAAAAAFF0h26ACAH3D7O6FBGij0TZ1H5KbV0KPhICZx2dzw6m01NYsNKRyBAX14QB1Igwsw=="); + final operation = + StableJettonWalletOperation.deserialize(cell.beginParse()); + final fromJson = StableJettonWalletOperation.fromJson(operation.toJson()); + expect(fromJson.type, StableJettonWalletOperationType.transfer); + final callTo = fromJson.cast(); + expect(callTo.to, + TonAddress("EQD7h9ndCgjRR6Js6j8lNq6FHwkBM47O54dTaamsWGlI5CMv")); + expect(callTo.jettonAmount, TonHelper.toNano("100")); + expect(callTo.forwardTonAmount, TonHelper.toNano("0.1")); + }); +} + +/// te6cckEBAQEAOgAAbw+KfqUAAAAAAAAAAFF0h26ACAH3D7O6FBGij0TZ1H5KbV0KPhICZx2dzw6m01NYsNKRyBAX14QB1Igwsw==