From 2ca49549e5a47446b3e18e63bf4fd56536045638 Mon Sep 17 00:00:00 2001 From: sansx Date: Wed, 13 Nov 2024 14:08:51 +0800 Subject: [PATCH] docs: Migrate mandarin docs (#849) * deflation_update_outdated_info.md * Adding Hacken and HP to security providers list * fix_caution * ecosystem_auditors_fix * Fix wallet-list-2.json redirect (#843) * docs(i18n): migrate Mandarin documentation for concepts dir * docs(i18n): migrate Mandarin documentation for contribute dir * docs(i18n): migrate Mandarin documentation for documentation dir * docs(i18n): migrate Mandarin documentation for guidelines dir * docs(i18n): migrate Mandarin documentation for guidelines/smart-contracts dir --------- Co-authored-by: AlexG <39581753+reveloper@users.noreply.github.com> Co-authored-by: dorsky <139157644+dorsky@users.noreply.github.com> Co-authored-by: Sherbek <91262758+SherbekMavlonov@users.noreply.github.com> --- .../current/v3/concepts/academy-overview.mdx | 13 + .../concepts/dive-into-ton/introduction.mdx | 48 + .../blockchain-of-blockchains.md | 72 + .../ton-blockchain/cells-as-data-storage.md | 50 + .../ton-blockchain/ton-networking.md | 11 + .../ton-ecosystem/explorers-in-ton.mdx | 155 + .../dive-into-ton/ton-ecosystem/nft.md | 80 + .../ton-ecosystem/wallet-apps.mdx | 245 ++ .../v3/concepts/educational-resources.mdx | 13 + .../current/v3/concepts/glossary.md | 509 +++ .../v3/concepts/qa-outsource/auditors.mdx | 30 + .../v3/concepts/qa-outsource/outsource.mdx | 165 + .../current/v3/contribute/README.md | 48 + .../v3/contribute/contribution-rules.md | 44 + .../current/v3/contribute/docs/guidelines.md | 24 + .../v3/contribute/docs/schemes-guidelines.mdx | 87 + .../localization-program/how-it-works.md | 152 + .../localization-program/how-to-contribute.md | 124 + .../localization-program/overview.md | 39 + .../translation-style-guide.md | 198 ++ .../current/v3/contribute/maintainers.md | 51 + .../current/v3/contribute/participate.md | 49 + .../v3/contribute/tutorials/guidelines.md | 135 + .../principles-of-a-good-tutorial.md | 27 + .../contribute/tutorials/sample-tutorial.md | 129 + .../v3/documentation/archive/compile.md | 201 ++ .../archive/hacktoberfest-2022/README.mdx | 60 + .../hacktoberfest-2022/as-contributor.md | 33 + .../hacktoberfest-2022/as-maintainer.md | 39 + .../v3/documentation/archive/mining.md | 199 ++ .../v3/documentation/archive/pow-givers.md | 199 ++ .../archive/precompiled-binaries.md | 158 + .../archive/tg-bot-integration-py.md | 577 ++++ .../archive/tg-bot-integration.mdx | 2061 +++++++++++++ .../v3/documentation/dapps/defi/coins.md | 29 + .../documentation/dapps/defi/subscriptions.md | 38 + .../v3/documentation/dapps/defi/tokens.mdx | 115 + .../documentation/dapps/defi/ton-payments.md | 38 + .../dapps/oracles/about_blockchain_oracles.md | 61 + .../v3/documentation/data-formats/tl.md | 45 + .../data-formats/tlb/block-layout.md | 238 ++ .../tlb/canonical-cell-serialization.md | 47 + .../data-formats/tlb/cell-boc.mdx | 300 ++ .../documentation/data-formats/tlb/crc32.md | 60 + .../data-formats/tlb/exotic-cells.md | 137 + .../data-formats/tlb/msg-tlb.mdx | 371 +++ .../documentation/data-formats/tlb/proofs.mdx | 908 ++++++ .../data-formats/tlb/tl-b-language.mdx | 605 ++++ .../data-formats/tlb/tl-b-types.mdx | 353 +++ .../documentation/data-formats/tlb/tlb-ide.md | 17 + .../data-formats/tlb/transaction-layout.md | 236 ++ .../current/v3/documentation/faq.md | 288 +- .../infra/crosschain/bridge-addresses.md | 213 ++ .../infra/crosschain/overview.md | 57 + .../v3/documentation/infra/minter-flow.md | 57 + .../documentation/infra/nodes/node-types.md | 79 + .../infra/nodes/validation/collators.md | 125 + .../nodes/validation/staking-incentives.md | 92 + .../network/configs/blockchain-configs.md | 554 ++++ .../network/configs/config-params.md | 320 ++ .../network/configs/network-configs.md | 11 + .../network/protocols/adnl/adnl-tcp.md | 571 ++++ .../network/protocols/adnl/adnl-udp.md | 352 +++ .../network/protocols/adnl/low-level-adnl.md | 110 + .../network/protocols/adnl/overview.md | 38 + .../network/protocols/dht/dht-deep-dive.md | 181 ++ .../network/protocols/dht/ton-dht.md | 52 + .../network/protocols/overlay.md | 75 + .../documentation/network/protocols/rldp.md | 183 ++ .../documentation/smart-contracts/README.mdx | 160 + .../smart-contracts/addresses.md | 175 ++ .../contracts-specs/examples.md | 164 + .../contracts-specs/governance.md | 82 + .../contracts-specs/vesting-contract.mdx | 156 + .../fift/fift-and-tvm-assembly.md | 129 + .../smart-contracts/fift/fift-deep-dive.md | 136 + .../smart-contracts/fift/overview.mdx | 49 + .../smart-contracts/func/changelog.md | 61 + .../smart-contracts/func/cookbook.md | 1495 +++++++++ .../smart-contracts/func/docs/builtins.md | 31 + .../smart-contracts/func/docs/comments.md | 32 + .../func/docs/compiler_directives.md | 90 + .../smart-contracts/func/docs/functions.md | 384 +++ .../func/docs/global_variables.md | 73 + .../func/docs/literals_identifiers.md | 104 + .../smart-contracts/func/docs/statements.md | 437 +++ .../smart-contracts/func/docs/stdlib.mdx | 942 ++++++ .../smart-contracts/func/docs/types.md | 86 + .../smart-contracts/func/libraries.md | 17 + .../smart-contracts/func/overview.mdx | 141 + .../getting-started/ide-plugins.md | 31 + .../getting-started/javascript.mdx | 58 + .../getting-started/testnet.md | 27 + .../smart-contracts/guidelines.mdx | 46 + .../message-management/external-messages.md | 29 + .../message-management/internal-messages.md | 107 + .../messages-and-transactions.mdx | 158 + .../non-bounceable-messages.md | 25 + .../message-management/sending-messages.md | 208 ++ .../shards/infinity-sharding-paradigm.mdx | 68 + .../smart-contracts/shards/shards-intro.mdx | 33 + .../accept-message-effects.md | 27 + .../transaction-fees/fees-low-level.md | 176 ++ .../smart-contracts/transaction-fees/fees.md | 178 ++ .../transaction-fees/forward-fees.md | 13 + .../v3/documentation/ton-documentation.mdx | 142 + .../tvm/changelog/tvm-upgrade-2023-07.md | 279 ++ .../v3/documentation/tvm/tvm-exit-codes.md | 43 + .../documentation/tvm/tvm-initialization.md | 188 ++ .../v3/documentation/tvm/tvm-overview.mdx | 131 + .../v3/documentation/whitepapers/overview.md | 35 + .../current/v3/guidelines/dapps/README.mdx | 133 + .../guidelines/dapps/apis-sdks/api-types.md | 82 + .../v3/guidelines/dapps/apis-sdks/sdk.mdx | 77 + .../dapps/apis-sdks/ton-adnl-apis.md | 47 + .../dapps/apis-sdks/ton-http-apis.md | 52 + .../dapps/asset-processing/README.mdx | 206 ++ .../dapps/asset-processing/jettons.mdx | 605 ++++ .../nft-processing/metadata-parsing.md | 220 ++ .../asset-processing/nft-processing/nfts.md | 271 ++ .../current/v3/guidelines/dapps/cookbook.md | 1582 ++++++++++ .../v3/guidelines/dapps/tma/README.mdx | 91 + .../v3/guidelines/dapps/tma/grants.mdx | 17 + .../dapps/tma/guidelines/monetization.mdx | 66 + .../dapps/tma/guidelines/publishing.mdx | 114 + .../dapps/tma/guidelines/testing-apps.mdx | 92 + .../dapps/tma/guidelines/tips-and-tricks.mdx | 54 + .../dapps/tma/tutorials/app-examples.mdx | 310 ++ .../dapps/tma/tutorials/design-guidelines.mdx | 64 + .../tma/tutorials/step-by-step-guide.mdx | 63 + .../dapps/tutorials/mint-your-first-token.md | 214 ++ .../dapps/tutorials/nft-minting-guide.md | 1121 +++++++ .../accept-payments-in-a-telegram-bot-2.md | 540 ++++ .../accept-payments-in-a-telegram-bot-js.md | 481 +++ .../accept-payments-in-a-telegram-bot.md | 865 ++++++ .../dapps/tutorials/web3-game-example.md | 304 ++ .../dapps/tutorials/zero-knowledge-proofs.md | 630 ++++ .../nodes/node-maintenance-and-security.md | 217 ++ .../v3/guidelines/nodes/persistent-states.md | 24 + .../nodes/running-nodes/archive-node.md | 225 ++ .../nodes/running-nodes/full-node.mdx | 676 ++++ .../nodes/running-nodes/liteserver.mdx | 7 + .../running-nodes/running-a-local-ton.md | 14 + .../staking-with-nominator-pools.md | 45 + .../guidelines/smart-contracts/get-methods.md | 371 +++ .../guidelines/smart-contracts/guidelines.mdx | 46 + .../howto/compile/compilation-instructions.md | 232 ++ .../howto/compile/instructions-low-memory.md | 50 + .../smart-contracts/howto/multisig-js.md | 197 ++ .../smart-contracts/howto/multisig.md | 270 ++ .../howto/single-nominator-pool.mdx | 184 ++ .../smart-contracts/howto/wallet.md | 2722 +++++++++++++++++ .../smart-contracts/security/overview.mdx | 26 + .../security/random-number-generation.md | 106 + .../smart-contracts/security/random.md | 76 + .../security/ton-hack-challenge-1.md | 157 + .../smart-contracts/testing/overview.mdx | 141 + .../testing/writing-test-examples.mdx | 572 ++++ .../business/ton-connect-comparison.md | 19 + .../business/ton-connect-for-business.md | 46 + .../business/ton-connect-for-security.md | 11 + .../ton-connect/frameworks/react.mdx | 332 ++ .../guidelines/creating-manifest.md | 40 + .../ton-connect/guidelines/developers.md | 162 + .../guidelines/how-ton-connect-works.mdx | 31 + .../integration-with-javascript-sdk.md | 506 +++ .../guidelines/preparing-messages.mdx | 1177 +++++++ .../guidelines/sending-messages.md | 185 ++ .../guidelines/verifying-signed-in-users.mdx | 213 ++ .../v3/guidelines/ton-connect/overview.mdx | 101 + .../v3/guidelines/ton-connect/wallet.mdx | 19 + .../current/v3/guidelines/web3/overview.mdx | 17 + .../current/v3/guidelines/web3/ton-dns/dns.md | 45 + .../guidelines/web3/ton-dns/subresolvers.md | 445 +++ .../ton-proxy-sites/connect-with-ton-proxy.md | 71 + .../how-to-open-any-ton-site.md | 47 + .../ton-proxy-sites/how-to-run-ton-site.md | 63 + .../running-your-own-ton-proxy.md | 204 ++ .../site-and-domain-management.md | 49 + .../web3/ton-storage/storage-daemon.md | 159 + .../web3/ton-storage/storage-faq.md | 55 + .../web3/ton-storage/storage-provider.md | 150 + 182 files changed, 38565 insertions(+), 168 deletions(-) create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/academy-overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/introduction.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/cells-as-data-storage.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/ton-networking.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/explorers-in-ton.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/nft.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/wallet-apps.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/educational-resources.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/glossary.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/auditors.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/outsource.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/README.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/contribution-rules.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/guidelines.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/schemes-guidelines.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-it-works.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-to-contribute.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/overview.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/translation-style-guide.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/maintainers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/participate.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/guidelines.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/principles-of-a-good-tutorial.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/sample-tutorial.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/compile.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/README.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-contributor.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-maintainer.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/mining.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/pow-givers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/precompiled-binaries.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration-py.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/coins.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/subscriptions.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/tokens.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/ton-payments.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/oracles/about_blockchain_oracles.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tl.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/block-layout.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/canonical-cell-serialization.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/cell-boc.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/crc32.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/exotic-cells.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/msg-tlb.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/proofs.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-language.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-types.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tlb-ide.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/transaction-layout.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/bridge-addresses.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/overview.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/minter-flow.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/node-types.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/collators.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/staking-incentives.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/blockchain-configs.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/config-params.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/network-configs.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-tcp.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-udp.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/low-level-adnl.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/overview.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/dht-deep-dive.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/ton-dht.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/overlay.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/rldp.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/README.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/addresses.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/examples.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/governance.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/vesting-contract.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-and-tvm-assembly.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-deep-dive.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/changelog.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/cookbook.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/builtins.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/comments.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/compiler_directives.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/functions.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/global_variables.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/literals_identifiers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/statements.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/stdlib.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/types.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/libraries.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/ide-plugins.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/javascript.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/testnet.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/guidelines.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/external-messages.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/internal-messages.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/messages-and-transactions.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/non-bounceable-messages.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/sending-messages.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/infinity-sharding-paradigm.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/shards-intro.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/accept-message-effects.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees-low-level.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/forward-fees.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/ton-documentation.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/changelog/tvm-upgrade-2023-07.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-exit-codes.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-initialization.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/whitepapers/overview.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/README.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/api-types.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/sdk.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-adnl-apis.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-http-apis.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/README.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/jettons.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/metadata-parsing.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/nfts.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/cookbook.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/README.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/grants.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/monetization.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/publishing.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/testing-apps.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/tips-and-tricks.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/app-examples.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/design-guidelines.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/step-by-step-guide.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/mint-your-first-token.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/nft-minting-guide.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-2.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/web3-game-example.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/zero-knowledge-proofs.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/node-maintenance-and-security.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/persistent-states.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/archive-node.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/full-node.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/liteserver.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/running-a-local-ton.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/staking-with-nominator-pools.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/get-methods.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/guidelines.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/compilation-instructions.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/instructions-low-memory.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig-js.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/single-nominator-pool.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/wallet.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random-number-generation.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/ton-hack-challenge-1.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/writing-test-examples.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-comparison.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-business.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-security.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/frameworks/react.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/creating-manifest.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/developers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/how-ton-connect-works.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/integration-with-javascript-sdk.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/preparing-messages.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/sending-messages.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/verifying-signed-in-users.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/wallet.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/overview.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/dns.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/subresolvers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/connect-with-ton-proxy.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-open-any-ton-site.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-run-ton-site.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/running-your-own-ton-proxy.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/site-and-domain-management.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-daemon.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-faq.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-provider.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/academy-overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/academy-overview.mdx new file mode 100644 index 0000000000..b8194ce1ce --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/academy-overview.mdx @@ -0,0 +1,13 @@ +# 教育资源 + +## TON 区块链课程 + +:::danger +页面正在开发中. +::: + +## 参见 + +- [TON 速成](https://tonspeedrun.com/) +- [TON Hello World](https://tonhelloworld.com/01-wallet/) +- [[YouTube]TON 开发研究 EN ](https://www.youtube.com/@TONDevStudy)[[RU]](https://www.youtube.com/results?search_query=tondevstudy) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/introduction.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/introduction.mdx new file mode 100644 index 0000000000..6733e6f083 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/introduction.mdx @@ -0,0 +1,48 @@ +import Player from '@site/src/components/player' +import Button from '@site/src/components/button' + +# 开放网络 + +**开放网络 The Open Network(TON)** 是一个由多个组成部分构成的去中心化和开放的互联网平台。这些组成部分包括:TON 区块链、TON DNS、TON 存储和 TON 网站。TON 区块链是连接 TON 底层基础设施的核心协议,从而形成更大的 TON 生态系统。 + +TON 致力于实现广泛的跨链互操作性,同时在高度可扩展的安全框架内运行。TON 的设计用于处理每秒数百万笔交易(TPS),目标是最终达到数亿用户的采用。 + +**TON 区块链** 被设计为一个分布式超级计算机,或称为“超级服务器”,旨在提供各种产品和服务,以助力新互联网的去中心化愿景的发展。 + +- 通过查看此部分了解 TON 为其用户提供的服务:[参与 TON](/participate/) +- 通过查看此部分了解 TON 区块链的技术方面 [区块链的区块链](/learn/overviews/ton-blockchain) +- 通过查看此部分了解有关 TON 的所有事务的发展:[开始使用](/develop/overview) + +## 概览 + +要理解去中心化互联网的真正愿景以及 TON 如何对此不可避免地做出贡献,请深入学习下面的视频: + + + +## TON 区块链课程 + +我们自豪地推出 **TON 区块链课程**,这是一个关于 TON 区块链的全面指南。该课程是为想要学习如何在 TON 区块链上创建智能合约和去中心化应用的开发者设计的。 + +它包含 **9个模块**,涵盖了 TON 区块链的基础知识、FunC 编程语言和 TON 虚拟机 (TVM)。 + + + +## 区块链新手? + +如果你是区块链的新手,不了解这项技术为何如此革命性 — 考虑深入了解这些重要内容: + +- [什么是区块链?什么是智能合约?什么是 Gas?](https://blog.ton.org/what_is_blockchain) +- [在荒岛上,区块链如何帮助你](https://talkol.medium.com/why-decentralized-consensus-blockchain-is-good-for-business-5ff263468210) +- [\[YouTube\] 加密网络及其重要性](https://youtu.be/2wxtiNgXBaU) + +## TON 与以太坊的关系 + +对于熟悉以太坊开发的人,我们撰写了两篇介绍性文章,帮助你了解 TON 的独特之处: + +- [TON 区块链的六个独特之处,会让 Solidity 开发者感到惊讶](https://blog.ton.org/six-unique-aspects-of-ton-blockchain-that-will-surprise-solidity-developers) +- [尝试新事物的时候到了:异步智能合约](https://telegra.ph/Its-time-to-try-something-new-Asynchronous-smart-contracts-03-25) +- [区块链之间的比较](https://ton.org/comparison_of_blockchains.pdf) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains.md new file mode 100644 index 0000000000..4fa8905987 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains.md @@ -0,0 +1,72 @@ +# 区块链之链 + +:::tip +本文档中,“**智能合约**”、“**账户**”和“**Actor**”这几个术语可互换使用,用以描述区块链实体。 +::: + +## 单一Actor + +让我们考虑一个智能合约。 + +在TON中,它是一个拥有`地址`、`代码`、`数据`、`余额`等属性的_事物_。换句话说,它是一个拥有一定_存储_和_行为_的对象。 +行为会遵循以下模式: + +- 发生某事(最常见的情况是合约收到一条消息) +- 合约根据自身属性通过在TON虚拟机中执行其`代码`来处理该事件。 +- 合约修改自身属性(`代码`、`数据`等) +- 合约可选地生成传出消息 +- 合约进入待机模式,直到下一个事件发生 + +这些步骤的组合被称为一次**交易**。重要的是,事件是依次处理的,因此_交易_是严格有序的,不能相互打断。 + +这种行为模式众所周知,被称为“Actor”。 + +### 最低层级:账户链 + +一系列的_交易_ `Tx1 -> Tx2 -> Tx3 -> ....` 可以被称为一条**链**。在这个例子下,它被称为**账户链 (AccountChain)**,以强调这是单个账户的_交易链_。 + +现在,由于处理交易的节点时不时需要协调智能合约的状态(达成关于状态的_共识_),这些_交易_被批量处理: +`[Tx1 -> Tx2] -> [Tx3 -> Tx4 -> Tx5] -> [] -> [Tx6]`。 +批处理不干预排序,每个交易仍然只有一个“前一交易”和至多一个“下一交易”,但现在这个序列被切割成了**区块**。 + +将传入和传出消息的队列也包含在_区块_中是有益的。在这样的情况下,一个_区块_将包含决定和描述智能合约在该区块期间所发生的全部信息。 + +## 账户链的集合:分片链 + +现在让我们考虑有许多账户的情况。我们得到一些_账户链_并将它们存储在一起,这样的一组_账户链_被称为**分片链 (ShardChain)**。同样地,我们可以将**分片链**切割成**分片区块**,这些区块是个别_账户区块_的聚合。 + +### 分片链的动态拆分与合并 + +请注意,由于_分片链_由容易区分的_账户链_组成,我们可以轻松地将其分割。这样,如果我们有1个_分片链_,描述了100万个账户的事件,且每秒交易量过多,无法由一个节点处理和存储,那么我们就将该链分割(或**拆分**)为两个较小的_分片链_,每条链处理50万个账户,每条链在一组独立的节点上处理。 + +同样地,如果某些分片变得过于空闲,它们可以被**合并**为一个更大的分片。 + +显然有两个极限情况:分片只包含一个账户(因此无法进一步分割)以及当分片包含所有账户。 + +账户可以通过发送消息相互交互。这里会有一种特殊的路由机制,将消息从传出队列移动到相应的传入队列,并确保1) 所有消息都将被送达 2) 消息将连续送达(较早发送的消息将更早到达目的地)。 + +:::info 旁注 +为了使分割和合并具有确定性,将账户链聚合成分片是基于账户地址的位表示。例如,地址会看起来像`(分片前缀, 地址)`这种形式。这样,分片链中的所有账户将具有完全相同的二进制前缀(例如所有地址都以`0b00101`开头)。 +::: + +## 区块链 + +包含所有账户并按照一套规则运行的所有分片的集合被称为**区块链**。 + +在TON中,可以有许多套规则,因此允许多个区块链同时运行,并通过发送跨链消息相互交互,就像同链的账户之间的交互一样。 + +### 工作链:有自己规则的区块链 + +如果你想自定义一组分片链的规则,你可以创建一个**工作链 (Workchain)**。一个很好的例子是创建一个基于EVM的工作链,在其上运行Solidity智能合约。 + +理论上,社区中的每个人都可以创建自己的工作链。事实上,构建它是一个相当复杂的任务,在此之前还要支付创建它的(昂贵)费用,并获得验证者的2/3的票数来批准创建你的工作链。 + +TON允许创建多达`2^32`个工作链,每个工作链则可以细分为多达`2^60`个分片。 + +如今,在TON中只有2个工作链:主链和基本链。 + +基本链用于日常交易,因为它相对便宜,而主链对于TON具有至关重要的功能,所以让我们来了解它的作用! + +### 主链:区块链之链 + +网络需要对消息路由和交易执行进行同步。换句话说,网络中的节点需要一种方式来固定多链状态的某个“点”,并就该状态达成共识。在TON中,一个称为\*\*主链 (Masterchain)\*\*的特殊链被用于此目的。_主链_的区块包含有关系统中所有其他链的额外信息(最新的区块哈希),因此任何观察者都可以非常明确地确定单个主链区块时所有多链系统的状态。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/cells-as-data-storage.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/cells-as-data-storage.md new file mode 100644 index 0000000000..4ac0b85591 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/cells-as-data-storage.md @@ -0,0 +1,50 @@ +# 以 Cell 为数据存储 + +TON 中的所有内容都存储在cell(单元)中。一个cell是一个数据结构,包含: + +- 高达 **1023 位** 的数据(不是字节!) +- 高达 **4 个引用** 到其他cell + +位和引用不是混合存储的(它们被分开存储)。禁止循环引用:对于任何cell,其后代cell都不能将此原始cell作为引用。 + +因此,所有cell构成一个有向无环图(DAG)。这里有一个很好的图片来说明: + +![Directed Acylic Graph](/img/docs/dag.png) + +## Cell 类型 + +目前,有 5 种类型的cell:*普通* 和 4 种 *另类*。另类类型包括以下内容: + +- 裁剪分支cell +- 库引用cell +- Merkle 证明cell +- Merkle 更新cell + +:::tip +了解更多有关特殊cell的信息,请参见:[**TVM 白皮书,第 3 节**](https://ton.org/tvm.pdf)。 +::: + +## Cell 风格 + +cell是一种为紧凑存储而优化的不透明对象。 + +特别是,它会去重数据:如果在不同分支中引用了多个等效的子cell,那它们的内容仅存储一次。然而,不透明性意味着无法直接修改或读取cell。因此,还有两种额外的cell风格: + +- *Builder* 用于部分构建的cell,可以为其定义用于追加位串、整数、其他cell和引用其他cell的快速操作。 +- *Slice* 用于“解剖”cell,表示部分解析的cell的剩余部分或驻留在其中的值(子cell),通过解析指令从中提取。 + +另一种在 TVM 中使用的特殊cell风格: + +- *Continuation* 用于包含 TON 虚拟机的操作码(指令)的cell,请参阅[TVM 概览](/learn/tvm-instructions/tvm-overview)。 + +## 将数据序列化为 Cell + +TON 中的任何对象(消息、消息队列、区块、整个区块链状态、合约代码和数据)都可以序列化为cell。 + +序列化的过程由 TL-B 方案描述:这是一个正式描述如何将此对象序列化为 *Builder* 或如何从 *Slice* 解析给定类型对象的方案。对于cell的 TL-B 与字节流的 TL 或 ProtoBuf 相同。 + +如果您想了解有关cell(反)序列化的更多详细信息,可以阅读[单元(Cell)和单元包(Bag of Cells)](/develop/data-formats/cell-boc)文章。 + +## 参阅 + +- [TL-B 语言](/develop/data-formats/tl-b-language) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/ton-networking.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/ton-networking.md new file mode 100644 index 0000000000..57565d64b5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-blockchain/ton-networking.md @@ -0,0 +1,11 @@ +# TON网络 + +TON使用自己的点对点网络协议。 + +- **TON区块链使用这些协议**来传播新区块,发送和收集候选交易等。 + + 虽然单区块链项目(如比特币或以太坊)的网络需求相对容易满足(基本上需要构建一个点对点覆盖网络,然后通过[gossip协议](https://en.wikipedia.org/wiki/Gossip_protocol)传播所有新区块和候选交易),但多区块链项目(如TON)的要求更高(例如,必须能够仅订阅某些分片链的更新,而不一定是全部)。 + +- **TON生态系统服务(例如TON代理,TON网站,TON存储)运行在这些协议上。** + + 一旦为支持TON区块链所需的更复杂的网络协议就位,就会发现它们可以轻松地用于不一定与区块链本身的直接相关的需求,从而为在TON生态系统中创造新服务提供了更多的可能性和灵活性。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/explorers-in-ton.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/explorers-in-ton.mdx new file mode 100644 index 0000000000..e52ef6df52 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/explorers-in-ton.mdx @@ -0,0 +1,155 @@ +import Player from '@site/src/components/player' + +# TON 中的 Explorers + +在这篇文章中,我们将考虑TONExplorers,他们的能力和特点,从开发者的角度出发。 + +## 什么是 Explorers? + +Explorer是一个网站,允许您查看区块链中的信息,如账户余额、交易历史、区块等。 + + + +## 哪些 Explorers 存在? + +在TON Explorers中,您可以区分几个类别: + +- 日常使用 +- 为开发者提供扩展信息 +- 专业化 + +这种分类大体上是有条件的,一个Explorer可以同时属于几个类别。所以我们不要过多地关注这个。 + +## Explorers 中的地址别名 + +通过使用地址别名使您的服务地址更加用户友好。根据提供的指南创建拉取请求(PR): + +- [tonkeeper/ton-assets](https://github.com/tonkeeper/ton-assets) - Tonviewer.com别名 +- [catchain/address-book](https://github.com/catchain/address-book)- Tonscan.org别名 + +## 通用功能 + +让我们从所有Explorers中都存在的通用功能开始。 + +几乎所有的Explorers都能查找关于余额、交易历史和智能合约的信息,如果部署在地址上的话。 + +接下来,我们将考虑几个可以归为这些类别的Explorers。 + +## TON Scan.org + +适合日常使用的好Explorer。它提供了TON区块链的全面视图,允许用户搜索交易、地址、区块等。任何搜索都是针对公共[地址簿](https://github.com/catchain/tonscan/blob/master/src/addrbook.json)(TON基金会,OKX等)进行的。 + +### 特点 + +- **适合日常使用** +- 对开发者方便 +- TON DNS支持 +- 合约类型 +- 合约反汇编器 + +### 链接 + +- 网址:https://tonscan.org/ +- 测试网网址:https://testnet.tonscan.org/ + +## TON Scan.com + +### 特点 + +- 交易聚合分析 +- 交易追踪 + +### 链接 + +- 网址:https://tonscan.com/ + +## Ton Whales Explorer + +这个Explorer比普通用户更倾向于开发者。 + +### 特点 + +- **对开发者方便** +- 合约类型 +- 合约反汇编器 + +### 链接 + +- 网址:https://tonwhales.com/explorer + +## Tonviewer Explorer + +这个Explorer是最新的,有自己独特的特点。 +例如,追踪。这个功能允许您看到智能合约之间的整个交易序列,即使后续交易不包含您的地址。 + +交易信息不如TON Whales那样详细。 + +### 特点 + +- 对开发者方便 +- 适合日常使用 +- Jetton交易历史 +- **追踪** +- TON DNS支持 + +### 链接 + +- 网址:https://tonviewer.com/ +- 测试网网址:https://testnet.tonviewer.com/ + +## TON NFT Explorer + +这个Explorer专注于NFT,但也可以作为常规Explorer使用。 + +查看钱包地址时,您可以找出它存储了哪些NFT,并在查看NFT时,您可以找出元数据、集合地址、所有者和交易历史。 + +### 特点 + +- 对开发者方便 +- 合约类型 +- **专注于NFT** + +### 链接 + +- 网址:https://explorer.tonnft.tools/ +- 测试网网址:https://testnet.explorer.tonnft.tools/ + +## DTON + +DTON是另一个面向开发者的Explorer。它以方便的形式提供了大量的交易信息。 + +此外,它有一个功能,允许您逐步查看交易的 Compute Phase 。 + +### 特点 + +- 对开发者方便 +- 关于 Compute Phase 的扩展信息 +- 合约类型 +- 合约反汇编器 + +### 链接 + +- 网址:https://dton.io/ + +## TON NFTscan + +这个Explorer专为TON区块链上的非同质化代币(NFT)设计。它允许用户探索、跟踪和验证NFT交易、合约和元数据。 + +### 特点 + +- 对普通用户方便 +- 对交易者有用的信息,如日交易量 +- NFT集合排名 + +### 链接 + +- 网址:https://ton.nftscan.com/ + +## 想在这个列表中吗? + +请给其中一个[维护者](/docs/contribute/maintainers.md)写信。 + +## 参考资料 + +- [ton.app/explorers](https://ton.app/explorers) +- [非常棒的TON库](https://github.com/ton-community/awesome-ton) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/nft.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/nft.md new file mode 100644 index 0000000000..397a08b0b2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/nft.md @@ -0,0 +1,80 @@ +--- +--- + +# NFT 用例 + +NFT,或非同质化代币,是一种独特的数字资产,无法被另一种相同的资产替代。本文描述了在TON区块链中已实现的NFT使用案例和方法。 + +阅读本文后,您将了解为什么NFT有用以及如何在您的项目中使用它们。 + +## 物品所有权 + +非同质化代币主要被认为是您可以在OpenSea或[getgems.io](https://getgems.io)等NFT市场上买卖的酷炫图片。 + +NFT图片和集合很有趣,帮助人们理解基于区块链的所有权概念。然而,从长远来看,NFT的关注点应该超越此,以展示它们的多种潜在用途。 + +## 支持艺术家 + +开发NFT的最初动机之一是找到一种支持艺术家的方法,通过购买以NFT集合形式存储在区块链中的他们的作品。 + +通过这种方式,艺术家可以通过出售新作品以及之后在市场上转售NFT时获得的版税来赚钱。 + +这是getgems.io或OpenSea等市场成为如今任何区块链基本基础设施的最常见原因之一。 + +## 将账户作为 NFT + +11月,Telegram团队推出了[Fragment](https://fragment.com/)市场,任何人都可以在TON区块链上以NFT的形式购买和出售Telegram用户名或匿名号码。 + +此外,12月Telegram团队发布了[无SIM卡注册](https://telegram.org/blog/ultimate-privacy-topics-2-0#sign-up-without-a-sim-card)。您可以购买**虚拟电话号码**作为NFT,在Telegram Messenger中注册,确保您的隐私由TON区块链保护。 + +## 域名作为 NFT + +TON DNS服务完全基于链上运作。如果您想在TON支持的去中心化网络中拥有像`mystore.ton`这样的域名,您需要在[DNS市场](https://dns.ton.org/)为您的钱包购买NFT域名,并支付在区块链中存储和处理数据的租赁费。 + +在Fragment上购买的NFT用户名也可以用于TON DNS,将其绑定到您的钱包,并使用您的username.t.me NFT作为钱包地址。 + +### NFT 作为您钱包的地址 + +每个加密领域的人都理解钱包的独特地址,如`Egbt...Ybgx`。 + +但如果您想从您的妈妈那里接收钱,这对于区块链大规模采用来说是一种无用的方法! + +这就是为什么使用`billy.ton`这样的域名支持的钱包对于非加密领域的用户来说效果更好。 + +[Tonkeeper](https://tonkeeper.com/)钱包已经实现了这种方法。您现在可以去查看。 + +## 票务作为 NFT + +NFT票提供了一种出色的解决方案,用于验证参加音乐会或会议等活动的准入。 + +拥有NFT票提供了几个独特的优势: + +首先,NFT票无法被伪造或复制,消除了假票的欺诈性销售的可能性。这确保了买家可以信任票的真实性和卖家的合法性,让他们对所支付的内容充满信心。 + +其次,NFT票为收藏打开了令人兴奋的机会。作为NFT票的所有者,您可以将其存储在您的数字钱包中,并拥有来自各种活动的整个票务集合。这为音乐和艺术爱好者创造了一种新的审美和财务满足感。 + +第三,NFT票提供了便利性和方便性。它们可以使用数字钱包轻松转移,使用户免于亲自接收或发送票的麻烦。与朋友交换票或在二级市场上购买它们的过程变得更简单、更方便。 + +此外,拥有NFT票可能会带来额外的好处和特殊特权。一些艺术家或组织者可能会提供NFT票持有者独家后台访问权、与艺术家见面会或其他奖励,为NFT票持有者增添独特的文化体验。 + +## 授权令牌作为 NFT + +将NFT用作授权令牌引入了一种革命性的授权访问权和权限的方法。 + +NFT代币确保了高级别的安全性,不容易被复制或伪造。这消除了未经授权的访问或假认证的风险,提供了可靠的认证。 + +此外,由于它们的透明性和可追踪性,NFT授权令牌的真实性和所有权可以轻松验证。这使得快速高效的验证成为可能,确保方便地访问各种平台、服务或受限内容。 + +还值得一提的是,NFT在管理权限方面提供了灵活性和适应性。由于NFT可以以编程方式编码特定的访问规则或属性,它们可以适应不同的授权要求。这种灵活性允许对访问级别进行细致的控制,根据需要授予或撤销权限,这在需要分层访问或临时授权限制的场景中尤为有价值。 + +目前提供NFT认证的服务之一是[Playmuse](https://playmuse.org/),这是一个基于TON区块链的媒体服务。该服务旨在吸引Web3音乐家以及其他创作者。 + +Playmuse NFT的所有者可以进入持有者聊天室。作为这个聊天室的参与者,提供了影响服务发展方向的机会,对各种倡议进行投票,并提前接触到著名创作者的预售和NFT拍卖。 + +通过Telegram机器人进入聊天室,该机器人会验证用户钱包中是否有Playmuse NFT。 + +值得注意的是,这只是一个例子,随着TON生态系统的发展,可能会出现新的服务和技术,用于通过NFT进行认证。跟上TON领域的最新发展可以帮助识别其他提供类似认证功能的平台或开源项目。 + +## NFT 作为游戏中的虚拟资产 + +集成到游戏中的NFT允许玩家以可验证和安全的方式拥有和交易游戏内物品,这为游戏增添了额外的价值和兴奋感。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/wallet-apps.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/wallet-apps.mdx new file mode 100644 index 0000000000..0a068a293f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/dive-into-ton/ton-ecosystem/wallet-apps.mdx @@ -0,0 +1,245 @@ +import Player from '@site/src/components/player' + +# 钱包应用程序(面向开发者) + +## 概述 + +本文从开发者的角度描述了钱包。最终目标是创建支持TON大规模采用的钱包应用程序。 + +

+ +

+ +如果您想找到要安装的钱包,请打开[ton.org/wallets](https://ton.org/wallets)。 + +## 非托管软件(热)钱包 + +:::info +软件钱包,通常被称为热钱包,作为主机设备上的软件运行,并在其界面内存储您的私钥。大多数情况下,这些钱包是非托管的,意味着它们让您保管自己的钥匙。 +::: + +以下是一些TON区块链的非托管钱包: + +- [TON Wallet](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd) — TON生态系统的多平台(iOS、Android、macOS、Linux、Windows)经典钱包,由TON基金会开发。 +- [Tonkeeper](https://tonkeeper.com/) — 一个开源的多平台(iOS、Android、Firefox、Chrome)钱包,拥有庞大的用户基础。 +- [Tonhub](https://tonhub.com/) — 一个开源(iOS、Android)的手机钱包替代品,具有先进功能(TON Whales Staking UI)。 +- [OpenMask](https://www.openmask.app/) — 一个带有生物认证的开源Chrome扩展钱包。 +- [MyTonWallet](https://mytonwallet.io/) — 一个开源的浏览器Web钱包和TON的浏览器扩展钱包。 + +### 基本特性 + +| 钱包 | Jetton & NFT转账 | Ton Connect 2.0 | 授权 | 钱包合约 | +| ----------- | --------------- | ----------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| TON Wallet | 未实现 | - | - | [wallet v3](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet3-code.fc) | +| Tonkeeper | 支持 | [支持](https://github.com/ton-society/ton-footsteps/issues/161) | [TON Connect 2.0](/develop/dapps/ton-connect) | [wallet v4](https://github.com/ton-blockchain/wallet-contract/tree/3fd1d7ae39f1c46ec1f2be54c4040d8d87505e0f) | +| Tonhub | 支持\* | [支持](https://github.com/ton-society/ton-footsteps/issues/108) | [TON Connect 2.0](/develop/dapps/ton-connect) | [wallet v4](https://github.com/ton-blockchain/wallet-contract/tree/3fd1d7ae39f1c46ec1f2be54c4040d8d87505e0f) | +| OpenMask | 支持 | [支持](https://github.com/ton-society/ton-footsteps/issues/107) | [TON Connect 2.0](/develop/dapps/ton-connect) | [wallet v4](https://github.com/ton-blockchain/wallet-contract/tree/3fd1d7ae39f1c46ec1f2be54c4040d8d87505e0f) | +| MyTonWallet | 支持 | [支持](https://github.com/ton-society/ton-footsteps/issues/149) | [TON Connect 2.0](/develop/dapps/ton-connect) | [wallet v4](https://github.com/ton-blockchain/wallet-contract/tree/3fd1d7ae39f1c46ec1f2be54c4040d8d87505e0f) | + +> \*Tonhub扩展打开了一个内置浏览器,允许NFT市场布局。Jetton完全支持。 + +### TON Wallet + +TON Wallet是面向普通用户的大规模采用区块链技术的第一步。它展示了钱包在TON区块链上应如何工作。 + +| | +| --------------------------------------------------------------------------------------------------------- | +|               ![TON wallet](/img/docs/wallet-apps/TonWallet.png?raw=true)                                 | + +#### 优点和缺点 + +- ✅ TON基金会开发的原始钱包。TON Wallet根据TON区块链核心开发者的愿景运作。 +- ✅ 多平台架构支持。它在Linux、Windows、macOS、iOS、Android上运行,也可以作为Chrome插件使用。 +- ✅ [漏洞赏金计划](https://github.com/ton-blockchain/bug-bounty) +- ❌ 很少更新。这个钱包没有所有最新功能(TON DNS地址,和wallet-v4合约不支持)。 +- ❌ 当前UI已过时,比其他钱包差。 + +#### Ton Wallet测试环境 + +要将TON经典钱包切换到测试环境,您应该在浏览器中打开带有testnet参数的地址: + +#### 链接 + +- [GitHub\*](https://github.com/ton-blockchain/wallet-ios) + + > \*每个支持的操作系统的TON Wallet客户端都放在附近的库中。 + +### Tonkeeper + +[Tonkeeper](https://tonkeeper.com/) - 是最受欢迎的钱包,由Tonkeeper团队开发,并得到用户和开发者的积极支持。 + +| | +| -------------------------------------------------------------------------------------------------------- | +|               ![Tonkeeper](/img/docs/wallet-apps/Tonkeeper.png?raw=true)                                 | + +#### 优点和缺点 + +- ✅ 这个应用在用户中最受欢迎。 +- ✅ 支持所有最新功能,包括用户钱包之间的原生NFT转移。 +- ✅ 支持所有主要平台,如iOS和Android,也在Firefox或Chrome等流行浏览器中运行。 +- ❌ 要为其源代码做出贡献需要高级技能。很多工作已经完成,新手很难添加一些重要或有用的东西。 + +#### Tonkeeper测试环境 + +要在Mainnet和Testnet之间切换Tonkeeper应用:在设置中,点击底部Tonkeeper图标5次,然后在开发者菜单中切换网络。在开发者菜单中点击`Switch to Testnet`或`Switch to Mainnet`,根据您需要的网络。 + +| | +| --------------------------------------------------------------------------------------------------------------- | +|               ![TestMode](/img/docs/wallet-apps/Tonkeeper-testnet.png?raw=true)                                 | + +#### 链接 + +- [GitHub](https://github.com/tonkeeper/wallet) +- [Tonkeeper Wallet API](https://github.com/tonkeeper/wallet-api) + +### Tonhub + +[Tonhub](https://tonhub.com/) - 是另一个完整的TON钱包,支持基本的最新功能。Ton Whales正在迅速提升钱包的能力。 + +| | +| -------------------------------------------------------------------------------------------------- | +|               ![Tonhub](/img/docs/wallet-apps/Tonhub.png?raw=true)                                 | + +#### 优点和缺点 + +- ✅ 拥有自己定制的[Ton Nominator](https://github.com/tonwhales/ton-nominators)合约,由Tonhub UI支持。 +- ✅ 从一开始就是开源钱包。 +- ✅ [漏洞赏金计划](https://tonwhales.com/bounty)。 +- ❌ 没有任何桌面平台的支持。 +- ❌ 要为其源代码做出贡献需要高级技能。 + +#### Tonhub测试环境 + +您需要下载单独的应用程序才能连接到Testnet。 + +#### 链接 + +- [GitHub](https://github.com/tonwhales/wallet) +- [Sandbox iOS](https://apps.apple.com/app/ton-development-wallet/id1607857373) +- [Sandbox Android](https://play.google.com/store/apps/details?id=com.tonhub.wallet.testnet) + +### OpenMask + +[OpenMask](https://www.openmask.app/) - 作为浏览器扩展,是一款开创性的工具,使用户在Web3中的交互和体验成为可能。 + +| | +| ------------------------------------------------------------------------------------------------------ | +|               ![OpenMask](/img/docs/wallet-apps/OpenMask.png?raw=true)                                 | + +#### 优点和缺点 + +- ✅ 对开发者来说,通过桌面学习和创建dApps而无需移动设备很方便。 +- ✅ 独特的功能,如多钱包,以及其文档中的详细描述和示例。 +- ❌ 目前几乎没有与dApps的集成。 +- ❌ 仅支持浏览器扩展平台。 + +#### OpenMask测试环境 + +要在Mainnet和Testnet之间切换OpenMask:您需要点击OpenMask主屏幕顶部的“mainnet/testnet”按钮,然后选择您需要的网络。 + +#### 链接 + +- [GitHub](https://github.com/OpenProduct/openmask-extension) +- [文档](https://www.openmask.app/docs/introduction) + +### MyTonWallet + +[MyTonWallet](https://mytonwallet.io/) - 是TON最功能丰富的Web钱包和浏览器扩展 - 支持代币、NFT、TON DNS、TON站点、TON代理和TON魔法。 + +| | +| ------------------------------------------------------------------------------------------------------------ | +|               ![MyTonWallet](/img/docs/wallet-apps/MyTonWallet.png?raw=true)                                 | + +#### 优点和缺点 + +- ✅ 已实现所有基本功能。 +- ✅ 独特功能 - 从钱包UI管理官方[Nominator Pool合约](https://github.com/ton-blockchain/nominator-pool)。 +- ✅ 支持所有主要平台(如macOS、Linux和Windows),也可以作为Chrome扩展运行。 +- ❌ 不支持Firefox作为扩展。 + +#### MyTonWallet测试环境 + +要将MyTonWallet切换到Testnet:打开设置,点击应用版本5次,打开网络切换器。 + +#### 链接 + +- [GitHub](https://github.com/mytonwalletorg/mytonwallet) +- [MyTonWallet Telegram](https://t.me/MyTonWalletRu) + +## 非托管硬件(冷)钱包 + +:::info +硬件钱包是一种物理设备,它将您的加密货币资金的私钥存储在远离互联网的地方。即使您使用它进行交易,钱包也会在离线环境中确认交易。这个过程有助于始终将您的私钥远离互联网的风险。 +::: + +### Ledger + +[Ledger](https://www.ledger.com/)硬件钱包与Ledger Live应用程序。 + +#### 链接 + +- [Ledger TON博客文章](https://blog.ton.org/ton-is-coming-to-ledger-hardware-wallets) Ledger钱包中TON的用户手册。 +- [Ledger](https://www.ledger.com/)官方网站。 + +### SafePal + +[SafePal](https://www.safepal.com/en/)是您进入快速扩张的去中心化应用程序银河系的门户。 + +#### 链接 + +- [SafePal](https://www.safepal.com/en/)官方网站 + +## 托管钱包 + +:::info +使用托管钱包时,用户信任其他人持有钱包的私钥。 +::: + +### @wallet + +[@wallet](https://t.me/wallet) — 一个用于在Telegram中发送、接收或使用P2P交易TON换取真钱的机器人。支持Telegram Mini App UI。 + +| | +| -------------------------------------------------------------------------------------------------- | +|               ![wallet](/img/docs/wallet-apps/Wallet.png?raw=true)                                 | + +### @cryptobot + +[@cryptobot](https://t.me/cryptobot) — 一个用于存储、发送和交换TON的Telegram机器人钱包。 + +| | +| -------------------------------------------------------------------------------------------------------- | +|               ![CryptoBot](/img/docs/wallet-apps/CryptoBot.png?raw=true)                                 | + +#### 第三方集成链接 + +- [Crypto Pay API](https://help.crypt.bot/crypto-pay-api) + +### @tonRocketBot + +[@tonRocketBot](https://t.me/tonRocketBot) - 一个用于存储、发送和交换TON的Telegram机器人钱包。支持Jetton交易。 + +| | +| -------------------------------------------------------------------------------------------------------------- | +|               ![tonrocketbot](/img/docs/wallet-apps/tonrocketbot.png?raw=true)                                 | + +#### 第三方集成链接 + +- [Rocket exchange](https://trade.ton-rocket.com/api/) +- [Rocket pay docs](https://pay.ton-rocket.com/api/) + +## 多签名钱包 + +### Tonkey + +Tonkey是一个先进的项目,为TON区块链引入了多重签名功能。 + +### 链接 + +- https://tonkey.app/ +- [GitHub](https://github.com/tonkey-app) + +## 参阅 + +- [什么是区块链?什么是智能合约?什么是Gas?](https://blog.ton.org/what-is-blockchain) +- [钱包合约类型](/participate/wallets/contracts) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/educational-resources.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/educational-resources.mdx new file mode 100644 index 0000000000..b8194ce1ce --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/educational-resources.mdx @@ -0,0 +1,13 @@ +# 教育资源 + +## TON 区块链课程 + +:::danger +页面正在开发中. +::: + +## 参见 + +- [TON 速成](https://tonspeedrun.com/) +- [TON Hello World](https://tonhelloworld.com/01-wallet/) +- [[YouTube]TON 开发研究 EN ](https://www.youtube.com/@TONDevStudy)[[RU]](https://www.youtube.com/results?search_query=tondevstudy) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/glossary.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/glossary.md new file mode 100644 index 0000000000..f1748a97a9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/glossary.md @@ -0,0 +1,509 @@ +# 词汇表 + +## 介绍 + +在这个词汇表中,您可以找到任何与TON和加密相关的词汇 + +____________ + +## A + +### Airdrop + +**Airdrop** — 一种在特定参与者之间免费分发代币的活动。 + +### Altcoin + +**Altcoin** — 所有加密货币,除了比特币以外,都被称为山寨币。 + +### AMA + +**AMA** — 一种在线问答活动,称为“问我任何事”,项目领导回答社区关于产品或服务的问题。 + +### API + +**API** — 应用程序编程接口,一种允许两个程序通过一系列协议进行交互的机制。 + +### APY + +**APY** — 年百分比收益率,用于计算给定资产的年利率。 + +____________ + +## B + +### Bearish + +**Bearish** — 当资产价格由于投资者抛售而下降时使用的术语。(该术语通常用于描述整体市场情绪。) + +### Binance + +**Binance** — 按日交易量计算的全球最大的加密货币交易所。 + +### Bitcoin(BTC) + +**Bitcoin(BTC)** — 首个具有开源代码的加密货币,也是第一个去中心化网络,为区块链技术的普及奠定了基础。 + +### Blockchain + +**Blockchain** — 一种以区块链形式记录网络上每个事件的数据的分布式账本。 + +### Bloodbath + +**Bloodbath** — 通常用来描述严重且持续的市场崩溃的口头用语,导致价格图表上出现多个深红条。 + +### BoC + +**BoC** — 单元包。通常在代码中使用。 + +### BoF + +**BoF** — 文件袋。通常在代码中使用。 + +### Bot + +**Bot** — 为两个生态系统编写的程序,例如开放网络和Telegram信使。在Telegram上,机器人是由软件操作的信使中的账户。 + +### Bridge + +**Bridge** — 一种连接各种区块链以在网络之间传输代币和数据的程序。TON桥可以在此链接找到。 + +### Bullish + +**Bullish** — 用于描述价值上升的资产的术语。(“牛市”是市场整体价值增长的相反情况。) + +### Burning + +**Burning** — 燃烧或从流通和总供应中移除一定数量的代币的行为,通常导致需求增加。 + +___________ + +## C + +### CEX + +**CEX** — 用于交易代币的中心化加密货币交易所。 + +### CMC + +**CMC** — CoinMarketCap,一个密切关注代币价格和市值变化的加密信息聚合器。 + +### Coinbase + +**Coinbase** — 美国最大的加密货币交易所。 + +### Cryptobot + +**Cryptobot** — 用于在Telegram上购买、交易和出售Toncoin和其他加密货币的点对点(P2P)机器人服务,无需进行了解您的客户(KYC)验证。 + +### Custodial + +**Custodial** — 一种加密货币钱包,其中第三方存储加密货币,而不是其真正的所有者。 + +___________ + +## D + +### DApps + +**DApps** — 为由节点组支持的区块链而制作的去中心化应用程序,而不是由中央服务器支持。 + +### DCA + +**DCA** — 定期投资,一种投资策略,投资者以低但固定的价格购买加密资产,以降低风险敞口。 + +### Decentralization + +**Decentralization** — 是TON和其他区块链的主要原则之一。没有去中心化,将无法实现Web3;因此,TON生态系统的每个元素都围绕最大化去中心化展开。 + +### DeFi + +**DeFi** — 是传统金融的去中心化模拟,包括基于智能合约的可访问金融服务和应用程序。 + +### DEX + +**DEX** — 去中心化交易所(DEX),用户可以在没有任何中介的情况下交易加密货币。确保安全交易所需的在线实体是区块链本身。 + +### Diamond hands + +**Diamond hands** — 一个俚语术语,用于描述那些不打算出售其资产的投资者 — 即使市场崩溃或市场熊市。 + +### DNS + +**DNS** — 域名系统,一种允许用户在人类可读的域名(ton.org)和机器可读的IP地址(192.0.2.44)之间进行交易的技术。 + +### Dolphin + +**Dolphin** — 一个资本水平较低但在社区中具有影响力的投资者。 + +### Donate + +**Donate** — 通过Telegram上的机器人服务,人们可以捐款,或内容创作者可以在Telegram上通过Toncoin进行变现。 + +### Dump + +**Dump** — 操纵代币或加密货币价值的增加,然后卖出。 + +### Durov + +**Durov** — Pavel Durov,一位因创办VK社交网络和Telegram信使而闻名的俄罗斯企业家。Nikolai Durov是Pavel的兄弟,参与了VK、Telegram以及TON的开发。 + +### DYOR + +**DYOR** — 做你自己的研究,即在决定投资之前对项目、公司或加密货币进行研究的过程。 + +___________ + +## E + +### EVM + +**EVM** — 以太坊虚拟机,一台表现得像去中心化计算机的机器,它在每个新区块后计算以太坊区块链的状态并执行智能合约。 + +### Exchange + +**Exchange** — 用于交易和使用其他市场工具的场所。 + +___________ + +## F + +### Farming + +**Farming** — 将您的加密货币资产借出以获得奖 + +### Fiat + +**Fiat** - 中央银行或金融当局发行的常规货币。 + +### FOMO + +**FOMO**--"害怕错过",这是一种心理状态,当一些投资者想到可能会失去某个机会的潜在收益时,就会产生这种心理。它通常出现在牛市期间,以及交易者没有尽职尽责地分析某个项目时。 + +### Fungible tokens + +**Fungible tokens**--在任何特定时刻都与任何其他同类代币具有相同价值的加密货币。 + +### FUD + +**FUD**--"恐惧、不确定性和疑虑",基于多种因素的市场情绪。 + +### Full Node + +**Full Node** - TON 区块链上的计算机,用于同步和复制整个 TON 区块链。 + +### FunC + +**FunC** - TON 上的智能合约语言。 + +___________ + +## G + +### Gas + +**Gas** - 为区块链上的交易支付的费用。 + +### GitHub + +**GitHub** - 开发人员聚集在一起创建程序基础代码的平台。 + +___________ + +## H + +### Hackathon + +**Hackathon** - 程序员聚集在一起开发软件、程序、应用程序等。 + +### Hash + +**Hash** - 通过哈希算法创建的交易数据信息。 + +### Hash rate + +**Hash rate** - 表明网络上用于加密货币挖矿的计算能力。 + +### Hold + +**Hold** - 保留--即不出售--投资组合中的一项或多项资产。 + +___________ + +## I + +### + +**ICO**--首次代币发行,这是加密项目在早期阶段吸引资金的一种方法。 + +### IDO + +**IDO** - 首次去中心化交易所发行,是在去中心化交易所发行加密货币或代币时吸引资本的另一种方法。 + +### Inflation + +**Inflation** - 一种货币(如美元或欧元)价值下降的过程。通币(和其他加密货币)的发行具有高度的透明度和可预测性,具有通货紧缩的特性。 + +___________ + +## K + +### KYC + +**KYС** - *Know Your Customer*,用户在创建加密服务账户时验证身份的过程。 + +___________ + +## L + +### Launchpad + +\*\* Launchpad\*\*--一个将投资者和项目聚集在一起的加密初创企业平台。TON生态系统的主要启动平台是Tonstarter。 + +### Liquidity pool + +**Liquidity pool**--将加密资产组合在一起并冻结在智能合约中。流动池可用于去中心化交易、贷款和其他活动。 + +__________ + +## M + +### Mainnet + +**Mainnet** - 区块链的主网络。 + +### Market cap (capitalization) + +**Market cap (capitalization)**--加密货币代币数量总和的总价值。 + +### Masterchain + +**Masterchain** - 在多层区块链中,主链是最重要的。对于 TON 来说,主链是网络的主链。在区块链上进行的操作都是在主链上进行的。 + +### Metaverse + +**Metaverse** - 一个类似电子游戏的数字宇宙,用户在其中创建化身,并与其他人或用户的数字代表互动。 + +### Moon + +**Moon** - 一个加密术语,描述加密资产在价格图表上的垂直轨迹,即迅速增值。 + +__________ + +## N + +### NFA + +**NFA**--非金融建议,这个首字母缩写词被用作免责声明,以避免投资者在与他人讨论加密货币或项目时承担责任或义务。 + +### NFT + +**NFT**--不可篡改的代币,是区块链上独一无二的数字代币,无法复制或铸造多次。 + +### Nominator + +**分母**--向验证者提供资金的人,以便后者能够在 TON 区块链上确认区块。 + +### Non-custodial + +**Non-custodial** - 一种加密钱包,可让所有者/用户完全控制资产。 + +__________ + +## O + +### Off-ramp + +**Off-ramp** - 将加密货币转换为法定货币的方法。 + +### On-ramp + +**On-ramp** - 通过花费法币转换(购买)加密货币的方法。 + +### Onion routing + +**Onion routing** - 一种类似于 TOR 的技术,允许在网络上进行匿名交互。所有信息都经过层层加密,就像洋葱一样。TON Proxy 就采用了这种技术。 + +__________ + +## P + +### Paper hands + +**Paper hands**--倾向于恐慌性抛售的投资者--缺乏经验的投资者。 + +### Proof-of-stake + +**Proof-of-stake**--一种共识机制,用于处理区块链上新区块的交易。 + +### Proof-of-work + +**Proof-of-work** - 一种共识算法,一方向另一方证明已花费了特定数量的计算工作。通过消耗一点能量,一方可以验证这一点。 + +### Proxy + +**Proxy** - 计算机网络上的一种服务,允许客户端安装与其他网络服务的间接网络连接。 + +### Pump + +**Pump** - 人为抬高加密货币或资产的价格。 + +### P2P + +**P2P** - 点对点,用户之间的交易,无需第三方或中介的帮助。 + +__________ + +## R + +### Roadmap + +**Roadmap** - 一个项目的战略计划,显示其产品、服务、更新等的发布时间。 + +### ROI + +**ROI**--投资回报率,从投资中获得的利润。 + +_________ + +## S + +### SBT + +**SBT** - *Soulbound token*,一种永远无法转让的 NFT,因为它包含了其所有者及其成就的信息。 + +### Scalability + +**Scalability**--区块链网络处理复杂交易和大量交易的能力。 + +### SEC + +**SEC** - 证券交易委员会,美国的金融监管机构。 + +### Shard + +**Shard**--一种机制,通过分解成较小的区块链来缓解网络拥堵,从而帮助区块链网络进行扩展--TON 区块链就是这样做的。 + +### Smart contract + +**Smart contract**--自动执行代码,在数学算法的帮助下监督和实现操作,无需人工干预。 + +### Spot trading + +**Spot trading** - 交易金融资产赚钱。 + +### Stablecoin + +**Stablecoin** - 一种价值稳定(通常与法定货币挂钩)且不会崩溃的加密货币。 + +### Staking + +**Staking**--用户通过在押注证明算法中存储硬币或代币赚取被动收入的一种方式,而押注证明算法反过来又能确保区块链顺利运行。为此,他们可以获得奖励作为激励。 + +### Swap + +**Swap** - 两种金融资产的交换,如用通币交换 USDT。 + +________ + +## T + +### TEP + +**TEP** - [TON Enhancement Proposals](https://github.com/ton-blockchain/TEPs),一套与 TON 生态系统各部分交互的标准方法。 + +### Testnet + +**Testnet** - 在主网启动前测试项目或服务的网络。 + +### Ticker + +**Ticker** - 交易所、交易服务或其他 DeFi 解决方案中加密货币、资产或代币的简称 - 例如,TON 表示 Toncoin。 + +### The Merge + +**The Merge**--以太坊从工作量证明向权益证明转换的过渡过程。 + +### Token + +**Token** - 一种数字资产形式;可具有多种功能。 + +### Tokenomics + +**代币经济学** - 一种加密货币(或代币)的经济计划和发行策略。 + +### To the moon + +**To the moon**--这是人们产生 FOMO 时使用的一个俗语。它指的是希望加密货币的价值迅速获得大量增值--因此它的轨迹是奔月。 + +### Toncoin + +**Toncoin** - TON 生态系统的本地加密货币,用于开发服务以及支付费用和服务。它可以买卖和交易。 + +### Trading + +**Trading** - 以盈利为目的买卖加密货币。 + +### TVL + +**TVL**(锁定的总价值)--锁定的总价值表示当前在特定协议中被锁定的资产数量。 + +### TVM + +**TVM**--Ton 虚拟机(Ton Virtual Machine),这是一种行为类似于去中心化计算机的机器,它会在每个新区块之后计算 Ton 区块链的状态,并执行智能合约。 + +___________ + +## V + +### Validator + +**Validator** - 在 TON 区块链上验证新区块的人。 + +___________ + +## W + +### WAGMI + +**WAGMI**--"我们都会成功的",这是加密货币社区经常使用的一句话,用来表达有朝一日通过投资加密货币致富的愿望。 + +### Wallet + +**Wallet** - 通过购买或出售加密货币和代币所需的私钥系统存储加密货币的软件。它也是 TON 生态系统中用于买卖 Toncoin 的机器人。 + +### Web3 + +**Web3**--基于区块链技术的新一代互联网,包括去中心化和代币经济。 + +### Whale + +**Whale** - 拥有大量加密货币和代币的投资者。 + +### Whitelist + +**Whitelist** - 授予人们特殊津贴的名单。 + +### White paper + +**White paper** - 由项目开发人员撰写的项目主要文件。它解释了技术和项目目标。 + +### Watchlist + +**Wаtchlist** - 可定制的加密货币列表,投资者希望跟踪其价格走势。 + +### Workchain + +**Workchain** - 连接到主链的次级链。它们可以包含大量不同的连接链,这些链有自己的共识规则。它们还可以包含地址和交易信息以及智能合约的虚拟机。此外,它们还可以与主链兼容并相互影响。 + +___________ + +## Y + +### Yield farming + +**Yield farming**--在智能合约中出借或放置加密货币或代币,以赚取交易费形式的奖励。 + +### Yolo + +**Yolo**--"你只能活一次",这是一个俚语缩写,用来号召人们尽情享受生活,而不考虑所做努力的风险。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/auditors.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/auditors.mdx new file mode 100644 index 0000000000..ef6014dd99 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/auditors.mdx @@ -0,0 +1,30 @@ +import Button from '@site/src/components/button' + +# 安全保障提供商(SAP) + +## TON SAP 列表 + +请使用以下质量保证提供商测试您的软件: + +- [skynet.certik.com](https://skynet.certik.com/) +- [quantstamp.com](https://quantstamp.com/) +- [softstack.io formerly Chainsulting](https://softstack.io/) +- [slowmist.com](https://slowmist.com/) +- [hexens.io](https://hexens.io/) +- [vidma.io](https://vidma.io/) +- [scalebit](https://www.scalebit.xyz/) + +## 添加新的 SAP + +如果您是TON的新安全提供商并希望被列入名单,请填写表格。 + + + +## 参阅 + +- [外包开发](/develop/companies/outsource) +- [Ton 工作](https://jobs.ton.org/jobs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/outsource.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/outsource.mdx new file mode 100644 index 0000000000..be23c71207 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/concepts/qa-outsource/outsource.mdx @@ -0,0 +1,165 @@ +import Button from '@site/src/components/button' + +# 外包开发 + +## 外包团队列表 + +为您的TON项目发现第三方开发团队 + +- [Astralyx](#astralyx) +- [Blockczech R&D Lab](#blockczech-rd-lab) +- [EvaCodes](#evacodes) +- [Coinvent](#coinvent) +- [softstack](#softstack) + +### Astralyx + +#### 概要 + +在TON及其他链开发方面拥有丰富经验的公司。您可以请我们创建设计、Telegram小程序(TMA)、网站等。 + +#### 工作流 + +- TON智能合约开发(包括审计和测试) +- Web 2.0 和 Web 3.0 开发 +- 设计、艺术、为项目提供租赁线索 + +#### 项目 + +- [t.me/xjetswapbot](http://t.me/xjetswapbot)(前端,设计) +- [github.com/astralyxdev/lockup-jettons-contract](http://github.com/astralyxdev/lockup-jettons-contract)(智能合约,Web界面,测试) +- [github.com/astralyxdev/ton-proxy](http://github.com/astralyxdev/ton-proxy)(TON代理扩展,最初的之一) +- [store.devdao.io](http://store.devdao.io)(前端,设计) +- [scaleton.io](http://scaleton.io)(着陆页,前端,设计) +- [burn.astralyx.dev](http://burn.astralyx.dev)(TON中用于燃烧SBT NFT的服务,前端,设计) + +#### 联系方式 + +[astralyx.dev](http://astralyx.dev), contact@astralyx.dev + +### Blockczech R&D Lab + +#### 概要 + +Web3.0软件公司及创业工作室,专注于基于区块链的游戏和电子竞技解决方案。 + +#### 工作流 + +- dApps +- TMA开发 +- 区块链游戏 +- 集成 + +#### 项目 + +- [TCG.world](http://TCG.world) +- [cryptomeda.tech](http://cryptomeda.tech) +- [liithos.com](http://liithos.com) +- [x.la/contracts/](http://x.la/contracts/) +- [关于 Blockczech R&D Lab](https://docs.google.com/presentation/d/1htMH1ihm31wQSn08ZziFfK6NpbPSHA3M/edit?usp=sharing&ouid=105247529013711719883&rtpof=true&sd=true) + +#### 联系方式 + +- http://blockczech.io +- [@blockczech](https://t.me/blockczech) + +### EvaCodes + +#### 概要 + +EvaCodes是东欧顶尖的区块链开发公司,团队位于乌克兰、亚美尼亚和波兰。公司拥有50多名熟练的开发人员,已交付60多个web3解决方案,包括web3银行解决方案、L1区块链和web3基础设施。 + +#### 工作流 + +- DeFi +- 加密钱包 +- 基于NFT的解决方案 + +#### 项目 + +- [alium.finance](https://alium.finance/) +- [trush.io](https://trush.io/) +- [konsta.network](https://konsta.network/) + +#### 联系方式 + +- ton@evacodes.com +- [evacodes.com](https://evacodes.com/) +- Telegram [@salesevacodes](https://t.me/salesevacodes) + +### Coinvent + +#### 概要 + +Coinvent是一个致力于创建成功项目的专业外包开发团队。他们的专长从简单的机器人到复杂的DeFi协议开发。 + +#### 工作流 + +- 智能合约 +- DeFi,NFT +- dApps,Telegram小程序 +- 常规Web2 +- Telegram机器人 + +#### 项目 + +- [Tonraffles Lock模块](https://tonraffles.app/lock)(智能合约,前端) +- [Tonraffles NFT发行平台](https://tonraffles.app/nft/launchpad)(智能合约) +- [OOIA购物车功能](https://testnet.ooia.art/)(智能合约) +- [Monaki NFT质押](https://www.monaki.life/)(智能合约) + +#### 联系方式 + +- [coinvent.dev](https://coinvent.dev) +- [@coinvent_dev](https://t.me/coinvent_dev) +- contact@coinvent.dev + +### softstack + +#### 总结 + +Softstack 是一家领先的 Web3 综合解决方案服务提供商,自 2017 年起专注于软件开发和智能合约审计。德国制造 + +#### 工作流 + +- 智能合约&DApp 开发 +- 数字资产钱包 +- Telegram 小程序和机器人 +- 网络安全(合约审计、渗透测试) + +#### 项目 + +- [DeGods](https://degods.com) +- [tixbase](https://tixbase.com) +- [TMRW Foundation](https://tmrw.com) +- [Bitcoin.com](https://bitcoin.com) +- [Coinlink Finance](https://coinlink.finance) + +#### 联系方式 + +- hello@softstack.io +- [softstack.io](https://softstack.io/) +- Telegram [@yannikheinze](https://t.me/yannikheinze) + +## 参阅 + +如果您已经完全准备好成为TON生态系统的外包代理,您可以通过填写我们的表格或提交拉取请求来推广您的公司。 + + + + + +



+ +## 参阅 + +- [安全保证提供者](/develop/companies/auditors) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/README.md new file mode 100644 index 0000000000..df5f30a9c5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/README.md @@ -0,0 +1,48 @@ +# 如何贡献 + +## 确定贡献领域 + +有几种方法可以确定你能为 TON 文档贡献的领域: + +- 加入 Telegram 中的 [TON 文档社团频道](https://t.me/+c-0fVO4XHQsyOWM8),从维护者那里获取最新任务。 +- 如果你有一个特定的关于贡献的想法,但不确定是否合适,请直接联系[文档维护者](/contribute/maintainers)确认。 +- 熟悉 [TON 开发者](https://t.me/tondev_eng) 聊天中最常被问到的问题。 +- 请查看 GitHub 库中的[issues](https://github.com/ton-community/ton-docs/issues)。 +- 了解文档的可用的[开发奖金](https://github.com/ton-society/ton-footsteps/issues?q=documentation)。 + +## 简而言之 + +- 如果你需要在 TON 文档中添加或编辑内容,请针对 `main` 分支创建一个拉取请求。 +- 文档团队将审查拉取请求或在需要时联系你。 +- 库:https://github.com/ton-community/ton-docs + +## 开发 + +### 在线一键贡献设置 + +你可以使用 Gitpod(一个免费的、在线的、类似 VS code 的 IDE)来进行贡献。它将一键启动并创建一个工作空间: + +[![在 Gitpod 中打开](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ton-community/ton-docs) + +### 代码惯例 + +- **最重要的是**:多多阅读文档。匹配项目的整体风格。这包括格式、文件命名、在代码中命名对象、在文档中命名事物等等。 +- **对于文档来说**:编辑文档时,不要在 80 个字符处换行;相反,配置你的编辑器进行软换行。 + +总的来说,不用太担心惯例问题;维护者会在审查你的代码时帮你修正它们。 + +### 拉取请求 + +你需要通过拉取请求将代码贡献回上游。你已经付出了很多努力,我们很感激。我们会尽力与你合作并审查拉取请求。 + +提交拉取请求时,请确保以下内容: + +1. **保持你的拉取请求小**。较小的拉取请求(大约 300 行差异)更容易审查,也更有可能被合并。确保拉取请求只做一件事,否则就请拆分它。 +2. **使用描述性标题**。建议遵循提交消息的惯例。 +3. **测试你的更改**。在拉取请求的描述中讲述你的测试计划。 + +所有拉取请求都应在 `main` 分支上打开。 + +## 接下来的事情 + +TON 文档团队将监控拉取请求。请遵循上述指南帮助我们保持拉取请求的一致性。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/contribution-rules.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/contribution-rules.md new file mode 100644 index 0000000000..dc5f8cf2ff --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/contribution-rules.md @@ -0,0 +1,44 @@ +# 贡献手册 + +在贡献任何 docs.ton.org 页面之前,请审查以下关于常见的和重要的要求,以确保顺利的提交。 + +## 命名 + +- 确保正确使用 *THE* 在 TON 文档中至关重要。*TON Blockchain* 和 *TON Ecosystem* 是大写术语,因此,在使用时不需要 *THE*。 +- 我们将 *TON* 与普通名词一起写,如果根据英语语法需要 *THE*,我们会使用它。例如:"*The* TON Connect *protocol* is a..." + +:::info +TON Blockchain... + +TON Ecosystem... + +The TON Connect protocol... +::: + +请在[这里](https://ton.org/en/brand-assets)查看 TON 的品牌资产。 + +## 文档引用 + +TON 文档的每个页面应以“参阅”部分结束。在那里放置你认为与当前页面相关的页面,无需额外描述。 + +:::info + +``` +## See Also +* [TON Contribution Guidelines](/contribute/contribution-rules) +* [Tutorial Styling Guidelines](/contribute/tutorials/guidelines) +``` + +::: + +## 英文帮助资源 + +TON 生态系统正在为全世界建设,因此,确保每个人都能理解至关重要。在这里,我们提供对想提高英语技能的初级技术写作人员有帮助的材料。 + +- [复数名词](https://www.grammarly.com/blog/plural-nouns/) +- [文章:A 与 An](https://owl.purdue.edu/owl/general_writing/grammar/articles_a_versus_an.html) + +## 参阅 + +- [TON 贡献指南](/contribute/contribution-rules) +- [教程样式指南](/contribute/tutorials/guidelines) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/guidelines.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/guidelines.md new file mode 100644 index 0000000000..bcfad1b964 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/guidelines.md @@ -0,0 +1,24 @@ +# 通用文档原则 + +为了获得最佳的用户体验和清晰度,请在创建 docs.ton.org 上的新内容时,牢记我们旨在应用于所有文档的一般性和重要要求列表。 + +## 为专业人士制作的文档 + +文档页面主要是为了文档目的而非作为教程,因此在文本中最小化使用个人示例或类比非常重要。确保内容既适合专业人士也适合非专业人士,同时仍然提供有价值的信息。 + +## 使用一致的格式 + +为了使读者更容易浏览文档,使用整个文档中一致的格式非常重要。使用标题、副标题、项目符号列表和编号列表来分隔文本,使其更易于阅读。 + +## 在特殊部分提供示例 + +提供示例可以帮助读者更好地理解内容以及如何应用它。如果你正在编写文档页面并需要引用几个示例,请在“参考资料”和“参阅”部分之前创建一个特别的“示例”部分。请不要在文档页面中混合描述和示例。 +可以使用代码片段、截图或图表来阐述你的观点,使文档更具吸引力。 + +## 保持内容更新 + +由于技术或软件更新可能导致技术文档迅速过时,因此定期审查和更新文档非常重要,以确保它保持准确和与当前软件版本相关。 + +## 获取反馈 + +在发布文档之前,最好从其他贡献者或用户那里获取反馈。这可以帮助识别可能令人困惑或不清楚的地方,并允许您在文档发布之前进行改进。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/schemes-guidelines.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/schemes-guidelines.mdx new file mode 100644 index 0000000000..2f09844566 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/docs/schemes-guidelines.mdx @@ -0,0 +1,87 @@ +import ConceptImage from '@site/src/components/conceptImage'; +import ThemedImage from '@theme/ThemedImage'; + +# 图形解释指南 + +在文档中保持一致性至关重要,为此,已经制定了一个特定的标准,用于可视化智能合约中的流程。 + +## 图形解释符号 + +### 消息处理图 + +为了描述消息处理,建议使用类似于智能合约图的图形表示,包括交易和消息的标签。 + +如果交易的顺序不重要,可以省略它们的标签。这简化了图表,使其更易于阅读和理解与消息和合约相关的细节。 + +#### 注释基本图形 + +| 图形 | 描述 | +| ---------------------------------------------------------------------------------------------------------- | ------------- | +| ![](/img/docs/scheme-templates/message-processing-graphs/circle_for_smart_contract.svg?raw=true) | 圆形 - 智能合约实体 | +| ![](/img/docs/scheme-templates/message-processing-graphs/rectangle_for_regular_message.svg?raw=true) | 矩形 - 消息实体 | +| ![](/img/docs/scheme-templates/message-processing-graphs/dashed_rectgl_for_optional_message.svg?raw=true) | 虚线矩形 - 可选消息实体 | +| ![](/img/docs/scheme-templates/message-processing-graphs/line_for_transaction.svg?raw=true) | 交易(编号可选) | +| ![](/img/docs/scheme-templates/message-processing-graphs/person_figure_for_actor.svg?raw=true) | 参与者 | + +- 避免使用大量不同和鲜艳的颜色。 +- 使用图形的修改,例如使用虚线边框。 +- 为了更好的理解,不同的交易可以用不同的线条样式(实线和虚线)显示。 + +#### 消息处理示例 + +

+ +

+ +可以直接从 Visio 学习参考内容 [message-processing.vsdx](/static/schemes-visio/message-processing.vsdx)。 + +### 格式和颜色 + +#### 字体 + +- 图表中所有文本使用 **Inter** 字体系列。 + +#### 颜色 - 亮模式 + +- 铅笔手绘(默认主题) + +#### 颜色 - 暗模式 + +- 字体 `#e3e3e3` +- 背景 `#232328` +- 浅色高亮(箭头和方案边界) `#058dd2` +- 深色高亮(箭头和方案边界) `#0088cc` +- 内部背景(嵌套块) `#333337` + +#### 版本控制政策 + +- 以 SVG 格式设置文档中的图表,以确保在各种设备上的可读性。 +- 在项目的 Git 库的 "/static/visio" 目录下存储原始文件,以便将来更容易修改。 + +### 时序图 + +在涉及 2-3 个actor之间的复杂和重复的通信方案时,建议使用时序图。对于消息,使用常见同步消息箭头的表示。 + +#### 示例 + +

+
+ +
+

+ +### 方案参考 + +- [message-processing.vsdx](/schemes-visio/message_processing.vsdx) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-it-works.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-it-works.md new file mode 100644 index 0000000000..36d4e6eba3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-it-works.md @@ -0,0 +1,152 @@ +# 工作原理 + +![工作原理](/img/localizationProgramGuideline/localization-program.png) + +**TownSquare Labs 本地化系统**由几个关键部分组成。本章将概述该程序的运行方式,帮助您了解其工作原理以及如何有效地使用它。 + +在这个系统中,我们整合了多个应用程序,使其作为一个统一的整体无缝运行: + +- **GitHub**:托管文档,同步上游版本库中的文档,并将翻译同步到特定分支。 +- **Crowdin**:管理翻译流程,包括翻译、校对和设置语言首选项。 +- **人工智能系统**:利用先进的人工智能协助翻译员,确保工作流程顺畅。 +- **自定义词汇表**:为翻译员提供指导,同时确保人工智能根据项目上下文生成准确的译文。此外用户还可根据需要上传自己的词汇表。 + +:::info +本指南不会详细介绍整个流程,但会重点介绍使 TownSquare Labs 本地化系统独一无二的关键组成部分。您可以自行进一步了解该计划。 +::: + +## 通过GitHub实现文档和翻译同步 + +我们的版本库使用多个分支来管理文件和翻译。下面将详细解释每个特殊分支的目的和功能: + +### 分支概览 + +#### 1.`dev` + +`dev`分支运行 GitHub Actions 来处理同步任务。你可以在 [**`.github/workflows`**](https://github.com/TownSquareXYZ/ton-docs/tree/dev/.github/workflows) 目录中找到工作流配置: + +- **`sync-fork.yml`**:此工作流程从上游版本库同步文档。每天 00:00 运行。 +- **`sync-translations.yml`**:此工作流程将更新的翻译同步到相应的语言分支,以便在相应的语言网站上进行预览。 + +#### 2.`localization` + +该分支通过在 `dev` 分支上运行的 GitHub Actions 与上游版本库保持同步。它还用于更新我们打算提交到原始版本库的某些代码。 + +#### 3.`l10n_localization` + +该分支包含来自 `localization` 分支的所有修改和来自 Crowdin 的翻译。此分支中的所有修改都会提交到上游版本库。 + +#### 4.`[lang]_localization` + +这些分支指定用于特定语言的预览,如 "ko_localization "用于韩语,"ja_localization "用于日语。通过它们,我们可以预览不同语言的网站。 + +通过维护这些分支和使用 GitHub Actions,我们有效地管理了文档和翻译更新的同步,确保我们的多语言内容始终是最新的。 + +## 如何创建新的 Crowdin 项目 + +1. 登录您的 [**Crowdin 帐户**](https://accounts.crowdin.com/login)。 + +2. 点击菜单中的 `Create new project`。 + ![创建新项目](/img/localizationProgramGuideline/howItWorked/create-new-project.png) + +3. 设置项目名称和目标语言。您可以稍后在设置中更改语言。 + ![创建项目设置](/img/localizationProgramGuideline/howItWorked/create-project-setting.png) + +4. 转到刚刚创建的项目,选择`Integrations`,点击 `Add Integration` 按钮,搜索 `GitHub`,然后安装。 + ![install-github-integration](/img/localizationProgramGuideline/howItWorked/install-github-integration.png) + +5. 在 Crowdin 上配置 GitHub 集成之前,请先指定要上传到 Crowdin 的文件,以免上传不必要的文件: + + 1. 在**你的 GitHub 仓库**的根目录下创建一个**crowdin.yml**文件,输入以下基本配置: + + ```yml + project_id: + preserve_hierarchy: 1 + files: + - source: + translation: + ``` + + 2. 获取正确的配置值: + - **project_id**:在您的 Crowdin 项目中,转到 `Tools` 选项卡,选择 API,并在其中找到**project_id**。 + ![select-api-tool](/img/localizationProgramGuideline/howItWorked/select-api-tool.png) + ![projectId](/img/localizationProgramGuideline/howItWorked/projectId.png) + - **preserve_hierarchy**:是否在 Crowdin 服务器上保持 GitHub 中的目录结构。 + - **source** and **translation**:指定要上传到 Crowdin 的源文件路径(source)和翻译文件(translation)的输出路径。 + + 请参阅[**我们的官方配置文件**](https://github.com/TownSquareXYZ/ton-docs/blob/localization/crowdin.yml)了解示例。\ + 更多详情,请参阅[**Crowdin 配置文件**](https://developer.crowdin.com/configuration-file/)。 + +6. 配置 Crowdin 以连接到你的 GitHub 仓库: + 1. 单击 `Add Repository` 并选择 `Source and translation files mode`。 + ![选择集成模式](/img/localizationProgramGuideline/howItWorked/select-integration-mode.png) + 2. 连接 GitHub 账户并搜索要翻译的 repo。 + ![search-repo](/img/localizationProgramGuideline/howItWorked/search-repo.png) + 3. 选择左侧的分支,这将生成一个新的分支,Crowdin 将在该分支中发布翻译。 + ![设置分支](/img/localizationProgramGuideline/howItWorked/setting-branch.png) + 4. 选择将翻译更新到 GitHub 分支的频率。其他配置可保留默认设置,然后点击保存启用集成。 + ![频率-保存](/img/localizationProgramGuideline/howItWorked/frequency-save.png) + +详情请参考 [**GitHub 集成文档**](https://support.crowdin.com/github-integration/)。 + +7. 此外,你可以点击 "立即同步 "按钮,在需要时同步版本库和翻译。 + +## 术语表 + +### 什么是术语表? + +有时,人工智能无法识别不应翻译的特定术语。例如,当提到编程语言时,我们不希望词汇 "Rust" 变成其他意思。为了避免此类错误,我们使用词汇表来指导翻译。 + +通过**术语表**,您可以在一个地方创建、存储和管理项目特定术语,确保术语翻译的正确性和一致性。 + +您可以查看我们的 [**ton-i18n-glossary**](https://github.com/TownSquareXYZ/ton-i18n-glossary) 作为参考。 +![ton-i18n-glossary](/img/localizationProgramGuideline/howItWorked/ton-i18n-glossary.png) + +### 如何为新语言设置词汇表? + +大多数翻译平台都支持词汇表。在 Crowdin 中,设置词汇表后,每个术语都会在编辑器中显示为下划线词。将鼠标悬停在术语上,即可查看其翻译、语篇和定义(如果已在词汇表中提供)。 +![github-glossary](/img/localizationProgramGuideline/howItWorked/github-glossary.png) +![crowdin-glossary](/img/localizationProgramGuideline/howItWorked/crowdin-glossary.png) + +在 DeepL 中,只需上传您的词汇表,它就会在人工智能翻译过程中自动使用。 + +我们创建了[**词汇表程序**](https://github.com/TownSquareXYZ/ton-i18n-glossary),可自动上传更新内容。 + +在术语表中添加术语: + +1. 如果词汇表中已有英文术语,请找到要翻译的语言的相应行和列,输入译文并上传。 +2. 要上传新的词汇表,请克隆项目并运行: + + - `npm i` + - `npm run generate --` + +重复步骤 1 添加新术语。 + +**只需几步我们便完成了所有操作** + +## 如何利用人工智能翻译助手? + +人工智能翻译具有多种优势: + +- **增强一致性**:人工智能翻译以最新信息为基础,提供最准确和最新的翻译。 +- **速度与效率**:人工智能翻译瞬时完成,可实时处理大量内容。 +- **强大的可扩展性**:人工智能系统会不断学习和改进,随着时间的推移提高翻译质量。借助所提供的词汇表,人工智能翻译可根据不同资源库的具体需求进行定制。 + +在 Crowdin 中使用人工智能翻译(我们的项目中使用 DeepL): + +1. 在 Crowdin 菜单中选择 `Machine Translation` ,然后点击 DeepL 那一行上的`edit`。 + ![select-deepl](/img/localizationProgramGuideline/howItWorked/select-deepl.png) +2. 启用 DeepL 支持并输入 DeepL Translator API 密钥。 + > [如何获取 DeepL Translator API 密钥](https://www.deepl.com/pro-api?cta=header-pro-api) + +![config-crowdin-deepl](/img/localizationProgramGuideline/howItWorked/config-crowdin-deepl.png) + +3. 我们的 DeepL 设置使用定制的词汇表。有关上传词汇表的详细信息,请查阅 [**ton-i18n-glossary**](https://github.com/TownSquareXYZ/ton-i18n-glossary) 。 + +4. 在 repo 中,单击 Pre-translation(预翻译)并选择 via Machine Translation(通过机器翻译)。 + ![预翻译](/img/localizationProgramGuideline/howItWorked/pre-translation.png) + +5. 选择 DeepL 作为翻译引擎,选择目标语言,并选择要翻译的文件。 + ![预翻译配置](/img/localizationProgramGuideline/howItWorked/pre-translate-config.png) + +就是这样!现在您可以休息一下,等待预翻译完成。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-to-contribute.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-to-contribute.md new file mode 100644 index 0000000000..62c40e34ed --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/how-to-contribute.md @@ -0,0 +1,124 @@ +# 如何参与贡献 + +在我们努力使**TON 成为最成功的区块链**的过程中,使 TON 文档让全世界的人们都理解这一点至关重要。而本地化正是关键,我们很高兴**你**能加入这个旅程,并朝着相同的目标一起努力。 + +## 预备流程 + +**TownSquare Labs 本地化系统**向所有人开放!在开始贡献之前,您需要知晓: + +1. 登录或注册您的 [**Crowdin**](https://crowdin.com) 账户。 +2. 选择您要贡献的语言。 +3. 请熟悉[**如何使用 Crowdin**](/contribute/localization-program/how-to-contribute)指南和[**翻译风格指南**](/contribute/localization-program/translation-style-guide),了解使用技巧和最佳实践。 +4. 使用机器翻译辅助工作,但不要完全依赖机器翻译。 +5. 所有翻译结果均可在校对一小时后在网站上预览。 + +## 角色 + +以下是您在系统中可以担任的**角色**: + +- **语言协调员(Language Coordinator)** - 管理指定语言的项目功能。 +- **开发人员(Developer)** - 上传文件、编辑可翻译文本、连接集成和使用API。 +- **校对员(Proofreader)** - 翻译和批准字符串。 +- **翻译员(Translator)** - 翻译字符串并对他人添加的翻译进行投票。 + +我们的本地化项目托管在 [Crowdin](https://crowdin.com/project/ton-docs) 上。 + +:::info +Before you start contributing, **read the guidelines below** to ensure standardization and quality, making the review process much faster. + +## Side-by-Side Mode + +在 Crowdin 编辑器中,所有任务都以**side-by-side**模式执行。要启用此功能,请单击要处理的文件。在页面右上方,点击**Editor view**按钮,选择**side-by-side**模式,以获得更清晰的编辑器视图。\ +![并排模式](/img/localizationProgramGuideline/side-by-side.png) +::: + +### 语言协调员(Language Coordinator) + +- **翻译和批准字符串** +- **预翻译项目内容** +- **管理项目成员和加入请求** + ![manage-members](/img/localizationProgramGuideline/manage-members.png) +- **生成项目报告** + ![generate-reports](/img/localizationProgramGuideline/generate-reports.png) +- **创建任务** + ![create-tasks](/img/localizationProgramGuideline/create-tasks.png) + +### 开发人员(Developer) + +- **上传文件** +- **编辑可翻译文本** +- **连接集成**(例如,添加 GitHub 集成) + ![install-github-integration](/img/localizationProgramGuideline/howItWorked/install-github-integration.png) +- **使用 [Crowdin API](https://developer.crowdin.com/api/v2/)** + +### 校对员(Proofreader) + +作为**校对员**,您将处理带有**蓝色进度条**的文件。 +![proofread step1](/img/localizationProgramGuideline/proofread-step1.png) +点击文件进入编辑界面。 + +#### 让我们开始贡献吧 + +1. 确保您处于 [**side-by-side 模式**](#side-by-side-mode)。启用**Not Approved**过滤,查看需要校对的字符串。 + ![校对过滤器](/img/localizationProgramGuideline/proofread-filter.png) + +2. 请遵守这些规则: + - 选择带有**蓝色立方体图标**的字符串。检查每个翻译: + - 如果**正确**,请单击 ☑️ 按钮。 + - 如果**不正确**,请移至下一行。 + +![校对通过](/img/localizationProgramGuideline/proofread-approved.png) + +:::info +You can also review approved lines: + +1. 使用**Approved**过滤选项。 + +2. 如果已批准的翻译有问题,请单击 ☑️ 按钮将其还原为需要校对的状态。 + ::: + +3. 要移动到下一个文件,请单击顶部的文件名,从弹出窗口中选择新文件,然后继续校对。 + ![转到下一个](/img/localizationProgramGuideline/redirect-to-next.png) + +#### 预览你的成果 + +所有通过审核的内容都将在一小时内部署到预览网站上。请查看[**我们的仓库**](https://github.com/TownSquareXYZ/ton-docs/pulls),查看最新 PR 中的**preview**链接。 +![预览链接](/img/localizationProgramGuideline/preview-link.png) + +### 翻译员(Translator) + +作为一名**翻译员**,您的目标是确保翻译忠实且富有表现力,使其尽可能接近原意并易于理解。您的任务是使**蓝色进度条**达到 100%。 + +#### 开始翻译 + +请按照以下步骤成功完成翻译过程: + +1. 选择尚未达到 100% 翻译的文件。 + ![翻译选择](/img/localizationProgramGuideline/translator-select.png) + +2. 确保您处于 [**side-by-side 模式**](#side-by-side-mode)。通过**Untranslated**字符串进行过滤。 + ![翻译过滤器](/img/localizationProgramGuideline/translator-filter.png) + +3. 您的工作区有四个部分: + - **左上:** 根据源字符串输入您的翻译。 + - **左下:** 预览翻译文件。保持原始格式。 + - **右下:** Crowdin 建议的翻译。点击使用,但请核实准确性,尤其是链接。 + +4. 点击顶部的**Save**按钮保存翻译。 + ![translator save](/img/localizationProgramGuideline/translator-save.png) + +5. 要移动到下一个文件,请单击顶部的文件名,然后从弹出窗口中选择新文件。 + ![转到下一个](/img/localizationProgramGuideline/redirect-to-next.png) + +## 如何添加对新语言的支持 + +目前,我们在 Crowdin 中提供了所有需要的语言。如果您是社区管理,请按照以下步骤操作: + +1. 在 [TownSquareXYZ/ton-docs](https://github.com/TownSquareXYZ/ton-docs) 上添加一个名为 `[lang]_localization`(例如,韩语为 `ko_localization`)的新分支。 +2. **请联系此仓库的 Vercel 所有者**,将新语言添加到菜单中。 +3. 向开发分支创建 PR 请求。**请勿合并到开发分支**;这仅供预览之用。 + +完成这些步骤后,您就可以在 PR 请求中看到语言的预览。 +![ko preview](/img/localizationProgramGuideline/ko_preview.png) + +当您的语言准备好在 TON 文档中展示时,请创建一个issue,我们会将您的语言设置到生产环境中。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/overview.md new file mode 100644 index 0000000000..e4e80c86a7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/overview.md @@ -0,0 +1,39 @@ +# 本地化系统 + +本地化系统作为一个现代化的协同工作设施,旨在将 TON 相关的各种文档翻译成多种语言,使全球数十亿非英语使用者更容易访问该网站。 + +## 系统设计理念 + +![工作原理](/img/localizationProgramGuideline/localization-program.png) + +该本地化系统由**TON**最密切的合作伙伴之一[**TownSquare Labs**](https://github.com/TownSquareXYZ)发起**并积极维护**。 + +我们致力于为多语言社区的合作创建一个开放的基础设施,以**推动 TON 进入一个更好的阶段**,经过不懈的努力我们使其做到了: + +* **适用于多语言社区** + 程序支持多种语言,确保包容性,方便来自不同语言背景的用户使用。 + +* **自动化开发、集成和部署** + 通过利用自动化工具,该计划简化了开发、集成和部署流程,减少了人工操作,提高了所有本地化工作的效率和一致性。 + +* **将开发人员、翻译人员和校验人员的职责分离** + 我们的方法是将开发人员、翻译人员和校验人员的职责分离,让每个角色专注于各自的特定任务。这确保了高质量的翻译和顺畅的协作,而不会出现职责重叠或冲突。 + +* **社区贡献奖励** + 我们为在本地化过程中做出贡献的社区成员提供奖励。这既鼓励了积极参与,又奖励了那些帮助改进程序的人,培养了主人翁意识和社区精神。 + +* **集成先进的人工智能系统** + 先进的人工智能系统通过提供智能建议和自动执行重复性任务,不仅提高翻译准确性和效率,也确保能以更少的工作量获得高质量的结果。 + +这个项目不仅仅针对单一语言的用户,我们的目标是服务**全球开发者生态**。 + +## 致谢 + +我们非常感谢数以千计的社区成员,他们是翻译计划的重要组成部分。我们希望表彰我们的翻译员,并支持他们的职业道路。我们将在不久的将来创建排行榜以表彰我们的顶级翻译员,并创建一个包含翻译计划所有贡献者名单。 + +## 指南和资源 + +如果您正在为翻译计划做出贡献或考虑参与其中,请查看以下翻译指南: + +- [**翻译风格指南**](/contribute/localization-program/translation-style-guide) - 给翻译人员的说明和提示。 +- [**Crowdin 指南**](https://support.crowdin.com/online-editor/) - 关于使用 Crowdin 在线编辑器和 Crowdin 部分高级功能的深入指南。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/translation-style-guide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/translation-style-guide.md new file mode 100644 index 0000000000..a88783a9b8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/localization-program/translation-style-guide.md @@ -0,0 +1,198 @@ +# 翻译风格指南 + +此翻译风格指南包含一些最重要的指南、说明和翻译技巧,帮助我们对网站进行本地化。 + +本文档是一份一般性指南,并不特定于任何一种语言。 + +## 理解信息的精髓 + +当翻译 TON 文档内容时,避免直译。 + +重要的是翻译要抓住信息的本质。 这可能意味着改写某些短语,或者使用描述性翻译而不是逐字翻译内容。 + +不同的语言有不同的语法规则、约定和词序。 翻译时,请注意目标语言中句子的结构,避免按字面翻译英文源,因为这会导致句子结构和可读性差。 + +建议你阅读整个句子并对其进行调整以适应目标语言的惯例,而不是逐字翻译源文本。 + +## 正式与非正式 + +我们使用正式的称呼形式,这对所有访客来说始终是礼貌和适当的。 + +使用正式的称呼可以让我们避免听起来不官方或冒犯,并且无论访客的年龄和性别如何都可以通用。 + +大多数印欧语和亚非语语言使用特定性别的第二人称人称代词,以区分男性和女性。 在称呼用户或使用所有格代词时,我们可以避免假设访问者的性别,因为正式的称呼形式通常适用且一致,无论他们如何定位自己。 + +## 简单明了的词汇和意思 + +我们的目标是让尽可能多的人能够理解网站上的内容。 + +在大多数情况下,这可以通过使用易于理解的简短单词轻松实现。 如果你的语言中具有相同含义的某个单词有多种可能的翻译,那么最好的选择通常是清楚地反映含义的最短单词。 + +## 书写系统 + +所有内容都应使用适合你的语言的正确书写系统进行翻译,并且不应包含使用拉丁字符书写的任何单词。 + +翻译内容时,应确保翻译内容一致且不包含任何拉丁字符。 + +**以上规则不适用于通常不应翻译专有名词的语言。** + +## 翻译页面元数据 + +某些页面包含页面上的元数据,例如“title”、“lang”、“description”、“sidebar”等。 + +在将新页面上传到 Crowdin 时,我们隐藏了翻译人员不应翻译的内容,这意味着 Crowdin 中翻译人员可见的所有元数据都应该被翻译。 + +翻译源文本为“en”的任何字符串时,请特别注意。 这表示页面可用的语言,应翻译为[你的语言的 ISO 语言代码](https://www.andiamo.co.uk/resources/iso-language-codes/)。 这些字符串应始终使用拉丁字符而不是目标语言原生的书写脚本进行翻译。 + +如果你不确定要使用哪种语言代码,你可以查看 Crowdin 中的翻译记忆库,或在 Crowdin 在线编辑器的页面 URL 中找到你的语言的语言代码。 + +使用最广泛的语言的语言代码示例: + +- 英文 - en +- 简体中文 - zh-CN +- 俄语 - ru +- 韩语 - ko +- 波兰语 - pl +- 乌克兰语 - uk + +## 外部文章标题 + +一些字符串包含外部文章的标题。 我们的大多数开发人员文档页面都包含指向外部文章的链接,以供进一步阅读。 无论文章的语言如何,都需要翻译包含文章标题的字符串,以确保以他们的语言查看页面的访问者获得更一致的用户体验。 + +## Crowdin 警告 + +Crowdin 有一个内置功能,可以在翻译人员即将出错时发出警告。 在保存翻译之前,如果你忘记在译文中加上原文中的标签、翻译了不应翻译的元素、添加了多个连续的空格、忘记结尾标点等,Crowdin 会自动提醒你。 如果你看到这样的警告,请返回并仔细检查建议的翻译。 + +:::warning +永远不要忽略这些警告,因为它们通常意味着有问题,或者翻译缺少源文本的关键部分。 +::: + +## 简短与完整形式/缩写 + +网站上使用了很多缩写,例如 dApp、NFT、DAO、DeFi 等。 这些缩写通常用于英语,并且大多数网站访问者都熟悉它们。 + +由于它们通常没有其他语言的既定翻译,处理这些和类似术语的最佳方法是提供完整形式的描述性翻译,并在括号中添加英文缩写。 + +不要翻译这些缩写,因为大多数人不熟悉它们,而且本地化版本对大多数访问者来说没有多大意义。 + +如何翻译 dApp 的示例: + +- Decentralized applications (dapps) → 完整的翻译形式 (括号中为英文缩写) + +## 没有既定翻译的术语 + +某些术语在其他语言中可能没有既定翻译,并且以原始英语术语而广为人知。 这些术语主要包括较新的概念,如工作量证明、权益证明、信标链、质押等。 + +虽然翻译这些术语听起来不自然,但由于英文版本也常用于其他语言,因此强烈建议将它们翻译。 + +翻译它们时,请随意发挥创意,使用描述性翻译,或直接按字面翻译。 + +**大多数术语应该翻译而不是将其中一些保留英文的原因是,随着越来越多的人开始使用TON和相关技术,这种新术语将在未来变得更加普遍。 如果我们想让来自世界各地的更多人加入这个领域,我们需要以尽可能多的语言提供易于理解的术语,即使我们需要自行创建它。** + +## 按钮与行动号召 + +网站包含许多按钮,其翻译方式应与其他内容不同。 + +可以通过查看上下文屏幕截图、与大多数字符串连接或通过检查编辑器中的上下文(包括短语“button”)来识别按钮文本。 + +按钮的翻译应尽可能简短,以防止格式不匹配。 此外,按钮翻译应该是必要的,即呈现命令或请求。 + +## 翻译包容性 + +TON 文档的访问者来自世界各地和不同的背景。 因此,网站上的语言应该是中立的,欢迎所有人而不是排他性的。 + +其中一个重要方面是性别中立。 这可以通过使用正式的地址形式并避免在翻译中使用任何特定性别的词来轻松实现。 + +另一种形式的包容性是,尝试面向全球观众翻译,而不是面向任何国家、种族或地区。 + +最后,语言应该适合所有大众和年龄段的读者。 + +## 特定语言的翻译 + +翻译时,重要的是要遵循你的语言中使用的语法规则、约定和格式,而不是从源复制。 源文本遵循英语语法规则和约定,而这不适用于许多其他语言。 + +你应该了解你的语言规则并进行相应的翻译。 如果你需要帮助,请与我们联系,我们将帮助你找到一些有关如何在你的语言中使用这些元素的资源。 + +一些需要特别注意的例子: + +### 标点、格式 + +#### 大写 + +- 不同语言的大小写存在巨大差异。 +- 在英语中,通常将标题和名称、月份和日期、语言名称、假期等中的所有单词大写。 在许多其他语言中,这在语法上是不正确的,因为它们具有不同的大小写规则。 +- 一些语言也有关于人称代词、名词和某些形容词大写的规则,这些在英语中是不大写的。 + +#### 间距 + +- 正字法规则定义了每种语言的空格使用。 因为到处都使用空格,所以这些规则是最独特的,而空格是最容易误译的元素。 +- 英语和其他语言之间的一些常见间距差异: + - 计量单位和货币前的空格(例如 USD、EUR、kB、MB) + - 度数符号前的空格(例如°C、℉) + - 一些标点符号前的空格,尤其是省略号 (...) + - 斜杠前后的空格 (/) + +#### 列表 + +- 每种语言都有一套多样化和复杂的规则来编写列表。 这些可能与英语有很大不同。 +- 在某些语言中,每个新行的第一个单词需要大写,而在其他语言中,新行应该以小写字母开头。 许多语言对列表中的大小写也有不同的规则,具体取决于每行的长度。 +- 这同样适用于行项目的标点符号。 列表中的结束标点可以是句点 (.)、逗号 (,) 或分号 (;)具体取决于语言 + +#### 引号 + +- 语言使用许多不同的引号。 简单地从源中复制英文引号通常是不正确的。 +- 一些最常见的引号类型包括: + - “示例文本” + - ‘示例文本’ + - »示例文本« + - “示例文本” + - ‘示例文本’ + - «示例文本» + +#### 连字符和破折号 + +- 在英语中,连字符 (-) 用于连接单词或单词的不同部分,而破折号 (-) 用于表示范围或停顿。 +- 许多语言对使用连字符和破折号有不同的规则,应遵守这些规则。 + +### 格式 + +#### 数字 + +- 用不同语言书写数字的主要区别在于用于小数和千位的分隔符。 对于千数来说,这可以是句号、逗号或空格。 同样,一些语言使用小数点,而另一些语言使用小数点逗号。 + - 一些大数的例子: + - 英语 - 1,000.50 + - 西班牙语 - 1.000,50 + - 法语 - 1 000,50 +- 翻译数字时的另一个重要考虑因素是百分号。 它可以用不同的方式编写:100%、100 % 或 %100。 +- 最后,负数可以不同地显示,具体取决于语言:-100、100-、(100) 或 [100]。 + +#### 日期 + +- 在翻译日期时,有许多基于语言的考虑因素和差异。 这些包括日期格式、分隔符、大写和前导零。 全长日期和数字日期之间也存在差异。 + - 不同日期格式的一些示例: + - 英语(英国)(dd/mm/yyyy) – 1st January, 2022 + - 英语(美国)(mm/dd/yyyy) – January 1st, 2022 + - 中文 (yyyy-mm-dd) – 2022 年 1 月 1 日 + - 法语 (dd/mm/yyyy) – 1er janvier 2022 + - 意大利语 (dd/mm/yyyy) – 1º gennaio 2022 + - 德语 (yyyy/mm/dd) – 1. Januar 2022 + +#### 货币 + +- 由于格式、惯例和转换不同,货币转换可能具有挑战性。 作为一般规则,请保持货币与来源相同。 为了读者的利益,你可以在括号中添加你的当地货币和转换。 +- 用不同语言书写货币的主要区别包括符号位置、小数逗号与小数点、间距以及缩写与符号。 + - 符号放置:美元 100或 100 美元 + - 小数逗号和。小数点:100,50$ 或 100.50$ + - 间距:100美元或 100 美元 + - 缩写和符号:100$ 或 100 USD + +#### 计量单位 + +- 作为一般规则,请根据来源保留计量单位。 如果你所在的国家/地区使用不同的系统,你可以将转换包括在括号中。 +- 除了度量单位的本地化之外,注意语言处理这些单位的方式的差异也很重要。 主要区别在于数字和单位之间的间距,可以根据语言而有所不同。 这方面的示例包括 100kB 与 100 kB 或 50ºF 与 50ºF。 + +## 结论 + +翻译时尽量不要着急。 放轻松,玩得开心! + +感谢你参与翻译计划并帮助我们让更广泛的受众可以访问网站。 TON社区是全球性的,我们很高兴你也成为其中的一员! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/maintainers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/maintainers.md new file mode 100644 index 0000000000..74a53e21ea --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/maintainers.md @@ -0,0 +1,51 @@ +# 维护者 + +## 活跃团队 + +以下是按字母顺序排列的当前 TON 文档团队成员名单。 + +### Alex Golev + +TON 文档维护者和 TON 基金会的 DevRel + +- Telegram: [@alexgton](https://t.me/alexgton) +- GitHub: [Reveloper](https://github.com/Reveloper) + +### Gusarich + +Web3 开发者,[TON 开发社区](https://github.com/ton-community) 的贡献者,[TON Footsteps](https://github.com/ton-society/ton-footsteps) 和 TON 文档的维护者 + +- Telegram: [@Gusarich](https://t.me/Gusarich) +- GitHub: [Gusarich](https://github.com/Gusarich) + +### SwiftAdviser + +TON 基金会的开发者入职的管理者 + +- Telegram: [@SwiftAdviser](https://t.me/SwiftAdviser) +- GitHub: [SwiftAdviser](https://github.com/SwiftAdviser) + +## 致谢 + +TON 文档最初由 [tolya-yanot](https://github.com/tolya-yanot) 和 [EmelyanenkoK](https://github.com/EmelyanenkoK) 创建。 + +随着时间的推移,TON 文档得益于[众多外部贡献者](https://github.com/ton-community/ton-docs/graphs/contributors)的智慧和奉献。我们向他们每个人表示衷心的感谢。 + +然而,我们特别要感谢以下贡献者所做的重大贡献。他们极大地丰富了我们文档的质量和深度: + +- [akifoq](https://github.com/akifoq): 早期贡献 +- [amnch1](https://github.com/amnch1): 修复 +- [aSpite](https://github.com/aSpite): 内容 +- [awesome-doge](https://github.com/awesome-doge): 早期贡献 +- [coalus](https://github.com/coalus): 内容 +- [delovoyhomie](https://github.com/delovoyhomie): 内容 +- [krau5](https://github.com/krau5): 改进 +- [LevZed](https://github.com/LevZed): 内容 +- [ProgramCrafter](https://github.com/ProgramCrafter): 内容 +- [siandreev](https://github.com/siandreev): 内容 +- [SpyCheese](https://github.com/SpyCheese): 早期贡献 +- [Tal Kol](https://github.com/talkol): 早期贡献 +- [TrueCarry](https://github.com/TrueCarry): 内容 +- [xssnick](https://github.com/xssnick): 内容 + +我们真诚地感谢每一位贡献者,感谢他们使 TON 文档成为一个丰富可靠的平台资源。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/participate.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/participate.md new file mode 100644 index 0000000000..46e79043e9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/participate.md @@ -0,0 +1,49 @@ +# 贡献指南 + +这是用 TON 文档撰写教程的逐步指南。 + +:::tip 机会 +很幸运!这是一个改善 TON 生态系统的好机会。 +::: + +如果你决定编写教程,你可以因杰出的贡献而获得奖励: + +- **Special TON Footsteps NFT: 对 TON 最有价值的贡献** +- **TON 奖励: 作为报酬提供给被批准的高质量的贡献(如教程)** + +让我们看看你如何参与贡献过程。 + +## 决定你想写什么 + +找到或编写你想描述的材料。 + +1. 检查带有 `tutorial` 标签的 [TON Docs GitHub 上的问题列表](https://github.com/ton-community/ton-docs/issues)。 +2. *或者* 在 TON Docs GitHub 上用 [教程模板](https://github.com/ton-community/ton-docs/issues/new?assignees=\&labels=feature+%3Asparkles%3A%2Ccontent+%3Afountain_pen%3A\&template=suggest_tutorial.yaml\&title=Suggest+a+tutorial)写下你自己的想法。 + +## 描述问题以获得奖励 + +编写 *ton-footstep* 来获得你的贡献资金。 + +1. 更详细地了解 [TON Footsteps](https://github.com/ton-society/ton-footsteps) 计划。 + 1. **简而言之**:使用 [“探索者”封面文章](https://github.com/ton-society/ton-footsteps/issues/61) 作为示例。 +2. 编写[你自己的footstep](https://github.com/ton-society/ton-footsteps/issues/new/choose)来参与并等待批准。 +3. 在收到 `approved` 标签后开始编写你的教程。 + +## 编写教程 + +**准备工作**。尽量减少将来被要求更改的次数,从而_节省你的时间_: + +1. 遵循 [教程指南](/contribute/guidelines) 并使用 [示例教程结构](/contribute/sample-tutorial) 来检查。 +2. 阅读 [优秀教程原则](/contribute/principles-of-a-good-tutorial) 来编写出色的教程 :) +3. 可以从源代码中以 [铸造你的第一个 Jetton](/develop/dapps/tutorials/jetton-minter) 作为示例来找到灵感。 +4. **设置环境**。[检查教程](/contribute#online-one-click-contribution-setup) 是在本地或使用 Gitpod 运行你的 fork。 +5. **编写教程**。用到相应的环境,查看教程在你的 fork 上的样子。 +6. **发起 Pull Request**。打开 PR 以获取维护者的反馈。 +7. 合并! + +## 收到奖励 + +1. 在 TON Docs中的 PR 合并后,请在你的 ton-footsteps 任务中进行记录。 +2. 遵循指南 [如何完成 ton-footstep?](https://github.com/ton-society/ton-footsteps#how-to-complete-something-from-the-list) 完成footstep并获得奖励。 +3. 在你的任务中,你将被要求提供一个钱包,从而可以给你发送奖励。 +4. 获得奖励! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/guidelines.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/guidelines.md new file mode 100644 index 0000000000..1c95ee3163 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/guidelines.md @@ -0,0 +1,135 @@ +# 教程样式指南 + +所以你决定为 TON 文档写一个教程? + +我们很高兴你成为我们的贡献者之一!请仔细阅读以下指南,以确保您的教程遵循 TON 文档现有内容的样式和质量。 + +重要的是,您需要花些时间熟悉教程结构和如何使用标题。在提交自己的教程之前,请阅读我们的一些现有教程,并查看 [以前的拉取请求](https://github.com/ton-community/ton-docs/pulls?q=is%3Apr+is%3Aclosed)。 + +## 流程 + +:::info 重要 +在你开始写作之前,*请阅读下面的指南*!它们将帮助你确保达到标准化和质量水平,这将使审查过程更加迅速。 +::: + +另外,请参考我们提供的[**示例教程结构**](/contribute/tutorials/sample-tutorial)。 + +1. 首先,在 GitHub 上分叉然后克隆 [ton-docs](https://github.com/ton-community/ton-docs/) 的库,并在您的本地代码库中创建一个新分支。 +2. 书写您的教程时,请牢记质量和可读性!可以查看现有教程以明确您的目标。 +3. 当准备好提交审查时,[发起一个拉取请求](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)。我们将收到通知,并开始审查过程: + 1. **请尽一切努力提交您的教程的最终稿**。一些打字错误和语法修正是可以接受的,但如果在我们能够发布教程之前需要进行重大更改,审查和要求您进行必要更改的过程将会花费更多时间。 +4. 审查完您的提交后,并且您完成所有必要的更改后,我们将合并拉取请求并在 TON 文档上发布教程。此后不久,我们将与您联系安排付款! +5. 发布后,记得在社交媒体上**推广**您的教程![文档维护者](/contribute/maintainers)可以帮助扩大这种推广,只要您与我们合作。 + +总而言之,工作流程如下: + +1. ***分叉并克隆*** **`ton-docs`** 代码库 +2. ***编写并润色*** 您的教程 +3. ***提交拉取请求*** 进行审查 +4. ***进行其他必要的更改*** +5. 教程 ***合并并发布*** +6. ***在社交媒体上推广您的教程***! + +## 背景 + +在 "TON" 前加上 "THE" 的主要问题是,在开发 TON 文档和政策编辑期间,市场营销、供应商和开发人员等多个部门加入了讨论,以大写 "Blockchain"、"Ecosystem" 等词汇与 "TON" 结合使用,以创建一个单一系统、网络和品牌的强大形象。经过长时间的讨论,我们得出结论,为了推出强大的品牌形象,我们应该创建一个词汇和短语表,可以不使用 "THE" 并大写书写。如果可以大写,就不需要冠词。目前有两个这样的词组:TON Blockchain 和 TON Ecosystem。 + +对于其他 TON 模块名称,如 TON Connect、TON SDK、TON Grants等,取决于上下文。我们应用大写规则,但对于冠词规则则较为灵活。如果组件名称单独存在,最好不用冠词。然而,如果它与普通名词结合,如 TON Connect protocol,则需要冠词,因为它指的是实体协议。 + +至于其他词组,如 "TON + 名词"(例如,"the TON world"、"the TON community" 等),我们不限制使用冠词,因为我们期望能看到这样的一个结合。 + +## 一般提示 + +- **不要复制粘贴现有内容**。剽窃是一个严重的问题,我们不会容忍。如果教程受到现有内容的启发,请给出参照并链接到它。链接到其他教程/资源时,请尽可能使用 TON 文档的资源。 +- **在 PR 中包含演示视频或视频内容**,方法是上传到 Google Drive。 +- **必须清楚地解释如何从水龙头获得账户资金**,包括哪个账户能拿到资金,从哪里,以及为什么。不要假设学习者可以自行完成这项任务! +- **显示示例输出**,以终端片段或屏幕截图的形式,以帮助学习者了解预期效果。记得要修剪长输出。 +- **采取错误驱动的方法**,故意遇到错误,教学习者如何调试。例如,如果您需要注资一个账户才能部署合约,请先尝试在不资助的情况下部署,观察返回的错误,然后修复错误(通过注资账户)并再次尝试。 +- **添加潜在的错误和故障排除**。当然,教程不应列出所有可能的错误,但应努力捕捉重要的或最常见的错误。 +- **使用 React 或 Vue** 进行客户端开发。 +- **在提交 PR 之前,首先自行运行代码**,以避免其他明显的错误,并确保其按预期工作。 +- **避免在教程之间包含对不同来源的外部/交叉链接**。如果您的教程较长,我们可以讨论如何将其变成更长的课程或路径。 +- **提供图片或截图** 来说明复杂过程,如有需要。 +- 将您的图片上传到 learn-tutorials 库的 `static` 目录——**不要** 使用外部网站的热链接,因为这可能导致图片损坏。 +- **图片链接必须以 markdown 格式呈现**,您 **只能** 使用库中 `static` 目录的原始 GitHub URL:`![您图片的名称](https://raw.githubusercontent.com/ton-community/ton-docs/main/static/img/tutorials/<您图片的文件名>.png?raw=true)` + - 记住在 URL 的末尾添加 `?raw=true`。 + +## 如何构建您的教程 + +:::info 示例教程结构 +您可以随时查看[示例教程结构](/contribute/tutorials/sample-tutorial)进行了解。 +::: + +- **标题** 应该直接明了,概括教程的目标。不要在文档内以标题形式添加教程标题;而应使用 markdown 文档文件名。 + - *例如*:如果您的教程标题为"*Step by step guide for writing your first smart contract in FunC*",文件名应为:\ + `step-by-step-guide-for-writing-your-first-smart-contract-in-func.md` +- 包含一个**简介**部分,用来解释*为什么*这个教程很重要,以及教程的背景是什么。不要假设这是显而易见的。 +- 包含一个**必要条件**部分,用来解释任何要求*预先掌握的知识*或需要首先完成的其他现有教程,其他所需的代币等。 +- 包含一个**要求**部分,用来解释在开始教程之前必须安装的任何*技术程序*,以及教程不会涵盖的内容,如 TON 钱包扩展、Node.js 等。不要列出教程中将安装的包。 +- 使用**子标题**(H2: ##)来分构教程正文。使用子标题时要记住目录,并尽量保持重点。 + - 如果子标题下的内容很短(例如,只有一个段落和一个代码块),考虑使用加粗文本而不是子标题。 +- 包含一个**结论**部分,总结所学内容,强化关键点,同时也为学习者完成教程表示祝贺。 +- (***可选***)包含一个**接下来**部分,指向后续教程或其他资源(项目、文章等)。 +- (***可选***)在最后包含一个**关于作者**部分。您的简介应包括您的 GitHub 个人资料链接(将包含您的姓名、网站等)和您的 Telegram 个人资料链接(以便用户可以联系/标记您,从而获得帮助和提问题)。 +- 如果您在编写此教程时参考了其他文档、GitHub 库或其他教程,**必须** 存在一个**参考资料**部分。可实现的话,就通过添加他们的名称和文档链接来致谢(如果不是数字文档,请包括 ISBN 或其他参考方式)。 + +## 样式指南 + +- **写作措辞 -** 教程由社区贡献者为他们的同行撰写。 + - 鉴于此,我们建议在整个教程中创造一种包容和互动的语调。使用“我们”、“我们的”这样的词语。 + - *例如*:"我们已经成功部署了我们的合约。" + - 提供直接指导时,可以自由使用“你”、“你的”等。 + - *例如*:"*你的文件应该看起来像这样:*" + +- **在您的教程中正确使用 Markdown**。参考 [GitHub 的 markdown 指南](https://guides.github.com/features/mastering-markdown/) 以及 [示例教程结构](/contribute/tutorials/sample-tutorial)。 + +- **不要使用预格式化文本进行强调**,*例如*: + - ❌ "TON 计数器 `智能合约` 名为 `counter.fc`" 是不正确的。 + - ✅ "TON 计数器 **智能合约** 名为 `counter.fc`" 是正确的。 + +- **不要在节标题中使用任何 markdown 格式**,*例如*: + - ❌ # **简介** 是不正确的。 + - ✅ # 简介 是正确的。 + +- **解释你的代码!** 不要只让学习者盲目地复制和粘贴。 + - 函数名称、变量和常量 **必须** 在整个文档中保持一致。 + - 使用代码块开头的注释来显示代码所在的路径和文件名。*例如*: + + ```jsx + // test-application/src/filename.jsx + + import { useEffect, useState } from 'react'; + + ... + ``` + +- **选择合适的语言** 用于代码块语法高亮! + - 所有代码块 *必须* 有语法高亮。如果您不确定要应用哪种类型的语法高亮,请使用 **\`\`\`text**。 + +- **不要将代码块语法用于预格式化文本**,*例如*: + - ❌ \`filename.jsx\` 是不正确的。 + - ✅ \`filename.jsx\` 是正确的。 + +- **您的代码块应该有较好的注释**。注释应该简短(通常是两到三行)且有效。如果您需要更多空间来解释一段代码,请在代码块外进行。 + +- **记得在所有代码块前后留一个空行**。\ + *例如*: + +```jsx + +// test-application/src/filename.jsx + +import { useEffect, useState } from 'react'; + +``` + +- **使用 linter 和 prettifier** 在将代码粘贴到代码块之前。对于 JavaScript/React,我们推荐使用 `eslint`。对于代码格式化,请使用 `prettier`。 +- **避免过度使用项目符号**、编号列表或复杂的文本格式。使用 **粗体** 或 *斜体* 强调是允许的,但应保持最少。 + +# **应用设置** + +- Web3 项目通常会包含几个现有的代码库。编写教程时,请考虑到这一点。在可能的情况下,提供一个 GitHub 库作为学习者入门的起点。 +- 如果您*不*使用 GitHub 库来包含教程中使用的代码,请记得向读者解释如何创建文件夹以保持良好的代码组织。 + *例如*:`mkdir example && cd example` +- 如果使用 `npm init` 来初始化项目目录,请解释提示或使用 `-y` 标志。 +- 如果使用 `npm install`,请采用 `-save` 标志。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/principles-of-a-good-tutorial.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/principles-of-a-good-tutorial.md new file mode 100644 index 0000000000..eed0522aa6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/principles-of-a-good-tutorial.md @@ -0,0 +1,27 @@ +# 优秀教程的原则 + +这些原则最初由 [talkol](https://github.com/talkol) 提出: + +- [TON Footstep #7 上的原始评论](https://github.com/ton-society/ton-footsteps/issues/7#issuecomment-1187581181) + +以下是这些要点的总结,供新贡献者参考。 + +## 原则 + +1. 整个流程应在用户的客户端上运行。不应涉及任何第三方服务。你需要做的是让用户可以简单地克隆库并立即运行它。 + +2. README 应该非常详细。不要假设用户知道任何事情。如果教程需要,它还应该解释如何在你的设备上安装 FunC 编译器或轻客户端。你可以从这个文档中的其他教程复制这些内容。 + +3. 如果可以,库应该包含用于合约的全部源代码,以便用户可以对标准代码进行小的更改。例如,Jetton 智能合约允许用户尝试自定义行为。 + +4. 可以的话,尽量创建一个用户友好的界面,允许用户部署或运行项目,而无需下载代码或配置任何东西。请注意,这仍然应该是单独的,并从 GitHub Pages 上获取服务,以便在用户的设备上100%运行客户端。示例:https://minter.ton.org/ + +5. 向用户解释每个字段选择的含义,并用最佳的例子来进行解释。 + +6. 解释所有需要了解的关于安全的知识。你必须解释足够多,以便创作者不会犯错误并创建危险的智能合约/机器人/网站——你正在教他们最佳的安全实践。 + +7. 理想情况下,库应该包含编写好的测试,向读者展示如何在你的教程背景下最好地实现它们。 + +8. 库应该有易于理解的编译/部署脚本。用户能够只要输入 `npm install` 就能使用它们。 + +9. 有时一个 GitHub 库就足够了,不需要写一篇完整的文章。只需一个 README,里面包含了库中你需要的所有代码。在这种情况下,代码应该有良好的注释,以便用户可以轻松阅读和理解它。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/sample-tutorial.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/sample-tutorial.md new file mode 100644 index 0000000000..a8af597053 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/contribute/tutorials/sample-tutorial.md @@ -0,0 +1,129 @@ +# 示例教程结构 + +## 简介 + +简介的标题 **必须** 为 H2: `## 简介` + +这一部分是用来解释这个教程的背景和重要性,我们将在本教程中构建和学习什么。 + +- 像你对五岁小孩解释一样来阐述这一部分 (**[ELI5](https://www.dictionary.com/e/slang/eli5/)**) +- 用最多5-6行来解释这一切。 + +*例如:* + +> 智能合约只是一个在TON区块链上运行的计算机程序,或者更具体地说,在其[TVM](/learn/tvm-instructions/tvm-overview)(*TON虚拟机*)上运行。合约由代码(*编译的TVM指令*)和数据(*持久状态*)组成,这些都存储在TON上的某个地址。 + +## 必要条件 + +必要条件标题 **必须** 为 H2: `## 必要条件` + +这一部分是用来解释开始本教程前任何需要预先掌握的知识或需要先完成的教程。如果需要任何的代币—要在这里提及。 + +*例如:* + +> 在这个教程中,我们将在测试网上铸造Jetton。在我们继续之前,请确保你的[测试网](/develop/smart-contracts/environment/testnet)钱包有足够的余额。 + +## 要求 + +要求标题 **必须** 为 H2: `## 要求` + +**可选 :** 如果你的教程有任何视频内容,请在这一部分嵌入。 + +在开始教程之前需要安装的任何技术程序,以及本教程不会涉及的内容(`TON钱包扩展`、`node`等)。请不要将要安装的程序包在教程中列出。 + +*例如:* + +- 我们需要在本教程中使用TON钱包扩展;可以从[这里](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)安装。 +- 确保已安装NodeJS 12.0.1+。 + +## 教程正文 + +- 请不要使用“教程正文”作为标题,请使用与材料相关的自己的标题。 + - 如果你想不出别的,使用“开始”也是可以接受的😉 +- 添加文本内容来引导读者通过你的教程,并***记得在提交教程之前校对内容***,以避免拼写和语法错误。 + - [Grammarly](http://grammarly.com)是一个可以帮助你避免语法错误的免费程序。 + +### 关键点 + +- 不要使用“教程正文”作为标题! + +- \*\*保持所有子标题在H3,\*\*不要使用H4或更低。 + - 在Markdown语法中,两个井号用于H2标题: ## + - 三个井号用于H3标题: ### + +- 只在代码块中添加必要的注释。***不要***在终端输入代码块中添加#样式的注释。 + +- 添加所有相关的代码块: + - ## Markdown语法的代码块由代码块开始和结束时的三个反引号组成。同时,请确保在所有代码块的反引号前后都有一个新行。*例如*: + \`js + const testVariable = 'some string'; + someFunctionCall(); + \` + + - 所有代码块***必须***有语法高亮类型。如果不确定,使用\\`\`\`text。 + + - \\`\`\`text用于终端输出、终端命令和纯文本。 + + - \`javascript *或* `js可用于任何JavaScript代码。 + + - \`typescript或`ts可用于任何TypeScript代码。 + + - \\`\`\`jsx用于ReactJS代码。 + + - \\`\`\`cpp用于Func代码。 + + - 使用\\`\`\`graphql突出显示GraphQL语法。 + + - 使用\`json突出显示有效的JSON。(对于无效的JSON示例,请使用\`text。) + + - \\`\`\`bash应*仅*用于需要#样式注释的代码块。这必须小心进行,因为在许多情况下,#字符将呈现为markdown标题。如果发生这种情况,通常目录会受到影响。 + +- 不要使用`预格式化文本`来强调;而是只使用**粗体**或*斜体*文本。 + +- 添加图片或代码块以反映预期的终端输出。 + +- 采取错误驱动的方法来编写你的教程。添加常见错误和故障排除步骤。*例如:* + +> **由于执行`node deploy:testnet`命令时出错,无法连接到Testnet。** +> +> 让我们看看一些常见原因: + +- 确保你在`.env`中生成的测试网钱包有足够的资金。如果没有,请从水龙头赠送处添加一些测试网代币。 +- 如果你仍然遇到同样的问题,请向[Dev Chat](https://t.me/TonDev_eng/)中的开发者求助。 + +> + +## 结论 + +结论标题 **必须** 为 H2: `## 结论` + +这一部分应总结在教程中学到的内容,强调关键点,并祝贺学习者完成教程。使用最多5-6行。 +*例如*: + +> 我们创建了一个具有计数功能的简单新FunC合约。然后我们在链上构建并部署它,最后通过调用getter和发送消息与它进行交互。 + +请记住,这段代码不适用于生产;如果你想将其部署到主网,还有一些其他事项需要考虑,例如,如果代币在市场上挂牌,就禁用转移的方法等等。 + +> + +## 参阅 + +下一步标题 **必须** 为 H2: `## 参阅` + +使用这一部分来解释完成本教程后接下来可以做什么以继续学习。可以添加与本教程相关的推荐项目和文章。如果你正在进行任何其他高级教程,可以在这里简要提及。通常,只有来自docs.ton.org的相关页面会放在这里。 + +## 关于作者 *(可选)* + +关于作者标题 **必须** 是 H2: `## 关于作者` + +保持简短。最多一两行。你可以包括你的GitHub个人资料链接+ Telegram个人资料。请避免在这里添加你的LinkedIn或Twitter。 + +## 参考资料 *(可选)* + +参考资料标题 **必须** 是 H2: `## 参考资料` + +如果你在编写本教程时从其他文档、GitHub库或现有教程中获得了任何帮助,则***必须*** 有这一部分。 + +通过添加它们的名称和文档链接来致敬来源。 + +如果不是数字文档,请添加ISBN或其他形式的参考。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/compile.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/compile.md new file mode 100644 index 0000000000..40b3ee38a7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/compile.md @@ -0,0 +1,201 @@ +# 在 TON 上编译和构建智能合约 + +以下是构建智能合约的库和库列表。 + +**简而言之:** + +- 在大多数情况下,使用Blueprint SDK就足够了。 +- 如果您需要更低级别的方法,可以使用ton-compiler或func-js。 + +## Blueprint + +### 概览 + +TON区块链的开发环境,用于编写、测试和部署智能合约。在[Blueprint git库](https://github.com/ton-community/blueprint)中了解更多信息。 + +### 安装 + +在终端运行以下命令以创建一个新项目,并按照屏幕上的指示操作: + +```bash +npm create ton@latest +``` + +  + +### 特点 + +- 构建、测试和部署智能合约的简化工作流程 +- 使用您最喜欢的钱包(例如Tonkeeper)轻松部署到主网/测试网 +- 在一个独立的区块链中快速测试多个智能合约,该区块链在进程中运行 + +### 技术栈 + +1. 使用https://github.com/ton-community/func-js编译FunC(无CLI) +2. 使用https://github.com/ton-community/sandbox测试智能合约 +3. 使用[TON Connect 2](https://github.com/ton-connect)、[Tonhub wallet](https://tonhub.com/)或`ton://`深链接部署智能合约 + +### 要求 + +- [Node.js](https://nodejs.org)的最新版本,如v18,使用`node -v`验证版本 +- 支持TypeScript和FunC的IDE,如[Visual Studio Code](https://code.visualstudio.com/),配备[FunC插件](https://marketplace.visualstudio.com/items?itemName=tonwhales.func-vscode) + +### 如何使用? + +- [观看DoraHacks演示,了解使用blueprint的演示](https://www.youtube.com/watch?v=5ROXVM-Fojo)。 +- 在[Blueprint库](https://github.com/ton-community/blueprint#create-a-new-project)中阅读详细的说明。 + +## ton-compiler + +### 概览 + +打包的FunC编译器,用于TON智能合约: + +- GitHub:[ton-community/ton-compiler](https://github.com/ton-community/ton-compiler) +- NPM:[ton-compiler](https://www.npmjs.com/package/ton-compiler) + +### 安装 + +```bash npm2yarn +npm install ton-compiler +``` + +### 特点 + +- 多个FunC编译器版本 +- 无需安装和编译TON +- 程序化和CLI接口 +- 适用于cell测试 + +### 如何使用 + +这个包在项目中添加了`ton-compiler`二进制文件。 + +FunC编译是一个多阶段过程。其中之一是将Func编译为Fift代码,然后将其编译为二进制表示。Fift编译器已经内置了Asm.fif。 + +FunC标准库已被捆绑,但可以在运行时禁用。 + +#### 控制台使用 + +```bash +# Compile to binary form (for contract creation) +ton-compiler --input ./wallet.fc --output ./wallet.cell + +# Compile to fift (useful for debuging) +ton-compiler --input ./wallet.fc --output-fift ./wallet.fif + +# Compile to binary form and fift +ton-compiler --input ./wallet.fc --output ./wallet.cell --output-fift ./wallet.fif + +# Disable stdlib +ton-compiler --no-stdlib --input ./wallet.fc --output ./wallet.cell --output-fift ./wallet.fif + +# Pick version +ton-compiler --version "legacy" --input ./wallet.fc --output ./wallet.cell --output-fift ./wallet.fif +``` + +#### 程序化使用 + +```javascript +import { compileContract } from "ton-compiler"; +let result = await compileContract({ code: 'source code', stdlib: true, version: 'latest' }); +if (result.ok) { + console.log(result.fift); // Compiled Fift assembler + console.log(result.cell); // Compiled cell Buffer +} else { + console.warn(result.logs); // Output logs +} +``` + +## func-js + +### 概览 + +_Cross-platform_绑定TON FunC编译器。 + +它比ton-compiler更低级,所以只有在ton-compiler不适用时才使用它。 + +- GitHub:[ton-community/func-js](https://github.com/ton-community/func-js) +- NPM:[@ton-community/func-js](https://www.npmjs.com/package/@ton-community/func-js) + +### 安装 + +```bash npm2yarn +npm install @ton-community/func-js +``` + +### 特点 + +- 无需编译或下载FunC二进制文件 +- 在Node.js和**WEB**中都可工作(需要WASM支持) +- 直接编译为带有代码cell的BOC +- 返回汇编版本用于调试目的 +- 不依赖文件系统 + +### 如何使用 + +在内部,这个包使用了FunC编译器和Fift解释器组合成单个编译为WASM的库。 + +简单架构: + +```bash +(your code) -> WASM(FunC -> Fift -> BOC) +``` + +内部库的源代码可以在[这里](https://github.com/ton-blockchain/ton/tree/testnet/crypto/funcfiftlib)找到。 + +### 使用示例 + +```javascript +import {compileFunc, compilerVersion} from '@ton-community/func-js'; +import {Cell} from 'ton'; + +async function main() { + // You can get compiler version + let version = await compilerVersion(); + + let result = await compileFunc({ + // Entry points of your project + entryPoints: ['main.fc'], + // Sources + sources: { + "stdlib.fc": "", + "main.fc": "", + // Rest of the files which are included in main.fc if some + } + }); + + if (result.status === 'error') { + console.error(result.message) + return; + } + + // result.codeBoc contains base64 encoded BOC with code cell + let codeCell = Cell.fromBoc(Buffer.from(result.codeBoc, "base64"))[0]; + + // result.fiftCode contains assembly version of your code (for debug purposes) + console.log(result.fiftCode) +} +``` + +请注意,项目中使用的所有FunC源文件内容都应传递给`sources`,包括: + +- 入口点 +- stdlib.fc(如果您使用它) +- 所有包含在入口点中的文件 + +### 经TON社区验证 + +- [ton-community/ton-compiler](/develop/smart-contracts/sdk/javascript#ton-compiler) — 用于TON智能合约的现成FunC编译器。 +- [ton-community/func-js](/develop/smart-contracts/sdk/javascript#func-js) — TON FunC编译器的跨平台绑定。 + +### 第三方贡献者 + +- [grozzzny/ton-compiler-groz](https://github.com/grozzzny/ton-compiler-groz) — TON FunC智能合约编译器。 +- [Termina1/tonc](https://github.com/Termina1/tonc) — TONC(TON编译器)。使用WASM,非常适合Linux。 + +## 其他 + +- [disintar/toncli](https://github.com/disintar/toncli) — 最受欢迎的方法之一。您甚至可以在Docker中使用它。 +- [tonthemoon/ton](https://github.com/tonthemoon/ton) — _(封闭测试)_一行TON二进制安装程序。 +- [delab-team/tlbcrc](https://github.com/delab-team/tlbcrc) — 包和CLI,根据TL-B方案生成操作码。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/README.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/README.mdx new file mode 100644 index 0000000000..936cefb801 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/README.mdx @@ -0,0 +1,60 @@ +import Button from '@site/src/components/button' + +# 什么是Hacktoberfest? + +
+ tlb structure +
+ +[Hacktoberfest](https://hacktoberfest.digitalocean.com/) 是一个为期一个月的庆祝活动,旨在为_开源项目_、它们的_维护者_以及整个_贡献者_社区提供庆祝活动。每年10月,开源维护者会给予新贡献者额外的关注,指导开发者完成他们的第一个拉取请求。 + +对于TON社区来说,这是一个共同帮助生态系统成长的时刻,所以让我们加入我们的 **Hack-TON-berfest** 派对,成为_全年第一开源生态系统_吧! + +## 如何参与? + +2022年Hacktoberfest的规则如下: + +- **Hacktoberfest对所有人开放**! +- 9月26日至10月31日之间随时可以注册 +- 可以在任何GITHUB或GITLAB项目中发起拉取请求: + - [TON生态系统项目列表](/hacktonberfest) + - [GitHub上的项目列表](https://github.com/topics/hacktoberfest) +- 在10月1日至10月31日期间被接受了**4**个拉取/合并请求 +- 前40,000名完成Hacktoberfest的参与者(维护者和贡献者)可以在两个奖品之间选择:以他们的名义种植的树或Hacktoberfest 2022 T恤。(_来自Hacktoberfest社区_) +- 任何参与TON生态系统项目的参与者(维护者和贡献者)都将收到[**限量版Hack-TON-berfest NFT**](#what-the-rewards)。(_来自TON基金会_) + +对于TON中的每个人来说,这是一个推动整个生态系统成长并从TON基金会获得酷炫奖励的机会。让我们一起努力! + +## 奖励是什么? + +为了激励社区为TON生态系统的开源项目做出贡献,你将能够从TON基金会获得特殊的奖励。每位参与者都将获得**限量版Hack-TON-berfest NFT**成就,作为参与的证明: + +
+ +
+ +:::info 重要信息! +TON基金会将于11月为提交给[@toncontests_bot](https://t.me/toncontests_bot)的所有钱包地址铸造一个系列。这将在计算和验证所有贡献结果之后进行。 +::: + +你有足够的时间参与这个活动。让我们与来自全世界的成千上万的贡献者一起构建未来的去中心化网络! + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-contributor.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-contributor.md new file mode 100644 index 0000000000..756ead116f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-contributor.md @@ -0,0 +1,33 @@ +# 作为贡献者参与 + +要成为获得限量版 *Hack-TON-berfest NFT* 的贡献者,请设置你自己的 [TON 钱包](https://ton.org/wallets) 并验证你的 GitHub 账户。 + +## 开始你的旅程 + +1. 从 [ton.org/wallets](https://ton.org/wallets) 页面设置任意钱包。(例如,[TON Wallet 扩展](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)。) +2. 请将你的钱包地址提供给 Telegram 中的 [@toncontests_bot](https://t.me/toncontests_bot)。 +3. 在同一个机器人中验证你的 GitHub 账户。 + +完成这些步骤后,你就准备好完成贡献并领取 [限量版Hack-TON-berfest NFT](/contribute/hacktoberfest/#what-the-rewards) 即可。 + +欢迎加入俱乐部,这只是个开始! + +## 第一次参与开源项目? + +Hacktoberfest 是首次尝试开源贡献的绝佳场所。有很多关于如何开始的直播、帖子、指南和讨论。你将会认识许多在这个月开始他们旅程的人们! + +- [初学者关于 Hacktoberfest 的基本信息](https://hacktoberfest.com/participation/#beginner-resources) +- [进行首次贡献的指南](https://dev.to/codesandboxio/how-to-make-your-first-open-source-contribution-2oim),作者 Ceora Ford +- [练习工作流程来进行你的首次贡献](https://github.com/firstcontributions/first-contributions) +- [克服在开源贡献中的冒名顶替综合征](https://blackgirlbytes.dev/conquering-the-fear-of-contributing-to-open-source) + +## 我如何为 TON 做贡献? + +TON 生态系统拥有几个组织和代码库: + + + + 寻找贡献者的项目列表 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-maintainer.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-maintainer.md new file mode 100644 index 0000000000..e8b8355dc0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/hacktoberfest-2022/as-maintainer.md @@ -0,0 +1,39 @@ +# 作为维护者参与 + +Hacktoberfest 活动是一年中获得社区支持的最佳时机! + +如果你的代码库与TON生态系统相关,许多贡献者将会对其感兴趣。让我们帮助他们迅速投入到你的项目中! + +## 准备参与派对 + +为了能正确借助贡献者的力量,你需要拥有一个状况良好的代码库。 + +遵循以下最佳实践,为你的项目准备贡献: + +1. 在你的代码库中添加“hacktoberfest”主题,以**参与HACKTOBERFEST**并表明你正在寻求人们的贡献。 +2. 在你的GitHub或GitLab项目中,将“hacktoberfest”标签应用于你希望贡献者帮助解决的问题。 +3. 请阅读并使用TON社区提供的[对新开源维护者的基本提示](https://blog.ton.org/essential-tips-for-new-open-source-maintainers)。 +4. 通过合并、留下整体认可的审查,或添加“hacktoberfest-accepted”标签,来接受合法的拉取/合并请求。 +5. 通过将其标记为“垃圾邮件”,拒绝你收到的任何垃圾请求,并关闭或标记任何其他无效的贡献使其“无效”。 + +这里是一个完整代码库的示例:[ton-community/ton-compiler](https://github.com/ton-community/ton-compiler) + +之后,就可以将你的代码库添加到列表中。 + +## 维护者的奖励 + +作为TON生态系统中的代码库维护者,你将能够获得两种类型的奖励: + +1. [Hacktoberfest 奖励套件](https://hacktoberfest.com/participation/#maintainers)(*见维护者奖励*) +2. [限量版Hack-TON-berfest NFT](/contribute/hacktoberfest/#what-the-rewards)(*请在[@toncontests_bot](https://t.me/toncontests_bot)中注册钱包地址*) + +## 如何加入并被列入列表? + +要参加Hack-TON-berfest,请按照此链接操作: + + + + 将代码库添加到列表中 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/mining.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/mining.md new file mode 100644 index 0000000000..73d45a29ea --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/mining.md @@ -0,0 +1,199 @@ +# TON 挖矿指南 + +:::warning 已弃用 +此信息可能已过时,不再有效。可以忽略它。 +::: + +## 简介 + +本文档提供了使用PoW提供者挖掘Toncoin的过程介绍。请访问[ton.org/mining](https://ton.org/mining)以获取TON挖矿的最新状态。 + +## 快速开始 + +立即开始挖矿: + +1. 获取[适用于挖矿的计算机](#hardware)。 +2. 安装[Ubuntu](https://ubuntu.com) 20.04桌面或服务器发行版。 +3. 在`lite`模式下安装[mytonctrl](https://github.com/igroman787/mytonctrl#installation-ubuntu)。 +4. 运行`mytonctrl`中的`emi`命令,检查您的硬件和[预期挖矿收入](#faq-emi)。 +5. 如果您还没有钱包地址,请使用其中一个[钱包](https://www.ton.org/wallets)创建`钱包地址`。 +6. 通过在`mytonctrl`中执行`set minerAddr "..."`命令,将您的`钱包地址`定义为挖矿目标。 +7. 从[ton.org/mining](https://ton.org/mining)上提供的列表中选择一个giver合约,并通过在`mytonctrl`中执行`set powAddr "..."`命令设置您的miner以挖掘它。 +8. 通过在`mytonctrl`中执行`mon`命令开始挖矿。 +9. 检查您计算机上的CPU负载;名为`pow-miner`的进程应使用您大部分的CPU。 +10. 等待好运;第4步的输出应该告诉您挖到一个区块的大致几率。 + +## 基础知识 + +Toncoin通过所谓的`PoW Givers`(工作量证明提供者)进行分发,它们是分配了一定数量TON的智能合约。目前,TON网络上有10个活跃的PoW giver。Giver每次分发100 TON的币。为了接收这样一个块,您的计算机需要解决giver发布的复杂数学挑战,并且要尽可能快地完成;您将与其他矿工竞争100 TON的奖励。如果有人在您之前解决了问题,您的机器所做的所有工作都将作废,新的一轮/竞赛开始。 + +重要的是要理解,挖矿的收益不是随着机器工作而“逐渐增加”的,而是每成功解决一个giver挑战就以100 TON的批次形式出现。这意味着,如果您的机器有在24小时内计算出一个区块的10%机会(见[快速开始](#quickStart)的第4步),那么您可能需要等待大约10天时间才能获得100 TON的奖励。 + +挖矿过程在很大程度上由`mytonctrl`自动化。关于挖矿过程的详细信息可以在[PoW givers](https://www.ton.org/#/howto/pow-givers)文档中找到。 + +## 高级 + +如果您认真对待挖矿并希望操作多台机器/矿场,那么您真的需要了解TON以及挖矿的工作原理;请参阅[HOWTO](https://ton.org/#/howto/)部分以获取深入信息。以下是一些通用建议: + +- **应该**在单独的机器上运行您自己的节点/轻服务器;这将确保您的矿场不依赖于可能发生故障或无法及时处理您的查询的外部轻服务器。 +- **不要**用`get_pow_params`查询轰炸公共轻服务器,如果您有高频率轮询giver状态的自定义脚本,您**必须**使用您自己的轻服务器。违反此规则的客户端可能会导致其IP在公共轻服务器上被列入黑名单。 +- **应该**尝试了解[挖矿过程](https://www.ton.org/#/howto/pow-givers)的工作原理;大多数大型矿工使用自己的脚本,在多个挖矿机器的环境中提供比`mytonctrl`更多的优势。 + +## 矿工硬件 + +TON挖矿的总网络哈希率非常高;如果矿工希望成功,他们需要高性能的机器。在标准家用计算机和笔记本电脑上挖矿是徒劳的,我们不建议尝试。 + +#### CPU + +支持[Intel SHA扩展](https://zh.wikipedia.org/wiki/Intel_SHA_extensions)的现代CPU是**必须的**。大多数矿工使用至少32核心和64线程的AMD EPYC或Threadripper系列机器。 + +#### GPU + +是的!您可以使用GPU挖TON。有一个PoW矿工版本能够使用Nvidia和AMD GPU;您可以在[POW Miner GPU](https://github.com/tontechio/pow-miner-gpu/blob/main/crypto/util/pow-miner-howto.md)库中找到代码和使用说明。 + +目前,需要技术熟练才能使用这个,但我们正在开发更用户友好的解决方案。 + +#### 内存 + +几乎整个挖矿过程都发生在CPU的L2缓存中。这意味着内存速度和大小在挖矿性能中没有作用。一个只在一个内存通道上装有单个DIMM的双AMD EPYC系统将与占用所有通道的16个DIMM挖矿一样快地。 + +请注意,这**只**适用于普通挖矿过程,如果您的机器还运行全节点或其他进程,那么情况会改变!但这超出了本指南的范围。 + +#### 存储 + +以lite模式运行的普通矿工使用最少的空间,并且不在存储中存储任何数据。 + +#### 网络 + +普通矿工需要能够打开对外的互联网连接。 + +#### FPGA / ASIC + +参见[我可以使用FPGA / ASIC吗?](#faq-hw-asic) + +### 云算力 + +许多人使用AWS或Google计算云机器进行挖矿。如上所述,真正重要的是CPU。因此,我们建议AWS [c5a.24xlarge](https://aws.amazon.com/ec2/instance-types/c5/)或Google [n2d-highcpu-224](https://cloud.google.com/compute/vm-instance-pricing)实例。 + +### 收入估算 + +计算收入的公式非常简单:`($total_bleed / $total_hashrate) * $your_hashrate`。这将给您**当前**的估算。您可以在[ton.org/mining](https://ton.org/mining)上找到这些变量,也可以在`mytonctrl`中使用估算挖矿收入计算器(`emi`命令)。以下是2021年8月7日使用i5-11400F CPU进行的样本输出: + +``` +Mining income estimations +----------------------------------------------------------------- +Total network 24h earnings: 171635.79 TON +Average network 24h hashrate: 805276100000 HPS +Your machine hashrate: 68465900 HPS +Est. 24h chance to mine a block: 15% +Est. monthly income: 437.7 TON +``` + +**重要**:请注意,所提供的信息基于*执行时刻的网络哈希率*。您实际的长期收入将取决于许多因素,例如不断变化的网络哈希率、选择的giver以及好运。 + +## 常见问题解答 + +### 一般 + +#### TON是PoS还是PoW网络? + +TON区块链使用权益证明(Proof-of-Stake)共识。挖矿不是生成新块所必需的。 + +#### 那TON为什么是工作量证明(Proof-of-Work)? + +原因是最初的50亿Toncoin被转移到临时工作量证明提供者智能合约中。 +挖矿用于从这个智能合约中获取Toncoin。 + +#### 还有多少币可以挖? + +最新信息可在[ton.org/mining](https://ton.org/mining)上找到,参见`bleed`图表。PoW Giver合约有其限制,一旦用户挖出所有可用的Toncoin,它们就会枯竭。 + +#### 到目前为止已经挖出多少币? + +截至2021年8月,约有49亿Toncoin被挖出。 + +#### 谁挖出了这些币? + +这些币被挖到超过70,000个钱包中,这些钱包的所有者是未知的。 + +#### 开始挖矿难吗? + +一点也不难。您所需要的是[合适的硬件](#hardware)和按照[快速开始](#quickStart)部分中概述的步骤操作。 + +#### 还有其他方式挖矿吗? + +是的,有一个第三方应用——[TON Miner Bot](https://t.me/TonMinerBot)。 + +#### 我在哪里可以看到挖矿统计? + +[ton.org/mining](https://ton.org/mining) + +#### 有多少矿工? + +我们无法说出这个数字。我们所知道的是网络上所有矿工的总哈希率。然而,在[ton.org/mining](https://ton.org/mining)上有图表试图估算提供近似总哈希率的某种类型机器的数量。 + +#### 我需要Toncoin才能开始挖矿吗? + +不,您不需要。任何人都可以在不拥有任何Toncoin的情况下开始挖矿。 + +#### 我挖了几个小时,为什么我的钱包总额没有增加,甚至没有增加1 TON? + +TON在区块中是以每100进行开采的,你要么猜中一个区块并获得100 TON,要么一无所获。请参见[基础知识](#basics)。 + +#### 我挖了几天,为什么看不到结果? + +您检查了当前的[收入估算](#hardware-estimates)了吗?如果`Est. 24h chance to mine a block`字段小于100%,那么您需要耐心等待。另外,请注意,24小时内挖到一个块的50%几率并不自动意味着您将在2天内挖到一个块;每天分别适用50%。 + +#### 有挖矿池吗? + +截至目前,还没有挖矿池的实现,每个人都为自己挖矿。 + +#### 我应该挖哪个giver? + +您选择哪个giver并不真正重要。难度在每个giver上都会波动,所以[ton.org/mining](https://ton.org/mining)上当前最简单的giver可能在一个小时内变得最复杂。反之亦然。 + +### 硬件 + +#### 更快的机器是否总是胜出? + +不,所有矿工采取不同的途径来找到解决方案。更快的机器成功的概率更高,但并不保证胜利! + +#### 我的机器能产生多少收入? + +请参见[收入估算](#hardware-estimates)。 + +#### 我能用我的BTC/ETH装置来挖TON吗? + +不,TON使用单个SHA256散列方法,与BTC、ETH等不同。为挖其他加密货币而构建的ASIC或FPGA将不起作用。 + +#### 一台快速机器还是几台慢机器更好? + +这是有争议的。见:矿工软件为系统上的每个核心启动线程,每个核心都获得自己要处理的密钥集,所以如果您有一台能运行64线程的机器和4台能各自运行16线程的机器,那么它们将在成功方面完全相同,假设每个线程的速度相同。 + +然而,在现实世界中,核心数量较少的CPU通常时钟频率更高,所以您可能会用多台机器取得更好的成绩。 + +#### 如果我运行多台机器,它们会合作吗? + +不,它们不会。每台机器各自挖矿,但寻找解决方案的过程是随机的:没有机器,甚至没有单个线程(见上文)会采取相同的路径。因此,它们的哈希率加起来对你有利,而无需直接合作。 + +#### 我可以使用ARM CPU挖矿吗? + +这取决于CPU,AWS Graviton2实例确实是非常有能力的miner,并能够在性价比方面与基于AMD EPYC的实例相媲美。 + +### 软件 + +#### 我可以使用Windows/xBSD/其他操作系统挖矿吗? + +当然,[TON源代码](https://github.com/ton-blockchain/ton)已知可以在Windows、xBSD和其他操作系统上构建。然而,没有像Linux下的`mytonctrl`那样舒适的自动安装,您需要手动安装软件并创建自己的脚本。对于FreeBSD,有一个[port](https://github.com/sonofmom/freebsd_ton_port)源代码允许快速安装。 + +#### 如果我以完整节点模式运行mytonctrl,我的挖矿会变得更快吗? + +计算过程本身不会变快,但如果您操作自己的完整节点/轻服务器,您将获得一些稳定性和最重要的是灵活性。 + +#### 我需要什么/如何操作一个完整节点? + +这超出了本指南的范围,请参阅[完整节点howto](https://ton.org/#/howto/full-node)和/或[mytonctrl说明](https://github.com/igroman787/mytonctrl)。 + +#### 你能帮我在我的操作系统上构建软件吗? + +这超出了本指南的范围,请参阅[完整节点操作指南](https://ton.org/#/howto/full-node)以及[Mytonctrl安装脚本](https://github.com/igroman787/mytonctrl/blob/master/scripts/toninstaller.sh#L44),以获取有关依赖项和过程的信息。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/pow-givers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/pow-givers.md new file mode 100644 index 0000000000..ff5c8e98bc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/pow-givers.md @@ -0,0 +1,199 @@ +# POW Givers + +:::warning 已弃用 +此信息可能已过时,不再有用。您可以随意忽略它。 +::: + +本文旨在描述如何与POW Giver智能合约互动,以获得Toncoin。我们假设您已熟悉TON区块链轻客户端,如`入门`中所述,并熟悉编译轻客户端和其他软件的程序。为了获得运行验证者所需的更多Toncoin,我们还假设您熟悉`完整节点`和`验证者`页面。为了获得更多的Toncoin,您还需要一台足够强大的专用服务器来运行完整节点。获取少量的Toncoin不需要专用服务器,在家用电脑上几分钟内即可完成。 + +> 请注意,目前由于矿工数量众多,任何挖矿都需要大量资源。 + +## 1. Proof-of-Work Giver智能合约 + +为了防止少数恶意方收集所有Toncoin,网络的主链上部署了一种特殊的“工作量证明赠予者”智能合约。这些智能合约的地址如下: + +小额赠予者(每几分钟提供10至100 Toncoin): + +- kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN +- kf8SYc83pm5JkGt0p3TQRkuiM58O9Cr3waUtR9OoFq716lN- +- kf-FV4QTxLl-7Ct3E6MqOtMt-RGXMxi27g4I645lw6MTWraV +- kf_NSzfDJI1A3rOM0GQm7xsoUXHTgmdhN5-OrGD8uwL2JMvQ +- kf8gf1PQy4u2kURl-Gz4LbS29eaN4sVdrVQkPO-JL80VhOe6 +- kf8kO6K6Qh6YM4ddjRYYlvVAK7IgyW8Zet-4ZvNrVsmQ4EOF +- kf-P_TOdwcCh0AXHhBpICDMxStxHenWdLCDLNH5QcNpwMHJ8 +- kf91o4NNTryJ-Cw3sDGt9OTiafmETdVFUMvylQdFPoOxIsLm +- kf9iWhwk9GwAXjtwKG-vN7rmXT3hLIT23RBY6KhVaynRrIK7 +- kf8JfFUEJhhpRW80_jqD7zzQteH6EBHOzxiOhygRhBdt4z2N + +大额赠予者(每天至少提供10,000 Toncoin): + +- kf8guqdIbY6kpMykR8WFeVGbZcP2iuBagXfnQuq0rGrxgE04 +- kf9CxReRyaGj0vpSH0gRZkOAitm_yDHvgiMGtmvG-ZTirrMC +- kf-WXA4CX4lqyVlN4qItlQSWPFIy00NvO2BAydgC4CTeIUme +- kf8yF4oXfIj7BZgkqXM6VsmDEgCqWVSKECO1pC0LXWl399Vx +- kf9nNY69S3_heBBSUtpHRhIzjjqY0ChugeqbWcQGtGj-gQxO +- kf_wUXx-l1Ehw0kfQRgFtWKO07B6WhSqcUQZNyh4Jmj8R4zL +- kf_6keW5RniwNQYeq3DNWGcohKOwI85p-V2MsPk4v23tyO3I +- kf_NSPpF4ZQ7mrPylwk-8XQQ1qFD5evLnx5_oZVNywzOjSfh +- kf-uNWj4JmTJefr7IfjBSYQhFbd3JqtQ6cxuNIsJqDQ8SiEA +- kf8mO4l6ZB_eaMn1OqjLRrrkiBcSt7kYTvJC_dzJLdpEDKxn + +> 请注意,目前所有大额赠予者已被耗尽。 + +前十个智能合约使愿意获取少量Toncoin的用户能够在不花费太多计算功率的情况下获得一些(通常情况下,家用电脑上几分钟的工作应该就足够了)。其余智能合约用于获取网络中运行验证者所需的更多Toncoin;通常,一天在足够强大的专用服务器上的工作应该足以获得所需金额。 + +> 请注意,目前由于矿工数量众多,挖掘小额赠予者也需要大量资源。 + +您应该随机选择这些“proof-of-work giver”智能合约中的一个(根据您的目的从这两个列表中选择),并通过类似于挖矿的程序从该智能合约中获得Toncoin。基本上,您需要呈现一个包含工作量证明和您钱包地址的外部消息给所选的“proof-of-work giver”智能合约,然后金额将被发送给您。 + +## 2. 挖矿过程 + +为了创建一个包含“工作量证明(proof-of-work)”的外部消息,您应该运行一个特殊的挖矿实用程序,从GitHub库中的TON源代码编译而成。该实用程序位于构建目录的`./crypto/pow-miner`文件中,可以通过在构建目录中输入`make pow-miner`来编译。 + +然而,在运行`pow-miner`之前,您需要知道所选“proof-of-work giver”智能合约的`seed`和`complexity`参数的实际值。这可以通过调用该智能合约的get方法`get_pow_params`来完成。例如,如果您使用 giver 智能合约,`kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN`,您可以简单地键入: + +``` +> runmethod kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN get_pow_params +``` + +在轻客户端控制台中,并获得像这样的输出: + +```... + arguments: [ 101616 ] + result: [ 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 256 ] + remote result (not to be trusted): [ 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 256 ] +``` + +“result:”行中的前两个大数字分别是这个智能合约的`seed`和`complexity`。在此例中,seed是`229760179690128740373110445116482216837`,而复杂度是`53919893334301279589334030174039261347274288845081144962207220498432`。 + +接下来,您按如下方式调用`pow-miner`实用程序: + +``` +$ crypto/pow-miner -vv -w -t +``` + +这里: + +- ``是您希望用于挖矿的CPU核心数量。 +- ``是矿工运行失败前的最长秒数。 +- ``是您的钱包地址(可能尚未初始化)。它要么在主链上,要么在工作链上(请注意,您需要一个主链钱包来控制验证者)。 +- ``和``是通过运行get方法`get-pow-params`获得的最新值。 +- ``是所选proof-of-work giver智能合约的地址。 +- ``是成功时保存工作量证明的外部消息的输出文件的文件名。 + +例如,如果您的钱包地址是`kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7`,您可能会运行: + +``` +$ crypto/pow-miner -vv -w7 -t100 kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 229760179690128740373110445116482216837 53919893334301279589334030174039261347274288845081144962207220498432 100000000000 kf-kkdY_B7p-77TLn2hUhM6QidWrrsl8FYWCIvBMpZKprBtN mined.boc +``` + +程序将运行一段时间(在这种情况下最多100秒),并且要么成功终止(zero exit code)并将所需的工作量证明保存在文件`mined.boc`中,要么以非零 exit code 终止,如果没有找到工作量证明。 + +在失败的情况下,您会看到像这样的内容: + +``` + [ expected required hashes for success: 2147483648 ] + [ hashes computed: 1192230912 ] +``` + +程序将以非零 exit code 终止。然后您必须再次获取`seed`和`complexity`(因为它们可能已经在此期间改变,因为更成功的矿工的请求已经被处理),并重新运行`pow-miner`,使用新参数重复过程,直到成功。 + +在成功的情况下,您会看到类似于: + +``` + [ expected required hashes for success: 2147483648 ] + 4D696E65005EFE49705690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1A1F533B3BC4F5664D6C743C1C5C74BB3342F3A7314364B3D0DA698E6C80C1EA4ACDA33755876665780BAE9BE8A4D6385A1F533B3BC4F5664D6C743C1C5C74BB3342F3A7314364B3D0DA698E6C80C1EA4 + Saving 176 bytes of serialized external message into file `mined.boc` + [ hashes computed: 1122036095 ] +``` + +然后,您可以使用轻客户端将外部消息从文件`mined.boc`发送到 proof-of-work giver 智能合约(您必须尽快这样做): + +``` +> sendfile mined.boc +... external message status is 1 +``` + +您可以等待几秒钟,然后检查您的钱包状态: + +:::info +请注意,在此处和以下的代码、注释和/或文档中可能包含“gram”、“nanogram”等参数、方法和定义。这是原始TON代码的遗产,由Telegram开发。Gram加密货币从未发行。TON的货币是Toncoin,TON测试网的代币是Test Toncoin。 +::: + +``` +> last +> getaccount kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 +... +account state is (account + addr:(addr_std + anycast:nothing workchain_id:0 address:x5690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1) + storage_stat:(storage_info + used:(storage_used + cells:(var_uint len:1 value:1) + bits:(var_uint len:1 value:111) + public_cells:(var_uint len:0 value:0)) last_paid:1593722498 + due_payment:nothing) + storage:(account_storage last_trans_lt:7720869000002 + balance:(currencies + grams:(nanograms + amount:(var_uint len:5 value:100000000000)) + other:(extra_currencies + dict:hme_empty)) + state:account_uninit)) +x{C005690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F12025BC2F7F2341000001C169E9DCD0945D21DBA0004_} +last transaction lt = 7720869000001 hash = 83C15CDED025970FEF7521206E82D2396B462AADB962C7E1F4283D88A0FAB7D4 +account balance is 100000000000ng +``` + +如果在您之前没有人发送具有此`seed`和`complexity`的有效工作量证明,proof-of-work giver 将接受您的工作量证明,这将反映在您钱包的余额中(发送外部消息后可能需要10或20秒钟才会发生;请确保多次尝试并在每次检查钱包余额之前输入`last`以刷新轻客户端状态)。如果成功,您会看到余额增加(如果之前不存在,甚至您的钱包也会以未初始化的状态被创建)。如果失败,您将不得不获得新的`seed`和`complexity`,并从头开始重复挖矿过程。 + +如果您幸运并且钱包的余额增加了,如果之前没有初始化,您可能想初始化钱包(有关创建钱包的更多信息可以在`逐步操作`中找到): + +``` +> sendfile new-wallet-query.boc +... external message status is 1 +> last +> getaccount kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7 +... +account state is (account + addr:(addr_std + anycast:nothing workchain_id:0 address:x5690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1) + storage_stat:(storage_info + used:(storage_used + cells:(var_uint len:1 value:3) + bits:(var_uint len:2 value:1147) + public_cells:(var_uint len:0 value:0)) last_paid:1593722691 + due_payment:nothing) + storage:(account_storage last_trans_lt:7720945000002 + balance:(currencies + grams:(nanograms + amount:(var_uint len:5 value:99995640998)) + other:(extra_currencies + dict:hme_empty)) + state:(account_active + ( + split_depth:nothing + special:nothing + code:(just + value:(raw@^Cell + x{} + x{FF0020DD2082014C97BA218201339CBAB19C71B0ED44D0D31FD70BFFE304E0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54} + )) + data:(just + value:(raw@^Cell + x{} + x{00000001CE6A50A6E9467C32671667F8C00C5086FC8D62E5645652BED7A80DF634487715} + )) + library:hme_empty)))) +x{C005690D2AACC203003DBE333046683B698EF945FF250723C0F73297A2A1A41E2F1206811EC2F7F23A1800001C16B0BC790945D20D1929934_} + x{FF0020DD2082014C97BA218201339CBAB19C71B0ED44D0D31FD70BFFE304E0A4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54} + x{00000001CE6A50A6E9467C32671667F8C00C5086FC8D62E5645652BED7A80DF634487715} +last transaction lt = 7720945000001 hash = 73353151859661AB0202EA5D92FF409747F201D10F1E52BD0CBB93E1201676BF +account balance is 99995640998ng +``` + +现在您是100 Toncoin的幸运拥有者。祝贺您! + +## 3. 在失败的情况下自动化挖矿过程 + +如果您长时间无法获得Toncoin,这可能是因为太多其他用户同时从同一个 proof-of-work giver 智能合约进行挖矿。也许您应该从上面给出的列表中选择另一个 proof-of-work giver 智能合约。或者,您可以编写一个简单的脚本,自动运行`pow-miner`,使用正确的参数一遍又一遍地运行,直到成功(通过检查`pow-miner`的 exit code 来检测),并调用带有参数`-c 'sendfile mined.boc'`的轻客户端,以便在找到后立即给他发送外部消息。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/precompiled-binaries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/precompiled-binaries.md new file mode 100644 index 0000000000..95d7c3e63a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/precompiled-binaries.md @@ -0,0 +1,158 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Button from '@site/src/components/button' + +# 预编译二进制文件 + +:::caution 重要 +您不再需要手动安装Blueprint SDK的二进制文件。 +::: + +Blueprint SDK已提供所有开发和测试所需的二进制文件。 + + + + + +## 预编译二进制文件 + +如果您不使用Blueprint SDK进行智能合约开发,您可以使用适用于您的操作系统和工具选择的预编译二进制文件。 + +### 先决条件 + +对于在本地开发TON智能合约 _无需Javascript_,您需要在您的设备上准备`func`、`fift`和`lite client`的二进制文件。 + +您可以从下表中下载并设置它们,或阅读TON Society的这篇文章: +* [设置TON开发环境](https://blog.ton.org/setting-up-a-ton-development-environment) + +### 1. 下载 + +从下表中下载二进制文件。请确保选择适合您操作系统的正确版本,并安装任何附加依赖项: + +| 操作系统 | TON二进制文件 | fift | func | lite-client | 附加依赖项 | +|---------------|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|-------------|-------------------------------------------------------------------------------------| +| MacOS x86-64 | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/ton-mac-x86-64.zip) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/fift-mac-x86-64) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/func-mac-x86-64) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/lite-client-mac-x86-64) | | +| MacOS arm64 | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/ton-mac-arm64.zip) | ||| `brew install openssl ninja libmicrohttpd pkg-config` | +| Windows x86-64| [下载](https://github.com/ton-blockchain/ton/releases/latest/download/ton-win-x86-64.zip) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/fift.exe) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/func.exe) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/lite-client.exe) | 安装 [OpenSSL 1.1.1](/ton-binaries/windows/Win64OpenSSL_Light-1_1_1q.msi) | +| Linux x86_64 | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/ton-linux-x86_64.zip) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/fift-linux-x86_64) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/func-linux-x86_64) | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/lite-client-linux-x86_64) | | +| Linux arm64 | [下载](https://github.com/ton-blockchain/ton/releases/latest/download/ton-linux-arm64.zip) | | | | `sudo apt install libatomic1 libssl-dev` | + +### 2. 设置您的二进制文件 + +export const Highlight = ({children, color}) => ( + +{children} + +); + + + + + 1. 下载后,您需要`创建`一个新文件夹。例如:**`C:/Users/%USERNAME%/ton/bin`**,并将安装的文件移动到那里。 + + 2. 要打开Windows环境变量,请按键盘上的Win + R按钮,键入`sysdm.cpl`,然后按Enter键。 + + 3. 在“_高级_”选项卡上 + +,点击“环境变量...”按钮。 + + 4. 在_“用户变量”_部分,选择“_Path_”变量,然后点击“编辑”(通常需要)。 + + 5. 要在下一个窗口中向系统变量添加新值(路径),请单击“新建”按钮。 + 在新字段中,您需要指定存储先前安装的文件的文件夹路径: + + ``` + C:\Users\%USERNAME%\ton\bin\ + ``` + + 6. 要检查是否一切安装正确,请在终端运行(_cmd.exe_): + + ```bash + fift -V -and func -V -and lite-client -V + ``` + + 7. 如果您计划使用fift,您需要`FIFTPATH`环境变量,其中包含必要的导入项: + + 1. 下载 [fiftlib.zip](/ton-binaries/windows/fiftlib.zip) + 2. 在您的机器上的某个目录中打开zip(例如 **`C:/Users/%USERNAME%/ton/lib/fiftlib`**) + 3. 在_“用户变量”_部分创建一个新的(点击“新建”)环境变量`FIFTPATH`。 + 4. 在“_变量值_”字段中,指定文件的路径:**`/%USERNAME%/ton/lib/fiftlib`**,然后点击确定。完成。 + + +:::caution 重要 +您必须使用您自己的`用户名`代替`%USERNAME%`关键字。 +::: + + + + + 1. 下载后,请确保通过更改权限使下载的二进制文件可执行。 + ```bash + chmod +x func + chmod +x fift + chmod +x lite-client + ``` + + 2. 将这些二进制文件添加到您的路径中(或复制到`/usr/local/bin`),以便您可以在任何地方访问它们也是很有用的。 + ```bash + cp ./func /usr/local/bin/func + cp ./fift /usr/local/bin/fift + cp ./lite-client /usr/local/bin/lite-client + ``` + + 3. 要检查是否一切安装正确,请在终端运行。 + ```bash + fift -V && func -V && lite-client -V + ``` + + 4. 如果您计划`使用fift`,还需下载[fiftlib.zip](/ton-binaries/windows/fiftlib.zip),在您设备上的某个目录中打开zip(例如`/usr/local/lib/fiftlib`),并将环境变量`FIFTPATH`指向此目录。 + + ``` + unzip fiftlib.zip + mkdir -p /usr/local/lib/fiftlib + cp fiftlib/* /usr/local/lib/fiftlib + ``` + +:::info 嘿,你快完成了 :) +记得设置[环境变量](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix) `FIFTPATH`指向这个目录。 +::: + + + + + + + +## 从源码构建 + +如果您不想依赖预编译的二进制文件,而更愿意自己编译二进制文件,您可以按照[官方说明](/develop/howto/compile)操作。 + +下面提供了现成的gist说明: + +### Linux(Ubuntu / Debian) + +```bash +sudo apt update +sudo apt install git make cmake g++ libssl-dev zlib1g-dev wget +cd ~ && git clone https://github.com/ton-blockchain/ton.git +cd ~/ton && git submodule update --init +mkdir ~/ton/build && cd ~/ton/build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j 4 +``` +## 其他二进制文件来源 + +核心团队为几 + +种操作系统提供了[GitHub Actions](https://github.com/ton-blockchain/ton/releases/latest)的自动构建。 + +点击上面的链接,选择左侧与您的操作系统相关的工作流程,单击最近的绿色通过构建,然后下载“工件”下的`ton-binaries`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration-py.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration-py.md new file mode 100644 index 0000000000..3588c157c2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration-py.md @@ -0,0 +1,577 @@ +import Button from '@site/src/components/button' + +# Telegram 机器人的 TON Connect - Python + +在本教程中,我们将创建一个示例 Telegram 机器人,该机器人支持使用 Python TON Connect SDK [pytonconnect](https://github.com/XaBbl4/pytonconnect) 的 TON Connect 2.0 认证。 +我们将分析连接钱包、发送交易、获取有关已连接钱包的数据以及断开钱包的连接。 + + + + + +## 准备工作 + +### 安装库 + +要制作机器人,我们将使用 `aiogram` 3.0 Python 库。 +要开始将 TON Connect 集成到您的 Telegram 机器人中,您需要安装 `pytonconnect` 包。 +并且,为了使用 TON 原语并解析用户地址,我们需要 `pytoniq-core`。 +您可以使用 pip 来完成此操作: + +```bash +pip install aiogram pytoniq-core python-dotenv +pip install pytonconnect +``` + +### 设置配置 + +在 `.env` 文件中指定 [机器人令牌](https://t.me/BotFather) 和 TON Connect [清单文件](https://github.com/ton-connect/sdk/tree/main/packages/sdk#add-the-tonconnect-manifest) 的链接。之后在 `config.py` 中加载它们: + +```dotenv +# .env + +TOKEN='1111111111:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' # your bot token here +MANIFEST_URL='https://raw.githubusercontent.com/XaBbl4/pytonconnect/main/pytonconnect-manifest.json' +``` + +```python +# config.py + +from os import environ as env + +from dotenv import load_dotenv +load_dotenv() + +TOKEN = env['TOKEN'] +MANIFEST_URL = env['MANIFEST_URL'] +``` + +## 创建简单机器人 + +创建 `main.py` 文件,其中将包含主要机器人代码: + +```python +# main.py + +import sys +import logging +import asyncio + +import config + +from aiogram import Bot, Dispatcher, F +from aiogram.enums import ParseMode +from aiogram.filters import CommandStart, Command +from aiogram.types import Message, CallbackQuery + + +logger = logging.getLogger(__file__) + +dp = Dispatcher() +bot = Bot(config.TOKEN, parse_mode=ParseMode.HTML) + + +@dp.message(CommandStart()) +async def command_start_handler(message: Message): + await message.answer(text='Hi!') + +async def main() -> None: + await bot.delete_webhook(drop_pending_updates=True) # skip_updates = True + await dp.start_polling(bot) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + asyncio.run(main()) + +``` + +## 钱包连接 + +### TON Connect 存储 + +让我们为 TON Connect 创建简单的存储 + +```python +# tc_storage.py + +from pytonconnect.storage import IStorage, DefaultStorage + + +storage = {} + + +class TcStorage(IStorage): + + def __init__(self, chat_id: int): + self.chat_id = chat_id + + def _get_key(self, key: str): + return str(self.chat_id) + key + + async def set_item(self, key: str, value: str): + storage[self._get_key(key)] = value + + async def get_item(self, key: str, default_value: str = None): + return storage.get(self._get_key(key), default_value) + + async def remove_item(self, key: str): + storage.pop(self._get_key(key)) + +``` + +### 连接处理器 + +首先,我们需要一个为每个用户返回不同实例的函数: + +```python +# connector.py + +from pytonconnect import TonConnect + +import config +from tc_storage import TcStorage + + +def get_connector(chat_id: int): + return TonConnect(config.MANIFEST_URL, storage=TcStorage(chat_id)) + +``` + +其次,让我们在 `command_start_handler()` 中添加连接处理器: + +```python +# main.py + +@dp.message(CommandStart()) +async def command_start_handler(message: Message): + chat_id = message.chat.id + connector = get_connector(chat_id) + connected = await connector.restore_connection() + + mk_b = InlineKeyboardBuilder() + if connected: + mk_b.button(text='Send Transaction', callback_data='send_tr') + mk_b.button(text='Disconnect', callback_data='disconnect') + await message.answer(text='You are already connected!', reply_markup=mk_b.as_markup()) + else: + wallets_list = TonConnect.get_wallets() + for wallet in wallets_list: + mk_b.button(text=wallet['name'], callback_data=f'connect:{wallet["name"]}') + mk_b.adjust(1, ) + await message.answer(text='Choose wallet to connect', reply_markup=mk_b.as_markup()) + +``` + +现在,对于尚未连接钱包的用户,机器人会发送带有所有可用钱包按钮的消息。 +因此,我们需要编写函数来处理 `connect:{wallet["name"]}` 回调: + +```python +# main.py + +async def connect_wallet(message: Message, wallet_name: str): + connector = get_connector(message.chat.id) + + wallets_list = connector.get_wallets() + wallet = None + + for w in wallets_list: + if w['name'] == wallet_name: + wallet = w + + if wallet is None: + raise Exception(f'Unknown wallet: {wallet_name}') + + generated_url = await connector.connect(wallet) + + mk_b = InlineKeyboardBuilder() + mk_b.button(text='Connect', url=generated_url) + + await message.answer(text='Connect wallet within 3 minutes', reply_markup=mk_b.as_markup()) + + mk_b = InlineKeyboardBuilder() + mk_b.button(text='Start', callback_data='start') + + for i in range(1, 180): + await asyncio.sleep(1) + if connector.connected: + if connector.account.address: + wallet_address = connector.account.address + wallet_address = Address(wallet_address).to_str(is_bounceable=False) + await message.answer(f'You are connected with address {wallet_address}', reply_markup=mk_b.as_markup()) + logger.info(f'Connected with address: {wallet_address}') + return + + await message.answer(f'Timeout error!', reply_markup=mk_b.as_markup()) + + +@dp.callback_query(lambda call: True) +async def main_callback_handler(call: CallbackQuery): + await call.answer() + message = call.message + data = call.data + if data == "start": + await command_start_handler(message) + elif data == "send_tr": + await send_transaction(message) + elif data == 'disconnect': + await disconnect_wallet(message) + else: + data = data.split(':') + if data[0] == 'connect': + await connect_wallet(message, data[1]) +``` + +机器人给用户 3 分钟时间连接钱包,之后会报告超时错误。 + +## 实现交易请求 + +让我们以 [消息构建器](/develop/dapps/ton-connect/message-builders) 文章之一为例: + +```python +# messages.py + +from base64 import urlsafe_b64encode + +from pytoniq_core import begin_cell + + +def get_comment_message(destination_address: str, amount: int, comment: str) -> dict: + + data = { + 'address': destination_address, + 'amount': str(amount), + 'payload': urlsafe_b64encode( + begin_cell() + .store_uint(0, 32) # op code for comment message + .store_string(comment) # store comment + .end_cell() # end cell + .to_boc() # convert it to boc + ) + .decode() # encode it to urlsafe base64 + } + + return data + +``` + +并在 `main.py` 文件中添加 `send_transaction()` 函数: + +```python +# main.py + +@dp.message(Command('transaction')) +async def send_transaction(message: Message): + connector = get_connector(message.chat.id) + connected = await connector.restore_connection() + if not connected: + await message.answer('Connect wallet first!') + return + + transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_comment_message( + destination_address='0:0000000000000000000000000000000000000000000000000000000000000000', + amount=int(0.01 * 10 ** 9), + comment='hello world!' + ) + ] + } + + await message.answer(text='Approve transaction in your wallet app!') + await connector.send_transaction( + transaction=transaction + ) +``` + +但我们也应该处理可能的错误,所以我们将 `send_transaction` 方法放入 `try - except` 语句中: + +```python +@dp.message(Command('transaction')) +async def send_transaction(message: Message): + ... + await message.answer(text='Approve transaction in your wallet app!') + try: + await asyncio.wait_for(connector.send_transaction( + transaction=transaction + ), 300) + except asyncio.TimeoutError: + await message.answer(text='Timeout error!') + except pytonconnect.exceptions.UserRejectsError: + await message.answer(text='You rejected the transaction!') + except Exception as e: + await message.answer(text=f'Unknown error: {e}') +``` + +## 添加断开连接处理器 + +这个函数的实现非常简单: + +```python +async def disconnect_wallet(message: Message): + connector = get_connector(message.chat.id) + await connector.restore_connection() + await connector.disconnect() + await message.answer('You have been successfully disconnected!') +``` + +目前,项目结构如下: + +```bash +. +.env +├── config.py +├── connector.py +├── main.py +├── messages.py +└── tc_storage.py +``` + +`main.py` 的内容如下: + +
+展示 main.py + +```python +# main.py + +import sys +import logging +import asyncio +import time + +import pytonconnect.exceptions +from pytoniq_core import Address +from pytonconnect import TonConnect + +import config +from messages import get_comment_message +from connector import get_connector + +from aiogram import Bot, Dispatcher, F +from aiogram.enums import ParseMode +from aiogram.filters import CommandStart, Command +from aiogram.types import Message, CallbackQuery +from aiogram.utils.keyboard import InlineKeyboardBuilder + + +logger = logging.getLogger(__file__) + +dp = Dispatcher() +bot = Bot(config.TOKEN, parse_mode=ParseMode.HTML) + + +@dp.message(CommandStart()) +async def command_start_handler(message: Message): + chat_id = message.chat.id + connector = get_connector(chat_id) + connected = await connector.restore_connection() + + mk_b = InlineKeyboardBuilder() + if connected: + mk_b.button(text='Send Transaction', callback_data='send_tr') + mk_b.button(text='Disconnect', callback_data='disconnect') + await message.answer(text='You are already connected!', reply_markup=mk_b.as_markup()) + + else: + wallets_list = TonConnect.get_wallets() + for wallet in wallets_list: + mk_b.button(text=wallet['name'], callback_data=f'connect:{wallet["name"]}') + mk_b.adjust(1, ) + await message.answer(text='Choose wallet to connect', reply_markup=mk_b.as_markup()) + + +@dp.message(Command('transaction')) +async def send_transaction(message: Message): + connector = get_connector(message.chat.id) + connected = await connector.restore_connection() + if not connected: + await message.answer('Connect wallet first!') + return + + transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_comment_message( + destination_address='0:0000000000000000000000000000000000000000000000000000000000000000', + amount=int(0.01 * 10 ** 9), + comment='hello world!' + ) + ] + } + + await message.answer(text='Approve transaction in your wallet app!') + try: + await asyncio.wait_for(connector.send_transaction( + transaction=transaction + ), 300) + except asyncio.TimeoutError: + await message.answer(text='Timeout error!') + except pytonconnect.exceptions.UserRejectsError: + await message.answer(text='You rejected the transaction!') + except Exception as e: + await message.answer(text=f'Unknown error: {e}') + + +async def connect_wallet(message: Message, wallet_name: str): + connector = get_connector(message.chat.id) + + wallets_list = connector.get_wallets() + wallet = None + + for w in wallets_list: + if w['name'] == wallet_name: + wallet = w + + if wallet is None: + raise Exception(f'Unknown wallet: {wallet_name}') + + generated_url = await connector.connect(wallet) + + mk_b = InlineKeyboardBuilder() + mk_b.button(text='Connect', url=generated_url) + + await message.answer(text='Connect wallet within 3 minutes', reply_markup=mk_b.as_markup()) + + mk_b = InlineKeyboardBuilder() + mk_b.button(text='Start', callback_data='start') + + for i in range(1, 180): + await asyncio.sleep(1) + if connector.connected: + if connector.account.address: + wallet_address = connector.account.address + wallet_address = Address(wallet_address).to_str(is_bounceable=False) + await message.answer(f'You are connected with address {wallet_address}', reply_markup=mk_b.as_markup()) + logger.info(f'Connected with address: {wallet_address}') + return + + await message.answer(f'Timeout error!', reply_markup=mk_b.as_markup()) + + +async def disconnect_wallet(message: Message): + connector = get_connector(message.chat.id) + await connector.restore_connection() + await connector.disconnect() + await message.answer('You have been successfully disconnected!') + + +@dp.callback_query(lambda call: True) +async def main_callback_handler(call: CallbackQuery): + await call.answer() + message = call.message + data = call.data + if data == "start": + await command_start_handler(message) + elif data == "send_tr": + await send_transaction(message) + elif data == 'disconnect': + await disconnect_wallet(message) + else: + data = data.split(':') + if data[0] == 'connect': + await connect_wallet(message, data[1]) + + +async def main() -> None: + await bot.delete_webhook(drop_pending_updates=True) # skip_updates = True + await dp.start_polling(bot) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + asyncio.run(main()) + +``` + +
+ +## 改进 + +### 添加持久存储 - Redis + +目前,我们的 TON Connect 存储使用字典,导致机器人重启后会丢失会话。 +让我们使用 Redis 添加永久数据库存储: + +在启动 Redis 数据库后安装用于与之交互的 python 库: + +```bash +pip install redis +``` + +并在 `tc_storage.py` 中更新 `TcStorage` 类: + +```python +import redis.asyncio as redis + +client = redis.Redis(host='localhost', port=6379) + + +class TcStorage(IStorage): + + def __init__(self, chat_id: int): + self.chat_id = chat_id + + def _get_key(self, key: str): + return str(self.chat_id) + key + + async def set_item(self, key: str, value: str): + await client.set(name=self._get_key(key), value=value) + + async def get_item(self, key: str, default_value: str = None): + value = await client.get(name=self._get_key(key)) + return value.decode() if value else default_value + + async def remove_item(self, key: str): + await client.delete(self._get_key(key)) +``` + +### 添加二维码 + +安装 python `qrcode` 包以生成它们: + +```bash +pip install qrcode +``` + +更改 `connect_wallet()` 函数,使其生成二维码并以图片形式发送给用户: + +```python +from io import BytesIO +import qrcode +from aiogram.types import BufferedInputFile + + +async def connect_wallet(message: Message, wallet_name: str): + ... + + img = qrcode.make(generated_url) + stream = BytesIO() + img.save(stream) + file = BufferedInputFile(file=stream.getvalue(), filename='qrcode') + + await message.answer_photo(photo=file, caption='Connect wallet within 3 minutes', reply_markup=mk_b.as_markup()) + + ... +``` + +## 总结 + +接下来可以做什么? + +- 您可以在机器人中添加更好的错误处理。 +- 您可以添加启动文本和类似 `/connect_wallet` 的命令。 + +## 参阅 + +- [完整的机器人代码](https://github.com/yungwine/ton-connect-bot) +- [准备消息](/develop/dapps/ton-connect/message-builders) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration.mdx new file mode 100644 index 0000000000..5fca8104cc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/archive/tg-bot-integration.mdx @@ -0,0 +1,2061 @@ +import Button from '@site/src/components/button' + +# Telegram 机器人的 TON Connect + +在本教程中,我们将创建一个示例 Telegram 机器人,使用 Javascript TON Connect SDK 支持 TON Connect 2.0 认证。 +我们将分析连接钱包、发送交易、获取关于已连接钱包的数据以及断开钱包连接。 + + + + + +## 文档链接 + +- [TON Connect SDK 文档](https://www.npmjs.com/package/@tonconnect/sdk) + +## 必要条件 + +- 你需要使用 [@BotFather](https://t.me/BotFather) 创建一个 telegram 机器人,并保存其令牌。 +- 应安装 Node JS(本教程中我们使用版本 18.1.0)。 +- 应安装 Docker。 + +## 创建项目 + +### 设置依赖 + +首先,我们要创建一个 Node JS 项目。我们将使用 TypeScript 和 [node-telegram-bot-api](https://www.npmjs.com/package/node-telegram-bot-api) 库(你也可以选择任何适合你的库)。此外,我们将使用 [qrcode](https://www.npmjs.com/package/qrcode) 库生成 QR 码,但你可以用任何其他同类库替换。 + +让我们创建一个目录 `ton-connect-bot`。在那里添加以下的 package.json 文件: + +```json +{ + "name": "ton-connect-bot", + "version": "1.0.0", + "scripts": { + "compile": "npx rimraf dist && tsc", + "run": "node ./dist/main.js" + }, + "dependencies": { + "@tonconnect/sdk": "^3.0.0-beta.1", + "dotenv": "^16.0.3", + "node-telegram-bot-api": "^0.61.0", + "qrcode": "^1.5.1" + }, + "devDependencies": { + "@types/node-telegram-bot-api": "^0.61.4", + "@types/qrcode": "^1.5.0", + "rimraf": "^3.0.2", + "typescript": "^4.9.5" + } +} +``` + +运行 `npm i` 以安装依赖项。 + +### 添加 tsconfig.json + +创建一个 `tsconfig.json`: + +
+tsconfig.json 代码 + +```json +{ + "compilerOptions": { + "declaration": true, + "lib": ["ESNext", "dom"], + "resolveJsonModule": true, + "experimentalDecorators": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "target": "es6", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "useUnknownInCatchVariables": false, + "noUncheckedIndexedAccess": true, + "emitDecoratorMetadata": false, + "importHelpers": false, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": ["src"], + "exclude": [ + "./tests","node_modules", "lib", "types"] +} +``` + +
+ +[了解更多关于 tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) + +### 添加简单的机器人代码 + +创建一个 `.env` 文件,并在其中添加你的机器人令牌、DAppmanifest 和钱包列表缓存存活时间: + +[了解更多关于 tonconnect-manifes.json](https://github.com/ton-connect/sdk/tree/main/packages/sdk#add-the-tonconnect-manifest) + +```dotenv +# .env +TELEGRAM_BOT_TOKEN= +TELEGRAM_BOT_LINK= +MANIFEST_URL=https://raw.githubusercontent.com/ton-connect/demo-telegram-bot/master/tonconnect-manifest.json +WALLETS_LIST_CACHE_TTL_MS=86400000 +``` + +创建目录 `src` 和文件 `bot.ts`。在其中创建一个 TelegramBot 实例: + +```ts +// src/bot.ts + +import TelegramBot from 'node-telegram-bot-api'; +import * as process from 'process'; + +const token = process.env.TELEGRAM_BOT_TOKEN!; + +export const bot = new TelegramBot(token, { polling: true }); +``` + +现在我们可以在 `src` 目录内创建一个入口文件 `main.ts`: + +```ts +// src/main.ts +import dotenv from 'dotenv'; +dotenv.config(); + +import { bot } from './bot'; + +bot.on('message', msg => { + const chatId = msg.chat.id; + + bot.sendMessage(chatId, 'Received your message'); +}); +``` + +我们可以了。你可以运行 `npm run compile` 和 `npm run start`,然后给你的机器人发送任何消息。机器人将回复“Received your message”。我们准备好进行 TonConnect 集成了。 + +目前我们有以下文件结构: + +```text +ton-connect-bot +├── src +│ ├── bot.ts +│ └── main.ts +├── package.json +├── package-lock.json +├── .env +└── tsconfig.json +``` + +## 连接钱包 + +我们已经安装了 `@tonconnect/sdk`,因此我们可以直接导入它开始使用。 + +我们将从获取钱包列表开始。我们只需要 http-bridge 兼容的钱包。在 `src` 中创建文件夹 `ton-connect` 并添加 `wallets.ts` 文件: +我们还定义了函数 `getWalletInfo` 来通过其 `appName` 查询详细的钱包信息。 +`name` 和 `appName` 之间的区别是 `name` 是钱包的人类可读标签,而 `appName` 是钱包的唯一标识符。 + +```ts +// src/ton-connect/wallets.ts + +import { isWalletInfoRemote, WalletInfoRemote, WalletsListManager } from '@tonconnect/sdk'; + +const walletsListManager = new WalletsListManager({ + cacheTTLMs: Number(process.env.WALLETS_LIST_CACHE_TTL_MS) +}); + +export async function getWallets(): Promise { + const wallets = await walletsListManager.getWallets(); + return wallets.filter(isWalletInfoRemote); +} + +export async function getWalletInfo(walletAppName: string): Promise { + const wallets = await getWallets(); + return wallets.find(wallet => wallet.appName.toLowerCase() === walletAppName.toLowerCase()); +} +``` + +现在我们需要定义一个 TonConnect 存储。TonConnect 在浏览器中运行时使用 `localStorage` 来保存连接详情,但是 NodeJS 环境中没有 `localStorage`。这就是为什么我们应该添加一个自定义的简单存储实现。 + +[查看关于 TonConnect 存储的详情](https://github.com/ton-connect/sdk/tree/main/packages/sdk#init-connector) + +在 `ton-connect` 目录内创建 `storage.ts`: + +```ts +// src/ton-connect/storage.ts + +import { IStorage } from '@tonconnect/sdk'; + +const storage = new Map(); // temporary storage implementation. We will replace it with the redis later + +export class TonConnectStorage implements IStorage { + constructor(private readonly chatId: number) {} // we need to have different stores for different users + + private getKey(key: string): string { + return this.chatId.toString() + key; // we will simply have different keys prefixes for different users + } + + async removeItem(key: string): Promise { + storage.delete(this.getKey(key)); + } + + async setItem(key: string, value: string): Promise { + storage.set(this.getKey(key), value); + } + + async getItem(key: string): Promise { + return storage.get(this.getKey(key)) || null; + } +} +``` + +我们正在进行实现钱包连接。 +修改 `src/main.ts` 并添加 `connect` 命令。我们打算在这个命令处理器中实现一个钱包连接。 + +```ts +import dotenv from 'dotenv'; +dotenv.config(); + +import { bot } from './bot'; +import { getWallets } from './ton-connect/wallets'; +import TonConnect from '@tonconnect/sdk'; +import { TonConnectStorage } from './ton-connect/storage'; +import QRCode from 'qrcode'; + +bot.onText(/\/connect/, async msg => { + const chatId = msg.chat.id; + const wallets = await getWallets(); + + const connector = new TonConnect({ + storage: new TonConnectStorage(chatId), + manifestUrl: process.env.MANIFEST_URL + }); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const tonkeeper = wallets.find(wallet => wallet.appName === 'tonkeeper')!; + + const link = connector.connect({ + bridgeUrl: tonkeeper.bridgeUrl, + universalLink: tonkeeper.universalLink + }); + const image = await QRCode.toBuffer(link); + + await bot.sendPhoto(chatId, image); +}); +``` + +让我们分析一下我们在这里做的事情。首先我们获取钱包列表并创建一个TonConnect实例。 +之后,我们订阅钱包变化。当用户连接一个钱包时,机器人会发送信息 `${wallet.device.appName} wallet connected!`。 +接下来我们找到Tonkeeper钱包并创建连接链接。最后我们生成一个带有链接的二维码,并将其作为照片发送给用户。 + +现在,你可以运行机器人(接着运行 `npm run compile` 和 `npm run start`)并向机器人发送 `/connect` 信息。机器人应该会回复二维码。用Tonkeeper钱包扫描它。你将在聊天中看到 `Tonkeeper wallet connected!` 的信息。 + +我们会在许多地方使用连接器,因此让我们将创建连接器的代码移动到一个单独的文件中: + +```ts +// src/ton-connect/connector.ts + +import TonConnect from '@tonconnect/sdk'; +import { TonConnectStorage } from './storage'; +import * as process from 'process'; + +export function getConnector(chatId: number): TonConnect { + return new TonConnect({ + manifestUrl: process.env.MANIFEST_URL, + storage: new TonConnectStorage(chatId) + }); +} +``` + +并在 `src/main.ts` 中导入它 + +```ts +// src/main.ts + +import dotenv from 'dotenv'; +dotenv.config(); + +import { bot } from './bot'; +import { getWallets } from './ton-connect/wallets'; +import QRCode from 'qrcode'; +import { getConnector } from './ton-connect/connector'; + +bot.onText(/\/connect/, async msg => { + const chatId = msg.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const tonkeeper = wallets.find(wallet => wallet.appName === 'tonkeeper')!; + + const link = connector.connect({ + bridgeUrl: tonkeeper.bridgeUrl, + universalLink: tonkeeper.universalLink + }); + const image = await QRCode.toBuffer(link); + + await bot.sendPhoto(chatId, image); +}); +``` + +目前我们有以下文件结构: + +```text +bot-demo +├── src +│ ├── ton-connect +│ │ ├── connector.ts +│ │ ├── wallets.ts +│ │ └── storage.ts +│ ├── bot.ts +│ └── main.ts +├── package.json +├── package-lock.json +├── .env +└── tsconfig.json +``` + +## 创建连接钱包菜单 + +### 添加内联键盘 + +我们已经完成了Tonkeeper钱包的连接。但我们还没有实现通过通用二维码连接所有钱包,并且没有允许用户选择合适的钱包。现在让我们来实现它。 + +为了更好的用户体验,我们打算使用 Telegram 的 `callback_query` 和 `inline_keyboard` 功能。如果你不熟悉,可以在[这里](https://core.telegram.org/bots/api#callbackquery)阅读更多。 + +我们将为钱包连接实现以下用户体验: + +```text +First screen: + +, , + +Second screen: + + +<@wallet button (opens third screen)>, , , <...> + +Third screen: + + + +``` + +让我们开始在 `src/main.ts` 中的 `/connect` 命令处理程序中添加内联键盘 + +```ts +// src/main.ts +bot.onText(/\/connect/, async msg => { + const chatId = msg.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + connector.onStatusChange(async wallet => { + if (wallet) { + const walletName = + (await getWalletInfo(wallet.device.appName))?.name || wallet.device.appName; + bot.sendMessage(chatId, `${walletName} wallet connected!`); + } + }); + + const link = connector.connect(wallets); + const image = await QRCode.toBuffer(link); + + await bot.sendPhoto(chatId, image, { + reply_markup: { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + } + }); +}); +``` + +我们需要将TonConnect深层链接包装为https://ton-connect.github.io/open-tc?connect=${encodeURIComponent(link)},因为在Telegram内联键盘中只允许使用`http`链接。 +网站https://ton-connect.github.io/open-tc仅将用户重定向到`connect`查询参数中传递的链接,因此这只是在Telegram中打开`tc://`链接的一种间接方式。 + +注意我们替换了 `connector.connect` 调用参数。现在我们为所有钱包生成一个统一链接。 + +接下来我们告诉 Telegram,在用户点击 `选择钱包` 按钮时以 `{ "method": "chose_wallet" }` 值调用 `callback_query` 处理程序。 + +### 添加选择钱包按钮处理程序 + +创建文件 `src/connect-wallet-menu.ts`。 + +让我们在那里添加“选择钱包”按钮点击处理程序: + +```ts +// src/connect-wallet-menu.ts + +async function onChooseWalletClick(query: CallbackQuery, _: string): Promise { + const wallets = await getWallets(); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + wallets.map(wallet => ({ + text: wallet.name, + callback_data: JSON.stringify({ method: 'select_wallet', data: wallet.appName }) + })), + [ + { + text: '« Back', + callback_data: JSON.stringify({ + method: 'universal_qr' + }) + } + ] + ] + }, + { + message_id: query.message!.message_id, + chat_id: query.message!.chat.id + } + ); +} +``` + +这里我们用一个包含点击列表的钱包和“返回”按钮的新内联键盘替换了信息内联键盘。 + +现在我们将添加全局 `callback_query` 处理程序并在其中注册 `onChooseWalletClick`: + +```ts +// src/connect-wallet-menu.ts +import { CallbackQuery } from 'node-telegram-bot-api'; +import { getWallets } from './ton-connect/wallets'; +import { bot } from './bot'; + +export const walletMenuCallbacks = { // Define buttons callbacks + chose_wallet: onChooseWalletClick +}; + +bot.on('callback_query', query => { // Parse callback data and execute corresponing function + if (!query.data) { + return; + } + + let request: { method: string; data: string }; + + try { + request = JSON.parse(query.data); + } catch { + return; + } + + if (!walletMenuCallbacks[request.method as keyof typeof walletMenuCallbacks]) { + return; + } + + walletMenuCallbacks[request.method as keyof typeof walletMenuCallbacks](query, request.data); +}); + +// ... other code from the previous ster +async function onChooseWalletClick ... +``` + +我们在这里定义了按钮处理程序列表和 `callback_query` 解析器。不幸的是,回调数据总是字符串,因此我们必须将JSON传递给 `callback_data`,并在 `callback_query` 处理程序中稍后解析它。 +然后我们查找请求的方法,并使用传递的参数调用它。 + +现在我们应该将 `connect-wallet-menu.ts` 导入到 `src/main.ts` + +```ts +// src/main.ts + +// ... other imports + +import './connect-wallet-menu'; + +// ... other code +``` + +编译并运行机器人。你现在可以点击选择钱包按钮,机器人将替换内联键盘按钮! + +### 添加其他的 buttons handlers + +让我们完成这个菜单并添加其余的命令处理程序。 + +首先,我们将创建一个工具函数 `editQR`。编辑消息媒体(QR 图像)有点棘手。我们需要将图像存储到文件中并将其发送到 Telegram 服务器。然后我们可以删除这个文件。 + +```ts +// src/connect-wallet-menu.ts + +// ... other code + + +async function editQR(message: TelegramBot.Message, link: string): Promise { + const fileName = 'QR-code-' + Math.round(Math.random() * 10000000000); + + await QRCode.toFile(`./${fileName}`, link); + + await bot.editMessageMedia( + { + type: 'photo', + media: `attach://${fileName}` + }, + { + message_id: message?.message_id, + chat_id: message?.chat.id + } + ); + + await new Promise(r => fs.rm(`./${fileName}`, r)); +} +``` + +在 `onOpenUniversalQRClick` 处理器中,我们仅重新生成 QR 和 deeplink 并修改消息: + +```ts +// src/connect-wallet-menu.ts + +// ... other code + +async function onOpenUniversalQRClick(query: CallbackQuery, _: string): Promise { + const chatId = query.message!.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const link = connector.connect(wallets); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: query.message?.chat.id + } + ); +} + +// ... other code +``` + +在 `onWalletClick` 处理程序中,我们为仅选定的钱包创建特殊的 QR 和通用链接,并修改消息。 + +```ts +// src/connect-wallet-menu.ts + +// ... other code + +async function onWalletClick(query: CallbackQuery, data: string): Promise { + const chatId = query.message!.chat.id; + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const selectedWallet = await getWalletInfo(data); + if (!selectedWallet) { + return; + } + + const link = connector.connect({ + bridgeUrl: selectedWallet.bridgeUrl, + universalLink: selectedWallet.universalLink + }); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: '« Back', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: `Open ${selectedWallet.name}`, + url: link + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: chatId + } + ); +} + +// ... other code +``` + +现在我们必须将这些函数注册为回调(`walletMenuCallbacks`): + +```ts +// src/connect-wallet-menu.ts +import TelegramBot, { CallbackQuery } from 'node-telegram-bot-api'; +import { getWallets } from './ton-connect/wallets'; +import { bot } from './bot'; +import * as fs from 'fs'; +import { getConnector } from './ton-connect/connector'; +import QRCode from 'qrcode'; + +export const walletMenuCallbacks = { + chose_wallet: onChooseWalletClick, + select_wallet: onWalletClick, + universal_qr: onOpenUniversalQRClick +}; + +// ... other code +``` + +
+目前 src/connect-wallet-menu.ts 看起来像这样 + +```ts +// src/connect-wallet-menu.ts + +import TelegramBot, { CallbackQuery } from 'node-telegram-bot-api'; +import { getWallets, getWalletInfo } from './ton-connect/wallets'; +import { bot } from './bot'; +import { getConnector } from './ton-connect/connector'; +import QRCode from 'qrcode'; +import * as fs from 'fs'; + +export const walletMenuCallbacks = { + chose_wallet: onChooseWalletClick, + select_wallet: onWalletClick, + universal_qr: onOpenUniversalQRClick +}; + +bot.on('callback_query', query => { // Parse callback data and execute corresponing function + if (!query.data) { + return; + } + + let request: { method: string; data: string }; + + try { + request = JSON.parse(query.data); + } catch { + return; + } + + if (!callbacks[request.method as keyof typeof callbacks]) { + return; + } + + callbacks[request.method as keyof typeof callbacks](query, request.data); +}); + + +async function onChooseWalletClick(query: CallbackQuery, _: string): Promise { + const wallets = await getWallets(); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + wallets.map(wallet => ({ + text: wallet.name, + callback_data: JSON.stringify({ method: 'select_wallet', data: wallet.appName }) + })), + [ + { + text: '« Back', + callback_data: JSON.stringify({ + method: 'universal_qr' + }) + } + ] + ] + }, + { + message_id: query.message!.message_id, + chat_id: query.message!.chat.id + } + ); +} + +async function onOpenUniversalQRClick(query: CallbackQuery, _: string): Promise { + const chatId = query.message!.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const link = connector.connect(wallets); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: query.message?.chat.id + } + ); +} + +async function onWalletClick(query: CallbackQuery, data: string): Promise { + const chatId = query.message!.chat.id; + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const selectedWallet = await getWalletInfo(data); + if (!selectedWallet) { + return; + } + + const link = connector.connect({ + bridgeUrl: selectedWallet.bridgeUrl, + universalLink: selectedWallet.universalLink + }); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: '« Back', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: `Open ${selectedWallet.name}`, + url: link + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: chatId + } + ); +} + +async function editQR(message: TelegramBot.Message, link: string): Promise { + const fileName = 'QR-code-' + Math.round(Math.random() * 10000000000); + + await QRCode.toFile(`./${fileName}`, link); + + await bot.editMessageMedia( + { + type: 'photo', + media: `attach://${fileName}` + }, + { + message_id: message?.message_id, + chat_id: message?.chat.id + } + ); + + await new Promise(r => fs.rm(`./${fileName}`, r)); +} +``` + +
+ +编译并运行机器人以检查钱包连接功能现在如何工作。 + +您可能会注意到,我们还没有考虑到 QR 代码过期和停止连接器的问题。我们稍后会处理这个问题。 + +目前我们有以下文件结构: + +```text +bot-demo +├── src +│ ├── ton-connect +│ │ ├── connector.ts +│ │ ├── wallets.ts +│ │ └── storage.ts +│ ├── bot.ts +│ ├── connect-wallet-menu.ts +│ └── main.ts +├── package.json +├── package-lock.json +├── .env +└── tsconfig.json +``` + +## 实现发送交易 + +在编写发送交易的新代码之前,让我们清理一下代码。我们将为机器人命令处理程序('/connect', '/send_tx', ...)创建一个新文件 + +```ts +// src/commands-handlers.ts + +import { bot } from './bot'; +import { getWallets } from './ton-connect/wallets'; +import QRCode from 'qrcode'; +import TelegramBot from 'node-telegram-bot-api'; +import { getConnector } from './ton-connect/connector'; + +export async function handleConnectCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + connector.onStatusChange(wallet => { + if (wallet) { + bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`); + } + }); + + const link = connector.connect(wallets); + const image = await QRCode.toBuffer(link); + + await bot.sendPhoto(chatId, image, { + reply_markup: { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + } + }); +} +``` + +让我们在 `main.ts` 中导入并将 `callback_query` 入口点从 `connect-wallet-menu.ts` 移动到 `main.ts`: + +```ts +// src/main.ts + +import dotenv from 'dotenv'; +dotenv.config(); + +import { bot } from './bot'; +import './connect-wallet-menu'; +import { handleConnectCommand } from './commands-handlers'; +import { walletMenuCallbacks } from './connect-wallet-menu'; + +const callbacks = { + ...walletMenuCallbacks +}; + +bot.on('callback_query', query => { + if (!query.data) { + return; + } + + let request: { method: string; data: string }; + + try { + request = JSON.parse(query.data); + } catch { + return; + } + + if (!callbacks[request.method as keyof typeof callbacks]) { + return; + } + + callbacks[request.method as keyof typeof callbacks](query, request.data); +}); + +bot.onText(/\/connect/, handleConnectCommand); +``` + +```ts +// src/connect-wallet-menu.ts + +// ... imports + + +export const walletMenuCallbacks = { + chose_wallet: onChooseWalletClick, + select_wallet: onWalletClick, + universal_qr: onOpenUniversalQRClick +}; + +async function onChooseWalletClick(query: CallbackQuery, _: string): Promise { + +// ... other code +``` + +现在我们可以添加 `send_tx` 命令处理程序: + +```ts +// src/commands-handlers.ts + +// ... other code + +export async function handleSendTXCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + + const connector = getConnector(chatId); + + await connector.restoreConnection(); + if (!connector.connected) { + await bot.sendMessage(chatId, 'Connect wallet to send transaction'); + return; + } + + connector + .sendTransaction({ + validUntil: Math.round(Date.now() / 1000) + 600, // timeout is SECONDS + messages: [ + { + amount: '1000000', + address: '0:0000000000000000000000000000000000000000000000000000000000000000' + } + ] + }) + .then(() => { + bot.sendMessage(chatId, `Transaction sent successfully`); + }) + .catch(e => { + if (e instanceof UserRejectsError) { + bot.sendMessage(chatId, `You rejected the transaction`); + return; + } + + bot.sendMessage(chatId, `Unknown error happened`); + }) + .finally(() => connector.pauseConnection()); + + let deeplink = ''; + const walletInfo = await getWalletInfo(connector.wallet!.device.appName); + if (walletInfo) { + deeplink = walletInfo.universalLink; + } + + await bot.sendMessage( + chatId, + `Open ${walletInfo?.name || connector.wallet!.device.appName} and confirm transaction`, + { + reply_markup: { + inline_keyboard: [ + [ + { + text: 'Open Wallet', + url: deeplink + } + ] + ] + } + } + ); +} +``` + +这里我们检查用户的钱包是否已连接并处理发送交易。 +然后我们向用户发送一条带有按钮的消息,该按钮打开用户的钱包(钱包通用链接,无需额外参数)。 +请注意,这个按钮包含一个空的deeplink。这意味着发送交易请求数据通过http-bridge传输,即使用户只是打开钱包应用而没有点击这个按钮,交易也会出现在用户的钱包中。 + +我们来注册这个处理程序: + +```ts +// src/main.ts + +// ... other code + +bot.onText(/\/connect/, handleConnectCommand); + +bot.onText(/\/send_tx/, handleSendTXCommand); +``` + +编译并运行机器人以检查交易发送是否正常工作。 + +目前我们有以下文件结构: + +```text +bot-demo +├── src +│ ├── ton-connect +│ │ ├── connector.ts +│ │ ├── wallets.ts +│ │ └── storage.ts +│ ├── bot.ts +│ ├── connect-wallet-menu.ts +│ ├── commands-handlers.ts +│ └── main.ts +├── package.json +├── package-lock.json +├── .env +└── tsconfig.json +``` + +## 添加断开连接和显示已连接钱包命令 + +这些命令的实现很简单: + +```ts +// src/commands-handlers.ts + +// ... other code + +export async function handleDisconnectCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + + const connector = getConnector(chatId); + + await connector.restoreConnection(); + if (!connector.connected) { + await bot.sendMessage(chatId, "You didn't connect a wallet"); + return; + } + + await connector.disconnect(); + + await bot.sendMessage(chatId, 'Wallet has been disconnected'); +} + +export async function handleShowMyWalletCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + + const connector = getConnector(chatId); + + await connector.restoreConnection(); + if (!connector.connected) { + await bot.sendMessage(chatId, "You didn't connect a wallet"); + return; + } + + const walletName = + (await getWalletInfo(connector.wallet!.device.appName))?.name || + connector.wallet!.device.appName; + + + await bot.sendMessage( + chatId, + `Connected wallet: ${walletName}\nYour address: ${toUserFriendlyAddress( + connector.wallet!.account.address, + connector.wallet!.account.chain === CHAIN.TESTNET + )}` + ); +} +``` + +注册这些命令: + +```ts +// src/main.ts + +// ... other code + +bot.onText(/\/connect/, handleConnectCommand); +bot.onText(/\/send_tx/, handleSendTXCommand); +bot.onText(/\/disconnect/, handleDisconnectCommand); +bot.onText(/\/my_wallet/, handleShowMyWalletCommand); +``` + +编译并运行机器人以检查上述命令是否正常工作。 + +## 优化 + +我们已完成所有基本命令。但值得注意的是,每个连接器保持 SSE 连接打开,直到它被暂停。 +此外,当用户多次调用 `/connect`,或调用 `/connect` 或 `/send_tx` 且不扫描 QR 码时,我们没有处理。我们应设置一个超时时间,并关闭连接以节省服务器资源。 +然后,我们应通知用户 QR / 交易请求已过期。 + +### 发送交易优化 + +让我们创建一个实用函数,将一个promise包装起来,并在指定的超时后拒绝它: + +```ts +// src/utils.ts + +export const pTimeoutException = Symbol(); + +export function pTimeout( + promise: Promise, + time: number, + exception: unknown = pTimeoutException +): Promise { + let timer: ReturnType; + return Promise.race([ + promise, + new Promise((_r, rej) => (timer = setTimeout(rej, time, exception))) + ]).finally(() => clearTimeout(timer)) as Promise; +} +``` + +你可以使用这段代码或选择你喜欢的库。 + +在 `.env` 中添加超时参数值 + +```dotenv +# .env +TELEGRAM_BOT_TOKEN= +MANIFEST_URL=https://raw.githubusercontent.com/ton-connect/demo-telegram-bot/master/tonconnect-manifest.json +WALLETS_LIST_CACHE_TTL_MS=86400000 +DELETE_SEND_TX_MESSAGE_TIMEOUT_MS=600000 +``` + +现在我们要改进 `handleSendTXCommand` 函数,并将 tx 发送包装到 `pTimeout` + +```ts +// src/commands-handlers.ts + +// export async function handleSendTXCommand(msg: TelegramBot.Message): Promise { ... + +pTimeout( + connector.sendTransaction({ + validUntil: Math.round( + (Date.now() + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS)) / 1000 + ), + messages: [ + { + amount: '1000000', + address: '0:0000000000000000000000000000000000000000000000000000000000000000' + } + ] + }), + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS) +) + .then(() => { + bot.sendMessage(chatId, `Transaction sent successfully`); + }) + .catch(e => { + if (e === pTimeoutException) { + bot.sendMessage(chatId, `Transaction was not confirmed`); + return; + } + + if (e instanceof UserRejectsError) { + bot.sendMessage(chatId, `You rejected the transaction`); + return; + } + + bot.sendMessage(chatId, `Unknown error happened`); + }) + .finally(() => connector.pauseConnection()); + +// ... other code +``` + +
+完整的 handleSendTXCommand 代码 + +```ts +export async function handleSendTXCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + + const connector = getConnector(chatId); + + await connector.restoreConnection(); + if (!connector.connected) { + await bot.sendMessage(chatId, 'Connect wallet to send transaction'); + return; + } + + pTimeout( + connector.sendTransaction({ + validUntil: Math.round( + (Date.now() + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS)) / 1000 + ), + messages: [ + { + amount: '1000000', + address: '0:0000000000000000000000000000000000000000000000000000000000000000' + } + ] + }), + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS) + ) + .then(() => { + bot.sendMessage(chatId, `Transaction sent successfully`); + }) + .catch(e => { + if (e === pTimeoutException) { + bot.sendMessage(chatId, `Transaction was not confirmed`); + return; + } + + if (e instanceof UserRejectsError) { + bot.sendMessage(chatId, `You rejected the transaction`); + return; + } + + bot.sendMessage(chatId, `Unknown error happened`); + }) + .finally(() => connector.pauseConnection()); + + let deeplink = ''; + const walletInfo = await getWalletInfo(connector.wallet!.device.appName); + if (walletInfo) { + deeplink = walletInfo.universalLink; + } + + await bot.sendMessage( + chatId, + `Open ${walletInfo?.name || connector.wallet!.device.appName} and confirm transaction`, + { + reply_markup: { + inline_keyboard: [ + [ + { + text: 'Open Wallet', + url: deeplink + } + ] + ] + } + } + ); +} +``` + +
+ +如果用户在 `DELETE_SEND_TX_MESSAGE_TIMEOUT_MS`(10分钟)内未确认交易,交易将被取消并且机器人将发送消息“Transaction was not confirmed”。 + +你可以将此参数设置为 `5000`,编译并重新运行机器人,测试其行为。 + +### 钱包连接流程优化 + +目前,我们在每次通过钱包连接菜单步骤时都会创建一个新的连接器。 +这种做法不太好,因为在创建新连接器时我们并没有关闭之前连接器的连接。 +让我们改进这种行为,为用户的连接器创建一个缓存映射。 + +
+src/ton-connect/connector.ts 代码 + +```ts +// src/ton-connect/connector.ts + +import TonConnect from '@tonconnect/sdk'; +import { TonConnectStorage } from './storage'; +import * as process from 'process'; + +type StoredConnectorData = { + connector: TonConnect; + timeout: ReturnType; + onConnectorExpired: ((connector: TonConnect) => void)[]; +}; + +const connectors = new Map(); + +export function getConnector( + chatId: number, + onConnectorExpired?: (connector: TonConnect) => void +): TonConnect { + let storedItem: StoredConnectorData; + if (connectors.has(chatId)) { + storedItem = connectors.get(chatId)!; + clearTimeout(storedItem.timeout); + } else { + storedItem = { + connector: new TonConnect({ + manifestUrl: process.env.MANIFEST_URL, + storage: new TonConnectStorage(chatId) + }), + onConnectorExpired: [] + } as unknown as StoredConnectorData; + } + + if (onConnectorExpired) { + storedItem.onConnectorExpired.push(onConnectorExpired); + } + + storedItem.timeout = setTimeout(() => { + if (connectors.has(chatId)) { + const storedItem = connectors.get(chatId)!; + storedItem.connector.pauseConnection(); + storedItem.onConnectorExpired.forEach(callback => callback(storedItem.connector)); + connectors.delete(chatId); + } + }, Number(process.env.CONNECTOR_TTL_MS)); + + connectors.set(chatId, storedItem); + return storedItem.connector; +} +``` + +
+ +这段代码可能看起来有点复杂,让我们来解释一下。 +这里我们为每个用户存储一个连接器、它的清理超时和在超时后应执行的回调列表。 + +当`getConnector`被调用时,我们检查此`chatId`(用户)是否已在缓存中存在一个存在的连接器。如果存在,我们重置清理超时并返回连接器。 +这允许保持活跃用户的连接器在缓存中。如果缓存中没有连接器,我们创建一个新的,注册一个超时清理函数并返回此连接器。 + +为了使它工作,我们必须在`.env`中添加一个新参数 + +```dotenv +# .env +TELEGRAM_BOT_TOKEN= +MANIFEST_URL=https://ton-connect.github.io/demo-dapp-with-react-ui/tonconnect-manifest.json +WALLETS_LIST_CACHE_TTL_MS=86400000 +DELETE_SEND_TX_MESSAGE_TIMEOUT_MS=600000 +CONNECTOR_TTL_MS=600000 +``` + +现在让我们在handleConnectCommand中使用它 + +
+src/commands-handlers.ts 代码 + +```ts +// src/commands-handlers.ts + +import { + CHAIN, + isWalletInfoRemote, + toUserFriendlyAddress, + UserRejectsError +} from '@tonconnect/sdk'; +import { bot } from './bot'; +import { getWallets, getWalletInfo } from './ton-connect/wallets'; +import QRCode from 'qrcode'; +import TelegramBot from 'node-telegram-bot-api'; +import { getConnector } from './ton-connect/connector'; +import { pTimeout, pTimeoutException } from './utils'; + +let newConnectRequestListenersMap = new Map void>(); + +export async function handleConnectCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + let messageWasDeleted = false; + + newConnectRequestListenersMap.get(chatId)?.(); + + const connector = getConnector(chatId, () => { + unsubscribe(); + newConnectRequestListenersMap.delete(chatId); + deleteMessage(); + }); + + await connector.restoreConnection(); + if (connector.connected) { + const connectedName = + (await getWalletInfo(connector.wallet!.device.appName))?.name || + connector.wallet!.device.appName; + + await bot.sendMessage( + chatId, + `You have already connect ${connectedName} wallet\nYour address: ${toUserFriendlyAddress( + connector.wallet!.account.address, + connector.wallet!.account.chain === CHAIN.TESTNET + )}\n\n Disconnect wallet firstly to connect a new one` + ); + + return; + } + + const unsubscribe = connector.onStatusChange(async wallet => { + if (wallet) { + await deleteMessage(); + + const walletName = + (await getWalletInfo(wallet.device.appName))?.name || wallet.device.appName; + await bot.sendMessage(chatId, `${walletName} wallet connected successfully`); + unsubscribe(); + newConnectRequestListenersMap.delete(chatId); + } + }); + + const wallets = await getWallets(); + + const link = connector.connect(wallets); + const image = await QRCode.toBuffer(link); + + const botMessage = await bot.sendPhoto(chatId, image, { + reply_markup: { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + } + }); + + const deleteMessage = async (): Promise => { + if (!messageWasDeleted) { + messageWasDeleted = true; + await bot.deleteMessage(chatId, botMessage.message_id); + } + }; + + newConnectRequestListenersMap.set(chatId, async () => { + unsubscribe(); + + await deleteMessage(); + + newConnectRequestListenersMap.delete(chatId); + }); +} + +// ... other code +``` + +
+ +我们定义了 `newConnectRequestListenersMap` 来存储每个用户最后一次连接请求的清理回调。 +如果用户多次调用 `/connect`,机器人将删除之前带有二维码的消息。 +此外,我们还订阅了连接器到期超时,以便在二维码消息过期时将其删除。 + +现在我们应该从`connect-wallet-menu.ts`中的函数中移除`connector.onStatusChange`订阅, +因为它们使用相同的连接器实例,且在`handleConnectCommand`中一个订阅足够了。 + +
+src/connect-wallet-menu.ts 代码 + +```ts +// src/connect-wallet-menu.ts + +// ... other code + +async function onOpenUniversalQRClick(query: CallbackQuery, _: string): Promise { + const chatId = query.message!.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + const link = connector.connect(wallets); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent( + link + )}` + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: query.message?.chat.id + } + ); +} + +async function onWalletClick(query: CallbackQuery, data: string): Promise { + const chatId = query.message!.chat.id; + const connector = getConnector(chatId); + + const wallets = await getWallets(); + + const selectedWallet = wallets.find(wallet => wallet.name === data); + if (!selectedWallet) { + return; + } + + const link = connector.connect({ + bridgeUrl: selectedWallet.bridgeUrl, + universalLink: selectedWallet.universalLink + }); + + await editQR(query.message!, link); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: '« Back', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: `Open ${data}`, + url: link + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: chatId + } + ); +} + +// ... other code +``` + +
+ +这样就完成了!编译并运行机器人,尝试两次调用`/connect`。 + +### 优化与 @wallet 的交互 + +从 v3 版本开始,TonConnect 支持连接到如 @wallet 这样的 TWA 钱包。目前在教程中,机器人可以连接到 @wallet。 +然而,我们应该改进重定向策略以提供更好的用户体验。此外,让我们在第一个(“Universal QR”)屏幕上添加 `Connect @wallet` 按钮。 + +首先,让我们创建一些实用函数: + +```ts +// src/utils.ts +import { encodeTelegramUrlParameters, isTelegramUrl } from '@tonconnect/sdk'; + +export const AT_WALLET_APP_NAME = 'telegram-wallet'; + +// ... other code +export function addTGReturnStrategy(link: string, strategy: string): string { + const parsed = new URL(link); + parsed.searchParams.append('ret', strategy); + link = parsed.toString(); + + const lastParam = link.slice(link.lastIndexOf('&') + 1); + return link.slice(0, link.lastIndexOf('&')) + '-' + encodeTelegramUrlParameters(lastParam); +} + +export function convertDeeplinkToUniversalLink(link: string, walletUniversalLink: string): string { + const search = new URL(link).search; + const url = new URL(walletUniversalLink); + + if (isTelegramUrl(walletUniversalLink)) { + const startattach = 'tonconnect-' + encodeTelegramUrlParameters(search.slice(1)); + url.searchParams.append('startattach', startattach); + } else { + url.search = search; + } + + return url.toString(); +} +``` + +TonConnect 在 Telegram 链接中的参数需要以特殊方式编码,这是为什么我们使用 `encodeTelegramUrlParameters` 来编码返回策略参数。 +我们将使用 `addTGReturnStrategy` 为 @wallet 提供正确的返回 URL 至演示机器人。 + +由于我们在两个地方使用通用 QR 页面创建代码,我们将其移动到单独的函数中: + +```ts +// src/utils.ts + +// ... other code + +export async function buildUniversalKeyboard( + link: string, + wallets: WalletInfoRemote[] +): Promise { + const atWallet = wallets.find(wallet => wallet.appName.toLowerCase() === AT_WALLET_APP_NAME); + const atWalletLink = atWallet + ? addTGReturnStrategy( + convertDeeplinkToUniversalLink(link, atWallet?.universalLink), + process.env.TELEGRAM_BOT_LINK! + ) + : undefined; + + const keyboard = [ + { + text: 'Choose a Wallet', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: 'Open Link', + url: `https://ton-connect.github.io/open-tc?connect=${encodeURIComponent(link)}` + } + ]; + + if (atWalletLink) { + keyboard.unshift({ + text: '@wallet', + url: atWalletLink + }); + } + + return keyboard; +} +``` + +这里我们为第一屏幕(通用 QR 屏幕)添加独立的 @wallet 按钮。接下来的工作是在 connect-wallet-menu 和 +command-handlers 中使用这个函数: + +
+src/connect-wallet-menu.ts 代码 + +```ts +// src/connect-wallet-menu.ts + +// ... other code + +async function onOpenUniversalQRClick(query: CallbackQuery, _: string): Promise { + const chatId = query.message!.chat.id; + const wallets = await getWallets(); + + const connector = getConnector(chatId); + + const link = connector.connect(wallets); + + await editQR(query.message!, link); + + const keyboard = await buildUniversalKeyboard(link, wallets); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [keyboard] + }, + { + message_id: query.message?.message_id, + chat_id: query.message?.chat.id + } + ); +} + +// ... other code +``` + +
+ +
+src/commands-handlers.ts 代码 + +```ts +// src/commands-handlers.ts + +// ... other code + +export async function handleConnectCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + let messageWasDeleted = false; + + newConnectRequestListenersMap.get(chatId)?.(); + + const connector = getConnector(chatId, () => { + unsubscribe(); + newConnectRequestListenersMap.delete(chatId); + deleteMessage(); + }); + + await connector.restoreConnection(); + if (connector.connected) { + const connectedName = + (await getWalletInfo(connector.wallet!.device.appName))?.name || + connector.wallet!.device.appName; + await bot.sendMessage( + chatId, + `You have already connect ${connectedName} wallet\nYour address: ${toUserFriendlyAddress( + connector.wallet!.account.address, + connector.wallet!.account.chain === CHAIN.TESTNET + )}\n\n Disconnect wallet firstly to connect a new one` + ); + + return; + } + + const unsubscribe = connector.onStatusChange(async wallet => { + if (wallet) { + await deleteMessage(); + + const walletName = + (await getWalletInfo(wallet.device.appName))?.name || wallet.device.appName; + await bot.sendMessage(chatId, `${walletName} wallet connected successfully`); + unsubscribe(); + newConnectRequestListenersMap.delete(chatId); + } + }); + + const wallets = await getWallets(); + + const link = connector.connect(wallets); + const image = await QRCode.toBuffer(link); + + const keyboard = await buildUniversalKeyboard(link, wallets); + + const botMessage = await bot.sendPhoto(chatId, image, { + reply_markup: { + inline_keyboard: [keyboard] + } + }); + + const deleteMessage = async (): Promise => { + if (!messageWasDeleted) { + messageWasDeleted = true; + await bot.deleteMessage(chatId, botMessage.message_id); + } + }; + + newConnectRequestListenersMap.set(chatId, async () => { + unsubscribe(); + + await deleteMessage(); + + newConnectRequestListenersMap.delete(chatId); + }); +} + +// ... other code +``` + +
+ +现在,我们将正确处理用户在第二个屏幕(选择钱包时)点击钱包按钮时的 TG 链接: + +
+src/connect-wallet-menu.ts 代码 + +```ts +// src/connect-wallet-menu.ts + +// ... other code + + +async function onWalletClick(query: CallbackQuery, data: string): Promise { + const chatId = query.message!.chat.id; + const connector = getConnector(chatId); + + const selectedWallet = await getWalletInfo(data); + if (!selectedWallet) { + return; + } + + let buttonLink = connector.connect({ + bridgeUrl: selectedWallet.bridgeUrl, + universalLink: selectedWallet.universalLink + }); + + let qrLink = buttonLink; + + if (isTelegramUrl(selectedWallet.universalLink)) { + buttonLink = addTGReturnStrategy(buttonLink, process.env.TELEGRAM_BOT_LINK!); + qrLink = addTGReturnStrategy(qrLink, 'none'); + } + + await editQR(query.message!, qrLink); + + await bot.editMessageReplyMarkup( + { + inline_keyboard: [ + [ + { + text: '« Back', + callback_data: JSON.stringify({ method: 'chose_wallet' }) + }, + { + text: `Open ${selectedWallet.name}`, + url: buttonLink + } + ] + ] + }, + { + message_id: query.message?.message_id, + chat_id: chatId + } + ); +} + +// ... other code +``` + +
+ +请注意,我们将不同的链接放置在 QR 和按钮链接(`qrLink` 和 `buttonLink`)中, +因为当用户通过@wallet扫描 QR 时我们不需要重定向,而同时当用户使用按钮链接连接@wallet时我们需要返回到机器人。 + +现在让我们在 `send transaction` 处理器中为 TG 链接添加返回策略: + +
+src/commands-handlers.ts 代码 + +```ts +// src/commands-handlers.ts + +// ... other code + +export async function handleSendTXCommand(msg: TelegramBot.Message): Promise { + const chatId = msg.chat.id; + + const connector = getConnector(chatId); + + await connector.restoreConnection(); + if (!connector.connected) { + await bot.sendMessage(chatId, 'Connect wallet to send transaction'); + return; + } + + pTimeout( + connector.sendTransaction({ + validUntil: Math.round( + (Date.now() + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS)) / 1000 + ), + messages: [ + { + amount: '1000000', + address: '0:0000000000000000000000000000000000000000000000000000000000000000' + } + ] + }), + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS) + ) + .then(() => { + bot.sendMessage(chatId, `Transaction sent successfully`); + }) + .catch(e => { + if (e === pTimeoutException) { + bot.sendMessage(chatId, `Transaction was not confirmed`); + return; + } + + if (e instanceof UserRejectsError) { + bot.sendMessage(chatId, `You rejected the transaction`); + return; + } + + bot.sendMessage(chatId, `Unknown error happened`); + }) + .finally(() => connector.pauseConnection()); + + let deeplink = ''; + const walletInfo = await getWalletInfo(connector.wallet!.device.appName); + if (walletInfo) { + deeplink = walletInfo.universalLink; + } + + if (isTelegramUrl(deeplink)) { + const url = new URL(deeplink); + url.searchParams.append('startattach', 'tonconnect'); + deeplink = addTGReturnStrategy(url.toString(), process.env.TELEGRAM_BOT_LINK!); + } + + await bot.sendMessage( + chatId, + `Open ${walletInfo?.name || connector.wallet!.device.appName} and confirm transaction`, + { + reply_markup: { + inline_keyboard: [ + [ + { + text: `Open ${walletInfo?.name || connector.wallet!.device.appName}`, + url: deeplink + } + ] + ] + } + } + ); +} + +// ... other code +``` + +
+ +就是这样。现在用户能够使用主屏幕上的特殊按钮连接 @wallet,我们也为 TG 链接提供了适当的返回策略。 + +## 添加永久存储 + +目前,我们将 TonConnect 会话存储在 Map 对象中。但你可能希望将其存储到数据库或其他永久存储中,以便在重新启动服务器时保存会话。 +我们将使用 Redis 来实现,但你可以选择任何永久存储。 + +### 设置 redis + +首先运行 `npm i redis`。 + +[查看包详情](https://www.npmjs.com/package/redis) + +要使用 redis,你必须启动 redis 服务器。我们将使用 Docker 镜像: +`docker run -p 6379:6379 -it redis/redis-stack-server:latest` + +现在将 redis 连接参数添加到 `.env`。默认的 redis url 是 `redis://127.0.0.1:6379`。 + +```dotenv +# .env +TELEGRAM_BOT_TOKEN= +MANIFEST_URL=https://ton-connect.github.io/demo-dapp-with-react-ui/tonconnect-manifest.json +WALLETS_LIST_CACHE_TTL_MS=86400000 +DELETE_SEND_TX_MESSAGE_TIMEOUT_MS=600000 +CONNECTOR_TTL_MS=600000 +REDIS_URL=redis://127.0.0.1:6379 +``` + +让我们将 redis 集成到 `TonConnectStorage` 中: + +```ts +// src/ton-connect/storage.ts + +import { IStorage } from '@tonconnect/sdk'; +import { createClient } from 'redis'; + +const client = createClient({ url: process.env.REDIS_URL }); + +client.on('error', err => console.log('Redis Client Error', err)); + +export async function initRedisClient(): Promise { + await client.connect(); +} +export class TonConnectStorage implements IStorage { + constructor(private readonly chatId: number) {} + + private getKey(key: string): string { + return this.chatId.toString() + key; + } + + async removeItem(key: string): Promise { + await client.del(this.getKey(key)); + } + + async setItem(key: string, value: string): Promise { + await client.set(this.getKey(key), value); + } + + async getItem(key: string): Promise { + return (await client.get(this.getKey(key))) || null; + } +} +``` + +要使其工作,我们必须在 `main.ts` 中等待 redis 初始化。让我们将此文件中的代码包装在一个异步函数中: + +```ts +// src/main.ts +// ... imports + +async function main(): Promise { + await initRedisClient(); + + const callbacks = { + ...walletMenuCallbacks + }; + + bot.on('callback_query', query => { + if (!query.data) { + return; + } + + let request: { method: string; data: string }; + + try { + request = JSON.parse(query.data); + } catch { + return; + } + + if (!callbacks[request.method as keyof typeof callbacks]) { + return; + } + + callbacks[request.method as keyof typeof callbacks](query, request.data); + }); + + bot.onText(/\/connect/, handleConnectCommand); + + bot.onText(/\/send_tx/, handleSendTXCommand); + + bot.onText(/\/disconnect/, handleDisconnectCommand); + + bot.onText(/\/my_wallet/, handleShowMyWalletCommand); +} + +main(); +``` + +## 总结 + +接下来 + +- 如果你想在生产环境中运行机器人,你可能想要安装并使用进程管理器,如 [pm2](https://pm2.keymetrics.io/)。 +- 你可以在机器人中增加更好的错误处理。 + +## 参阅 + +- [发送消息](/develop/dapps/ton-connect/transactions) +- [集成手册](/develop/dapps/ton-connect/integration) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/coins.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/coins.md new file mode 100644 index 0000000000..828b5417f1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/coins.md @@ -0,0 +1,29 @@ +# 原生代币:Toncoin + +TON 区块链的原生加密货币是 **Toncoin**。 + +交易费、gas费(即智能合约消息处理费)和持久存储费用都以 Toncoin 收取。 + +Toncoin 用于支付成为区块链验证者所需的押金。 + +制作 Toncoin 支付的过程在[相应部分](/develop/dapps/asset-processing)有描述。 + +您可以在[网站](https://ton.org/coin)上找到在哪里购买或交换 Toncoin。 + +## 额外代币 + +TON 区块链支持多达 2^32 种内建的额外代币。 + +额外代币余额可以存储在每个区块链账户上,并原生地(在一个智能合约到另一个智能合约的内部消息中,您可以除了 Toncoin 数量之外,指定一个额外代币数量的哈希映射)转移到其他账户。 + +TLB: `extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) = ExtraCurrencyCollection;` - 代币 ID 和数量的哈希映射。 + +然而,额外代币只能像 Toncoin 那样存储和转移,并且没有自己的任意代码或功能。 + +注意,如果创建了大量额外代币,账户会因为需要存储它们而“膨胀”。 + +因此,额外代币最适合用于知名的去中心化货币(例如,Wrapped Bitcoin 或 Ether),并且创建这样的额外代币应该相当昂贵。 + +对于其他任务,[Jettons](/develop/dapps/defi/tokens#jettons) 更为合适。 + +目前,TON 区块链上尚未创建任何额外代币。TON 区块链对账户和消息完全支持额外代币,但创建它们的铸币系统合约尚未创建。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/subscriptions.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/subscriptions.md new file mode 100644 index 0000000000..c8a043bf14 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/subscriptions.md @@ -0,0 +1,38 @@ +# 内容订阅 + +由于 TON 区块链中的交易快速,网络费用低廉,您可以通过智能合约在链上处理定期支付。 + +例如,用户可以订阅数字内容(或其他任何东西)并被收取每月 1 TON 的费用。 + +:::tip +此内容是版本v4的钱包的特定内容。旧钱包没有此功能;它也可以在未来版本中更改。 +::: + +:::warning +Subscription contract requires authorization exactly once, on installation; then it can withdraw TON as it pleases. Do your own research before attaching unknown subscriptions. + +另一方面,用户不能在不知情的情况下安装订阅程序。 +::: + +## 示例流程 + +- 用户使用 v4 钱包。它允许额外的智能合约,称为插件,扩展它的功能。 + + 在确保其功能后,用户可以批准他钱包的可信智能合约(插件)的地址。 此后,可信的智能合约可以从钱包中提取Tonco币。这类似于其他区块链中的“无限审批”。 + +- 每个用户和服务之间使用中间订阅智能合约作为钱包插件。 + + 该智能合约保证在指定周期内,用户钱包中的指定数量的 Toncoin 不会被扣除超过一次。 + +- 服务的后端通过向订阅智能合约发送外部消息,定期发起支付。 + +- 用户或服务可以决定他们不再需要订阅并终止订阅。 + +## 智能合约示例 + +- [钱包 v4 智能合约源代码](https://github.com/ton-blockchain/wallet-contract/blob/main/func/wallet-v4-code.fc) +- [订阅智能合约源代码](https://github.com/ton-blockchain/wallet-contract/blob/main/func/simple-subscription-plugin.fc) + +## 实现 + +一个良好的实现案例是通过 [@donate](https://t.me/donate) 机器人和 [Tonkeeper 钱包](https://tonkeeper.com) 对 Telegram 中私人频道的 Toncoin 进行去中心化订阅。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/tokens.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/tokens.mdx new file mode 100644 index 0000000000..7d3f2352c2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/tokens.mdx @@ -0,0 +1,115 @@ +import Button from '@site/src/components/button' + +# 代币(FT, NFT) + +[分布式 TON 代币概述](https://telegra.ph/Scalable-DeFi-in-TON-03-30) + +> 由 TON 驱动的代币和 NFT 没有单一中心,不会造成瓶颈。 +> +> 特定集合中的每个 NFT 都是一个独立的智能合约。每个用户的代币余额都存储在单独的用户钱包中。 +> +> 智能合约之间直接互动,分散了整个网络的负载。 +> +> 随着用户和交易数量的增长,负载仍将保持均衡,从而使网络得以扩展。 + +## TON 课程:Jettons 和 NFT + +[TON Blockchain 课程](https://stepik.org/course/201638/)是TON Blockchain 开发的综合指南。 + +模块 7 完全涵盖**NFT 和 Jettons**开发。 + + + + + + + + + + + +## 教程 + +- [Web3 游戏教程](/develop/dapps/tutorials/building-web3-game) - 学习如何使用 TON 区块链构建 Web3 游戏。 +- [铸造您的第一个 Jetton](/develop/dapps/tutorials/jetton-minter/) - 学习如何部署和定制您的第一个 Jetton +- [\[YouTube\] TON Keeper 创始人 Oleg Andreev and Oleg Illarionov 谈 TON jettons](https://www.youtube.com/watch?v=oEO29KmOpv4) + +### TON 速成 + +查看 [TON 速成](https://tonspeedrun.com/) 系列,其中包括 NFT 和 Jetton 开发: + +- [🚩 挑战 1:简单的 NFT 部署](https://github.com/romanovichim/TONQuest1) +- [🚩 挑战2:聊天机器人合约](https://github.com/romanovichim/TONQuest2) +- [🚩 挑战 3:Jetton 自动售卖机](https://github.com/romanovichim/TONQuest3) +- [🚩 挑战 4:抽奖/彩票](https://github.com/romanovichim/TONQuest4) +- [🚩 挑战 5:在 5 分钟内创建与合同交互的用户界面](https://github.com/romanovichim/TONQuest5) +- [🚩 挑战 6:分析 Getgems 市场上的 NFT 销售](https://github.com/romanovichim/TONQuest6) + +## Jettons(可替代代币) + +### 指南 + +- [TON Jetton 处理](/develop/dapps/asset-processing/jettons.md) +- [TON 元数据解析](/develop/dapps/asset-processing/metadata.md) + +### 标准 + +- [Jettons 标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) + +### 智能合约 + +- [智能合约实现(FunC)](https://github.com/ton-blockchain/token-contract/) + +### Jetton 部署器 + +Jettons 是 TON 区块链上的自定义可替代代币。您可以使用下面的 Jetton Deployer 示例在 TON 区块链上创建自己的代币: + +- **[TON Minter](https://minter.ton.org/)** - 开放源码 Jetton Deployer dApp +- [Jetton Deployer - contracts](https://github.com/ton-defi-org/jetton-deployer-contracts) (FunC, TL-B) +- [Jetton Deployer - WebClient](https://github.com/ton-defi-org/jetton-deployer-webclient) (React, TypeScript) + +### 使用 Jettons 的工具 + +- [NFT Jetton 销售合同](https://github.com/dvlkv/nft-jetton-sale-smc) - 支持 jetton 的 NFT 销售合同 +- [Scaleton](http://scaleton.io)—查看您的自定义代币余额 +- [@tegro/ton3-client](https://github.com/TegroTON/ton3-client#jettons-example)—查询 Jettons 信息的 SDK + +## NFT + +### 标准 + +- [NFT 标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) +- [SBT(Soulbound NFT)标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0085-sbt-standard.md) +- [NFTRoyalty标准扩展](https://github.com/ton-blockchain/TEPs/blob/master/text/0066-nft-royalty-standard.md) + +### 智能合约 + +- [智能合约实施(FunC)](https://github.com/ton-blockchain/token-contract/) +- [Getgems NFT、销售、拍卖智能合约 (FunC)](https://github.com/getgems-io/nft-contracts) + +### NFT 铸币器 + +- [由 TON Diamonds 提供的 NFT 部署器](https://github.com/tondiamonds/ton-nft-deployer) (TypeScript,无注释) +- [NFT 铸币示例](https://github.com/ton-foundation/token-contract/tree/main/nft/web-example) (JavaScript,带注释) +- [使用 React 的 NFT 铸币器](https://github.com/tonbuilders/tonbuilders-minter) (React,无注释) +- [NFT 部署器](https://github.com/anomaly-guard/nft-deployer) (Python,带注释) +- [使用 Golang 的 NFT 铸币器](https://github.com/xssnick/tonutils-go#nft) (Golang 库,带注释和完整示例) + +### 使用 NFT 的工具 + +- [LiberMall/tnt](https://github.com/LiberMall/tnt)—TNT 是一个一体化命令行工具,用于在 The Open Network 上查询、编辑和铸造新的非同质化代币。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/ton-payments.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/ton-payments.md new file mode 100644 index 0000000000..726a5e7d57 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/defi/ton-payments.md @@ -0,0 +1,38 @@ +# TON 支付 + +TON 支付是微支付通道的平台。 + +它允许即时支付,无需将所有交易提交到区块链、支付相关交易费用(例如,为消耗的燃料支付费用)以及等待五秒钟,直到确认包含有关交易的区块。 + +因为此类即时支付的总费用如此之低,它们可以用于游戏、API 和链下应用程序中的微支付。[查看示例](/develop/dapps/defi/ton-payments#examples)。 + +- [TON 上的支付](https://blog.ton.org/ton-payments) + +## 支付通道 + +### 智能合约 + +- [ton-blockchain/payment-channels](https://github.com/ton-blockchain/payment-channels) + +### SDK + +使用支付通道,您不需要深入了解加密学。 + +您可以使用准备好的 SDK: + +- [toncenter/tonweb](https://github.com/toncenter/tonweb) JavaScript SDK +- [toncenter/payment-channels-example](https://github.com/toncenter/payment-channels-example) - 如何使用 tonweb 的支付通道。 + +### 示例 + +在 [Hack-a-TON #1](https://ton.org/hack-a-ton-1) 获奖者中找到支付通道的使用示例: + +- [grejwood/Hack-a-TON](https://github.com/Grejwood/Hack-a-TON) - OnlyTONs 支付项目([网站](https://main.d3puvu1kvbh8ti.amplifyapp.com/),[视频](https://www.youtube.com/watch?v=38JpX1vRNTk)) +- [nns2009/Hack-a-TON-1_Tonario](https://github.com/nns2009/Hack-a-TON-1_Tonario) - OnlyGrams 支付项目([网站](https://onlygrams.io/),[视频](https://www.youtube.com/watch?v=gm5-FPWn1XM)) +- [sevenzing/hack-a-ton](https://github.com/sevenzing/hack-a-ton) - TON 中的按需求支付 API 使用([视频](https://www.youtube.com/watch?v=7lAnbyJdpOA\&feature=youtu.be)) +- [illright/diamonds](https://github.com/illright/diamonds) - 按分钟支付学习平台([网站](https://diamonds-ton.vercel.app/),[视频](https://www.youtube.com/watch?v=g9wmdOjAv1s)) + +## 参阅 + +- [支付处理](/develop/dapps/asset-processing) +- [TON Connect](/develop/dapps/ton-connect) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/oracles/about_blockchain_oracles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/oracles/about_blockchain_oracles.md new file mode 100644 index 0000000000..3c5561991c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/dapps/oracles/about_blockchain_oracles.md @@ -0,0 +1,61 @@ +# 关于预言机 + +预言机是将区块链与外部系统连接在一起的实体,允许智能合同根据现实世界的输入和输出执行。 + +## 区块链预言机如何工作 + +区块链预言机是一种专门服务,是现实世界与区块链技术之间的桥梁。它们为智能合约提供来自外部世界的相关必要信息,如汇率、支付状态甚至天气状况。这些数据有助于自动执行智能合约,而无需人工直接干预。 + +预言机背后的基本原则是,他们能够通过连接到各种在线资源来收集数据,从而在区块链之外运作。 虽然预言机并不是区块链本身的一部分, 它们作为可靠地将外部数据输入系统的中间人,在使其运作方面发挥着关键作用。 + +大多数交易工具都倾向于去中心化,以避免依赖单一数据源所带来的风险。这为系统提供了更高的安全性和可靠性,因为数据在用于智能合约之前会通过节点网络进行验证和确认。这种方法最大限度地降低了操纵和错误的风险,确保了所提供信息的准确性和时效性。 + +## 区块链规则的多样性 + +区块链按不同方面分类:操作机制、数据来源、数据方向和治理结构。 让我们看看最常见的预言机类型。 + +### 软件和硬件预言机 + +软件与演处理存储在数据库、服务器、云存储等各种数字源中的在线数据。硬件预言机将物理世界与数字世界连接起来,使用传感器和扫描仪将现实世界事件的数据传输到区块链上。 + +### 传入和传出的预言机 + +导入预言机向区块链输入信息,例如用于保险合同的天气数据。而导出预言机则将数据从区块链发送到外部世界,如交易通知。这是提高可靠性和解决单点故障问题所必需的。 + +### 集中式和去中心化预言机 + +集中式预言机由单方控制,存在安全和可靠性风险。去中心化型预言机使用多个节点来验证数据,因此更加安全可靠。 + +### 特定智能合约的预言机 + +这些预言机是为了某些智能合约而单独研制的,由于其特殊性和高昂的开发成本,这些预言机可能不那么受欢迎。 + +### 跨链预言机 + +这些用于在不同的区块链之间传输数据。当网络不兼容时使用。 有助于使用跨链交易的分散应用程序,例如将加密资产从一个网络转移到另一个网络。 + +## 区块链预言机的应用 + +区块链预言机架起了区块链数字世界与现实生活之间的桥梁,开辟了广泛的应用领域。让我们来看看区块链预言机的一些最常用的用途。 + +### DeFi(去中心化金融) + +通过提供市场价格和加密货币数据,预言机在 DeFi 生态系统中发挥着至关重要的作用。价格指标允许 DeFi 平台将代币价值与实际资产挂钩,这对于控制流动性和确保用户头寸至关重要。这使得交易更加透明和安全,有助于提高金融交易的稳定性和可靠性。 + +### 保险 + +预言机可以自动读取和分析各种来源的数据,以确定保险事件的发生。 这使保险合同能够自动支付索偿,减少了手工处理每个案件的必要性,并加快了对保险事件的反应时间。 + +### 物流 + +在物流中使用oracles,可以让智能合约根据从车辆上的条形码扫描仪或传感器接收到的数据自动执行支付和其他操作。这可以最大限度地减少错误和延误,从而提高交付的准确性和效率。 + +### 随机号码生成 + +在智能合约中很难生成随机数,因为所有操作都必须是可复制和可预测的,这与随机性的概念相矛盾。预言机通过将外部世界的数据引入合约来解决这个问题。它们可以为游戏和彩票生成可验证的随机数,确保结果的公平性和透明度。 + +## TON 中的预言机列表 + +- [RedStone Oracles](/develop/oracles/red_stone) + +## 另见: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tl.md new file mode 100644 index 0000000000..5360976552 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tl.md @@ -0,0 +1,45 @@ +# TL + +TL(类型语言)是一种用于描述数据结构的语言。 + +为了结构化有用的数据,在通信时使用 [TL 架构](https://github.com/ton-blockchain/ton/tree/master/tl/generate/scheme)。 + +TL 操作 32 位块。因此,TL 中的数据大小必须是 4 字节的倍数。 +如果对象的大小不是 4 的倍数,我们需要添加所需数量的零字节,直到达到倍数。 + +数字始终以小端序编码。 + +有关 TL 的更多详情可以在 [Telegram 文档](https://core.telegram.org/mtproto/TL)中找到。 + +## 编码字节数组 + +要编码一个字节数组,我们首先需要确定其大小。 +如果它少于 254 字节,则使用 1 字节作为大小的编码。如果更多, +则将 0xFE 写为第一个字节,作为大数组的指示符,其后跟随 3 字节的大小。 + +例如,我们编码数组 `[0xAA, 0xBB]`,其大小为 2。我们使用 1 字节 +大小然后写入数据本身,我们得到 `[0x02, 0xAA, 0xBB]`,完成,但我们看到 +最终大小为 3,不是 4 字节的倍数,那么我们需要添加 1 字节的填充使其达到 4。结果:`[0x02, 0xAA, 0xBB, 0x00]`。 + +如果我们需要编码一个大小等于例如 396 的数组, +我们这样做:396 >= 254,所以我们使用 3 字节进行大小编码和 1 字节超尺寸指示符, +我们得到:`[0xFE, 0x8C, 0x01, 0x00, 数组字节]`,396+4 = 400,是 4 的倍数,无需对齐。 + +## 非明显的序列化规则 + +通常,在架构本身之前会写入一个 4 字节前缀 - 其 ID。架构 ID 是从架构文本的 CRC32,其中 IEEE 表,同时从文本中先前删除了诸如 `;` 和括号 `()` 的符号。带有 ID 前缀的架构序列化称为 **boxed**,这使解析器能够确定在它之前出现的是哪个架构(如果有多个选项)。 + +如何确定是否序列化为 boxed?如果我们的架构是另一个架构的一部分,那么我们需要看字段类型是如何指定的,如果它是明确指定的,那么我们序列化时不带前缀,如果不是明确的(有很多这样的类型),那么我们需要序列化为 boxed。例子: + +```tlb +pub.unenc data:bytes = PublicKey; +pub.ed25519 key:int256 = PublicKey; +pub.aes key:int256 = PublicKey; +pub.overlay name:bytes = PublicKey; +``` + +我们有这样的类型,如果在架构中指定了 `PublicKey`,例如 `adnl.node id:PublicKey addr_list:adnl.addressList = adnl.Node`,那么它没有明确指定,我们需要使用 ID 前缀(boxed)序列化。而如果它被指定为这样:`adnl.node id:pub.ed25519 addr_list:adnl.addressList = adnl.Node`,那么它就是明确的,不需要前缀。 + +## 参考资料 + +*这里是 [Oleg Baranov](https://github.com/xssnick) 的[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/TL.md)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/block-layout.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/block-layout.md new file mode 100644 index 0000000000..e745462e82 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/block-layout.md @@ -0,0 +1,238 @@ +# 区块布局 + +:::info +为了最大限度地理解本页内容,强烈建议您熟悉 [TL-B 语言](/develop/data-formats/cell-boc)。 +::: + +区块链中的一个区块是一条新交易记录,一旦完成,就会作为这个去中心化账本的永久且不可更改的一部分被添加到区块链上。每个区块包含交易数据、时间以及对前一个区块的引用等信息,从而形成一个区块链。 + +TON 区块链中的区块由于系统的整体复杂性而具有相当复杂的结构。本页描述了这些区块的结构和布局。 + +## 区块 + +一个区块的原始 TL-B 方案如下: + +```tlb +block#11ef55aa global_id:int32 + info:^BlockInfo value_flow:^ValueFlow + state_update:^(MERKLE_UPDATE ShardState) + extra:^BlockExtra = Block; +``` + +让我们仔细看看每个字段。 + +## global_id:int32 + +创建此区块的网络的 ID。主网为 `-239`,测试网为 `-3`。 + +## info:^BlockInfo + +此字段包含关于区块的信息,如其版本、序列号、标识符和其他标志位。 + +```tlb +block_info#9bc7a987 version:uint32 + not_master:(## 1) + after_merge:(## 1) before_split:(## 1) + after_split:(## 1) + want_split:Bool want_merge:Bool + key_block:Bool vert_seqno_incr:(## 1) + flags:(## 8) { flags <= 1 } + seq_no:# vert_seq_no:# { vert_seq_no >= vert_seqno_incr } + { prev_seq_no:# } { ~prev_seq_no + 1 = seq_no } + shard:ShardIdent gen_utime:uint32 + start_lt:uint64 end_lt:uint64 + gen_validator_list_hash_short:uint32 + gen_catchain_seqno:uint32 + min_ref_mc_seqno:uint32 + prev_key_block_seqno:uint32 + gen_software:flags . 0?GlobalVersion + master_ref:not_master?^BlkMasterInfo + prev_ref:^(BlkPrevInfo after_merge) + prev_vert_ref:vert_seqno_incr?^(BlkPrevInfo 0) + = BlockInfo; +``` + +| 字段 | 类型 | 描述 | +| ---------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- | +| `version` | uint32 | 区块结构的版本。 | +| `not_master` | (## 1) | 标志位,表示此区块是否为主链区块。 | +| `after_merge` | (## 1) | 标志位,表示此区块是否在两个分片链合并后创建,因此它有两个父区块。 | +| `before_split` | (## 1) | 标志位,表示此区块是否在其分片链分裂前创建。 | +| `after_split` | (## 1) | 标志位,表示此区块是否在其分片链分裂后创建。 | +| `want_split` | Bool | 标志位,表示是否希望分片链分裂。 | +| `want_merge` | Bool | 标志位,表示是否希望分片链合并。 | +| `key_block` | Bool | 标志位,表示此区块是否为关键区块。 | +| `vert_seqno_incr` | (## 1) | 垂直序列号的增量。 | +| `flags` | (## 8) | 区块的附加标志位。 | +| `seq_no` | # | 与区块相关的序列号。 | +| `vert_seq_no` | # | 与区块相关的垂直序列号。 | +| `shard` | ShardIdent | + + 此区块所属的分片的标识符。 | +| `gen_utime` | uint32 | 区块的生成时间。 | +| `start_lt` | uint64 | 与区块相关的开始逻辑时间。 | +| `end_lt` | uint64 | 与区块相关的结束逻辑时间。 | +| `gen_validator_list_hash_short` | uint32 | 生成此区块时验证者列表的短哈希。 | +| `gen_catchain_seqno` | uint32 | 与此区块相关的 [Catchain](/catchain.pdf) 序列号。 | +| `min_ref_mc_seqno` | uint32 | 引用的主链区块的最小序列号。 | +| `prev_key_block_seqno` | uint32 | 上一个关键区块的序列号。 | +| `gen_software` | GlobalVersion | 生成区块的软件版本。仅当 `version` 的第一位设置为 `1` 时呈现。 | +| `master_ref` | BlkMasterInfo | 如果区块不是主区块,则引用主区块。存储在引用块中。 | +| `prev_ref` | BlkPrevInfo after_merge | 引用前一个区块。存储在引用中。 | +| `prev_vert_ref` | BlkPrevInfo 0 | 如果存在,则引用垂直序列中的前一个区块。存储在引用中。 | + +### value_flow:^ValueFlow + +此字段代表区块内的货币流动,包括收集的费用和其他涉及货币的交易。 + +```tlb +value_flow#b8e48dfb ^[ from_prev_blk:CurrencyCollection + to_next_blk:CurrencyCollection + imported:CurrencyCollection + exported:CurrencyCollection ] + fees_collected:CurrencyCollection + ^[ + fees_imported:CurrencyCollection + recovered:CurrencyCollection + created:CurrencyCollection + minted:CurrencyCollection + ] = ValueFlow; +``` + +| 字段 | 类型 | 描述 | +| ----------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| `from_prev_blk` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 表示从前一个区块流入的货币。 | +| `to_next_blk` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 表示流向下一个区块的货币。 | +| `imported` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 表示导入到区块的货币。 | +| `exported` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 表示从区块导出的货币。 | +| `fees_collected` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 区块中收集的费用总额。 | +| `fees_imported` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 导入到区块的费用金额。仅在主链中非零。 | +| `recovered` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 区块中恢复的货币金额。仅在主链中非零。 | +| `created` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 区块中创建的新货币金额。仅在主链中非零。 | +| `minted` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 区块中铸造的货币金额。仅在主链中非零。 | + +## state_update:^(MERKLE_UPDATE ShardState) + +此字段代表分片状态的更新。 + +```tlb +!merkle_update#02 {X:Type} old_hash:bits256 new_hash:bits256 + old:^X new:^X = MERKLE_UPDATE X; +``` + +| 字段 | 类型 | 描述 | +| ------------ | ------------------------- | ----------------------------------------------------- | +| `old_hash` | bits256 | 分片状态的旧哈希值。 | +| `new_hash` | bits256 | 分片状态的新哈希值。 | +| `old` | [ShardState](#shardstate) | 分片的旧状态。存储为引用。 | +| `new` | [ShardState](#shardstate) | 分片的新状态。存储为引用。 | + +### ShardState + +`ShardState` 可以包含关于分片的信息,或者在该分片被拆分的情况下,包含关于左右两个拆分部分的信息。 + +```tlb +_ ShardStateUnsplit = ShardState; +split_state#5f327da5 left:^ShardStateUnsplit right:^ShardStateUnsplit = ShardState; +``` + +### ShardState 未拆分 + +```tlb +shard_state#9023afe2 global_id:int32 + shard_id:ShardIdent + seq_no:uint32 vert_seq_no:# + gen_utime:uint32 gen_lt:uint64 + min_ref_mc_seqno:uint32 + out_msg_queue_info:^OutMsgQueueInfo + before_split:(## 1) + accounts:^ShardAccounts + ^[ overload_history:uint64 underload_history:uint64 + total_balance:CurrencyCollection + total_validator_fees:CurrencyCollection + libraries:(HashmapE 256 LibDescr) + master_ref:(Maybe BlkMasterInfo) ] + custom:(Maybe ^McStateExtra) + = ShardStateUnsplit; +``` + +| 字段 | 类型 | 是否必需 | 描述 | +| --------------------------- | ---------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- | +| `global_id` | int32 | 是 | 此分片所属网络的 ID。主网为 `-239`,测试网为 `-3`。 | +| `shard_id` | ShardIdent | 是 | 分片的标识符。 | +| `seq_no` | uint32 | 是 | 此分片链的最新序列号。 | +| `vert_seq_no` | # | 是 | 此分片链的最新垂直序列号。 | +| `gen_utime` | uint32 | 是 | 创建分片的生成时间。 | +| `gen_lt` | uint64 | 是 | 创建分片的逻辑时间。 | +| `min_ref_mc_seqno` | uint32 | 是 | 最新引用的主链区块的序列号。 | +| `out_msg_queue_info` | OutMsgQueueInfo | 是 | 此分片的出消息队列信息。存储为引用。 | +| `before_split` | ## 1 | 是 | 标志位,表示此分片链的下一个区块是否将发生拆分。 | +| `accounts` | ShardAccounts | 是 | 分片中账户的状态。存储为引用。 | +| `overload_history` | uint64 | 是 | 分片的超载事件历史。用于通过分片进行负载均衡。 | +| `underload_history` | uint64 | 是 | 分片的欠载事件历史。用于通过分片进行负载均衡。 | +| `total_balance` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 是 | 分片的总余额。 | +| `total_validator_fees` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 是 | 分片的总验证者费用。 | +| `libraries` | HashmapE 256 LibDescr | 是 | 此分片中的库描述哈希映射。目前仅在主链中非空。 + + | +| `master_ref` | BlkMasterInfo | 否 | 主块信息的引用。 | +| `custom` | McStateExtra | 否 | 分片状态的自定义额外数据。此字段仅在主链中存在,包含所有主链特有的数据。存储为引用。 | + +### ShardState 拆分 + +| 字段 | 类型 | 描述 | +| ---------- | -------------------------------------------- | --------------------------------------------------------- | +| `left` | [ShardStateUnsplit](#shardstate-unsplitted) | 拆分后左侧分片的状态。存储为引用。 | +| `right` | [ShardStateUnsplit](#shardstate-unsplitted) | 拆分后右侧分片的状态。存储为引用。 | + +## extra:^BlockExtra + +此字段包含有关区块的额外信息。 + +```tlb +block_extra in_msg_descr:^InMsgDescr + out_msg_descr:^OutMsgDescr + account_blocks:^ShardAccountBlocks + rand_seed:bits256 + created_by:bits256 + custom:(Maybe ^McBlockExtra) = BlockExtra; +``` + +| 字段 | 类型 | 是否必需 | 描述 | +| ----------------- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------- | +| `in_msg_descr` | InMsgDescr | 是 | 区块中传入消息的描述符。存储为引用。 | +| `out_msg_descr` | OutMsgDescr | 是 | 区块中传出消息的描述符。存储为引用。 | +| `account_blocks` | ShardAccountBlocks | 是 | 区块中处理的所有交易及分片分配账户状态的更新集合。存储为引用。 | +| `rand_seed` | bits256 | 是 | 区块的随机种子。 | +| `created_by` | bits256 | 是 | 创建区块的实体(通常是验证者的公钥)。 | +| `custom` | [McBlockExtra](#mcblockextra) | 否 | 此字段仅在主链中存在,包含所有主链特有的数据。区块的自定义额外数据。存储为引用。 | + +### McBlockExtra + +此字段包含有关主链区块的额外信息。 + +```tlb +masterchain_block_extra#cca5 + key_block:(## 1) + shard_hashes:ShardHashes + shard_fees:ShardFees + ^[ prev_blk_signatures:(HashmapE 16 CryptoSignaturePair) + recover_create_msg:(Maybe ^InMsg) + mint_msg:(Maybe ^InMsg) ] + config:key_block?ConfigParams + = McBlockExtra; +``` + +| 字段 | 类型 | 是否必需 | 描述 | +| ---------------------- | --------------------------------- | -------- | ------------------------------------------------------------------------------ | +| `key_block` | ## 1 | 是 | 标志位,表示区块是否为关键区块。 | +| `shard_hashes` | ShardHashes | 是 | 相应分片链的最新区块的哈希。 | +| `shard_fees` | ShardFees | 是 | 此区块中所有分片收集的总费用。 | +| `prev_blk_signatures` | HashmapE 16 CryptoSignaturePair | 是 | 前一区块的签名。 | +| `recover_create_msg` | InMsg | 否 | 与恢复额外代币相关的消息(如果有)。存储为引用。 | +| `mint_msg` | InMsg | 否 | 与铸造额外代币相关的消息(如果有)。存储为引用。 | +| `config` | ConfigParams | 否 | 此区块的实际配置参数。当设置了 `key_block` 时此字段存在。 | + +## 参阅 + +- [白皮书](https://docs.ton.org/tblkch.pdf#page=96&zoom=100,148,172)中原始的[区块布局](#)描述 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/canonical-cell-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/canonical-cell-serialization.md new file mode 100644 index 0000000000..971098e2ec --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/canonical-cell-serialization.md @@ -0,0 +1,47 @@ +# 规范化cell序列化 + +## cell权重 + +`Weight`是定义每个cell在cell树中的特征,具体如下: + +- 如果cell是cell树中的叶节点:`weight = 1`; +- 对于普通cell(非叶子),权重是一个总和:`cell weight = children weight + 1`; +- 如果cell是_特殊_的,其权重设为零。 + +以下算法解释了我们如何以及何时为每个cell分配权重以创建一个权重平衡的树。 + +## 权重重排序算法 + +每个cell都是权重平衡树,[reorder_cells()](https://github.com/ton-blockchain/ton/blob/15088bb8784eb0555469d223cd8a71b4e2711202/crypto/vm/boc.cpp#L249) 方法 +基于累积子权重重新分配权重。遍历顺序是根 -> 子节点。这是一种广度优先搜索,_可能_用于保持缓存线性。它还触发哈希大小的重新计算,并对包(根)和每个树进行重新索引,为空引用设置新索引。尽管重新索引是深度优先的,可能存在某些依赖于此索引顺序的内容,正如白皮书所述,这是首选的。 + +要遵循原始节点的cell序列化,您应该: + +- 首先,如果cell的权重尚未设置(节点在cell导入时这样做),我们将每个cell的权重设置为`1 + sum_child_weight`,其中`sum_child_weight`是其子节点权重的总和。我们添加1以使叶子具有1的权重。 + +- 遍历所有根,对于每个根cell: + - 检查它的每个引用是否有一个权重小于`maximum_possible_weight - 1 + ref_index`除以根cell引用的数量,以便它们均匀地共享父权重,我们做(+ index)以确保如果语言在除法中向0取整,我们总是得到一个数学上四舍五入的数字(例如对于5 / 3,c++会返回1,但我们在这里希望2) + + - 如果一些引用违反了该规则,我们将它们添加到列表中(或更有效地创建一个位掩码,就像原始节点所做的那样),然后再次遍历这些引用,并将它们的权重限制为`weight_left / invalid_ref_count`,其中`weight_left`是`maximum_possible_weight - 1 - sum_of_valid_refs_weights`。在代码中,它可以实现为减少一个计数器变量,该变量首先初始化为`maximum_possible_weight - 1`,然后当`counter -= valid_ref_weight`时递减。因此,我们基本上在这些节点之间重新分配剩余权重(平衡它们) + +- 再次遍历根,对于每个根: + - 确保其引用的新权重总和小于`maximum_possible_weight`,检查新总和是否小于先前根cell的权重,并将其权重限制为新总和。(如果`new_sum < root_cell_weight`,则将`root_cell_weight`设置为等于`new_sum`) + - 如果新总和高于根的权重,则它应该是一个特殊节点,其权重为0,设置它。(这里通过节点的哈希计数增加内部哈希计数) + +- 再次遍历根,对于每个根: + 如果它不是特殊节点(如果其权重> 0),则通过节点的哈希计数将顶部哈希计数增加。 + +- 递归地重新索引树: + - 首先,我们预访问所有根cell。如果我们之前没有预访问或访问过此节点,请递归检查其所有引用以查找特殊节点。如果我们找到一个特殊节点,我们必须在其他节点之前预访问和访问它,这意味着特殊节点的子节点将首先出现在列表中(它们的索引将是最低的)。然后我们添加其他节点的子节点(顺序最深 -> 最高)。根在列表的最后(它们有最大的索引)。因此,最终我们得到一个排序的列表,其中节点越深,其索引就越低。 + +`maximum_possible_weight`是常数64 + +## 注释 + +- 特殊cell没有权重(它是0) + +- 确保导入时的权重适合8位(weight \<= 255) + +- 内部哈希计数是所有特殊根节点的哈希计数之和 + +- 顶部哈希计数是所有其他(非特殊)根节点的哈希计数之和 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/cell-boc.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/cell-boc.mdx new file mode 100644 index 0000000000..6d708d5078 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/cell-boc.mdx @@ -0,0 +1,300 @@ +import ThemedImage from '@theme/ThemedImage'; + +# cell与cell包 (BoC) + +## cell + +cell是TON区块链上的一种数据结构。cell能够存储多达1023位,并且可以拥有最多4个对其他cell的引用。 + +

+ +

+ +## cell包 + +cell包 (BoC) 是一种将cell序列化为字节数组的格式,这一格式在 [TL-B schema](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/tl/boc.tlb#L25) 中有进一步描述。 + +

+ +

+ +在TON上,所有东西都由cell构成,包括合约代码、存储的数据、区块等,实现了过程中的流线型和强大的灵活性。 + +

+ +

+ +### cell序列化 + +让我们分析我们的第一个cell包示例: + +

+ +

+ +```json +1[8_] -> { + 24[0AAAAA], + 7[FE] -> { + 24[0AAAAA] + } +} +``` + +在此示例中,我们有一个1位大小的根cell,它有2个链接:第一个指向一个24位cell,第二个指向一个7位cell,后者又有一个链接指向一个24位cell。 + +为了使此框架按预期工作,需要将cell转换为单一的字节序列。为此,首先,我们只利用唯一的cell类型,下面3个中的2个如下所示: + +```json +1[8_] +24[0AAAAA] +7[FE] +``` + +:::note +为了只留下唯一的cell,需要进行比较。为此,我们需要比较cell的[哈希](#cell-hash)。 +::: + +```json +1[8_] -> index 0 (root cell) +7[FE] -> index 1 +24[0AAAAA] -> index 2 +``` + +现在,让我们计算上述3个cell的描述。这些描述由2个字节组成,存储了有关数据长度和数据链接数量的标志信息。 + +第一个字节 - **引用描述符** - 计算为 `r+8s+32l`,其中 `0 ≤ r ≤ 4` 是cell引用(链接)的数量,`0 ≤ s ≤ 1` 对于[异类](#special-exotic-cells)cell为1,对于普通cell为0,`0 ≤ l ≤ 3` 是cell的[层级](#cell-level)。 + +第二个字节 - **位描述符** - 等于 `floor(b / 8) + ceil (b / 8)`,其中 `0 <= b <= 1023` 是cell中的位数。这个描述符代表cell数据的完整4位组的长度(但如果不为空至少为1)。 + +结果是: + +```json +1[8_] -> 0201 -> 2 refs, length 1 +7[FE] -> 0101 -> 1 ref, length 1 +24[0AAAAA] -> 0006 -> 0 refs, length 6 +``` + +对于不完整的4位组的数据,在序列的末尾添加1位。这意味着它表示组的结束位,并用于确定不完整组的真实大小。让我们添加以下位: + +```json +1[8_] -> C0 -> 0b10000000->0b11000000 +7[FE] -> FF -> 0b11111110->0b11111111 +24[0AAAAA] -> 0AAAAA -> do not change (full groups) +``` + +现在让我们添加引用索引: + +```json +0 1[8_] -> 0201 -> refers to 2 cells with such indexes +1 7[FE] -> 02 -> refers to cells with index 2 +2 24[0AAAAA] -> no refs +``` + +并将其全部组合在一起: + +```json +0201 C0 0201 +0101 FF 02 +0006 0AAAAA +``` + +并通过将相应的字符串连接成一个单一的字节数组来拼接它们: +`0201c002010101ff0200060aaaaa`,大小14字节。 + +
+ 显示示例 + +```golang +func (c *Cell) descriptors() []byte { + ceilBytes := c.bitsSz / 8 + if c.bitsSz%8 ! = 0 { + ceilBytes++ + } + + // calc size + ln := ceilBytes + c.bitsSz / 8 + + specBit := byte(0) + if c.special { + specBit = 8 + } + + return []byte{byte(len(c.refs)) + specBit + c.level*32, byte(ln)} +} +``` + +[来源](https://github.com/xssnick/tonutils-go/blob/3d9ee052689376061bf7e4a22037ff131183afad/tvm/cell/serialize.go#L205) + +
+ +### 打包cell包 + +让我们打包上一节直接提到的cell。我们已经将其序列化为一个扁平的14字节数组。 + +因此,我们根据其[架构](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/tl/boc.tlb#L25)构建header。 + +``` +b5ee9c72 -> id tl-b of the BoC structure +01 -> flags and size:(## 3), in our case the flags are all 0, + and the number of bytes needed to store the number of cells is 1. + we get - 0b0_0_0_00_001 +01 -> number of bytes to store the size of the serialized cells +03 -> number of cells, 1 byte (defined by 3 bits size:(## 3), equal to 3. +01 -> number of root cells - 1 +00 -> absent, always 0 (in current implementations) +0e -> size of serialized cells, 1 byte (size defined above), equal to 14 +00 -> root cell index, size 1 (determined by 3 size:(## 3) bits from header), + always 0 +0201c002010101ff0200060aaaaa -> serialized cells +``` + +接下来,我们将上述所有内容连接成一个字节数组,得到我们最终的BoC: +`b5ee9c7201010301000e000201c002010101ff0200060aaaaa` + +cell包实现示例:[序列化](https://github.com/xssnick/tonutils-go/blob/master/tvm/cell/serialize.go),[反序列化](https://github.com/xssnick/tonutils-go/blob/master/tvm/cell/parse.go) + +## 特殊cell + +通常,TON上运行的cell分为两大类:普通cell和特殊cell。用户处理的大多数cell是普通cell,负责携带信息。 + +然而,为了实现网络的内部功能,有时需要特殊cell,并且它们用于多种目的,这取决于它们的子类型。 + +## cell层级 + +每个cell都有一个称为`Level`的属性,它由0到3的整数表示。 + +### 普通cell层级 + +普通cell的层级始终等于其所有引用的层级的最大值: + +```cpp +Lvl(c) = max(Lvl(r_0), ..., Lvl(r_i), ..., Lvl(r_e)) +``` + +其中`i`是`c`的引用索引,`e`是`c`的引用数量。 + +_没有引用的普通cell层级为零_ + +### 特殊cell层级 + +特殊cell对其层级的设置有不同的规则,这些规则在[此文](/develop/data-formats/exotic-cells)中有描述。 + +## cell哈希 + +在大多数情况下,用户使用的是层级为0的普通cell,它们只有一个哈希,称为 representation hash(或 hash infinity)。 + +层级为`Lvl(c) = l`的cell`c`,其中`1 ≤ l ≤ 3`,除了 representation hash 外,还有`l`个**"更高"**的哈希。 + +### 标准cell representation hash 计算 + +首先,我们需要计算cell表示(类似于上面描述的cell序列化) + +1. 计算描述符字节 +2. 添加序列化cell数据 +3. 对于每个cell的引用,添加其深度 +4. 对于每个cell的引用,添加其 representation hash +5. 计算结果的SHA256哈希 + +让我们分析以下示例: + +#### 无引用的cell + +```json +32[0000000F] +``` + +1. 描述符计算 + +引用描述符等于 `r+8s+32l = 0 + 0 + 0 = 0 = 00` + +位描述符等于 `floor(b / 8) + ceil (b / 8) = 8 = 08` + +连接这些字节,我们得到 `0008` + +2. cell数据序列化 + +在这种情况下,我们有完整的4位组,所以我们不必向cell数据添加任何位。结果是 `0000000f` + +3. 引用深度 + +我们跳过这部分,因为我们的cell没有任何引用 + +4. 引用哈希 + +我们跳过这部分,因为我们的cell没有任何引用 + +5. SHA256计算 + +连接前面步骤的字节,我们得到 `00080000000f`,其SHA256为 `57b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f9` - 这就是cell representation hash 。 + +#### 带有引用的cell + +```json +24[00000B] -> { + 32[0000000F], + 32[0000000F] +} +``` + +1. 描述符计算 + +引用描述符等于 `r+8s+32l = 2 + 0 + 0 = 0 = 02` + +位描述符等于 `floor(b / 8) + ceil (b / 8) = 6 = 06` + +连接这些字节,我们得到 `0206` + +2. cell数据序列化 + +在这种情况下,我们有完整的4位组,所以我们不必向cell数据添加任何位。结果是 `00000b` + +3. 引用深度 + +深度由2个字节表示。我们的cell有2个引用,每个的深度都是零,所以这一步的结果是 `00000000`。 + +4. 引用哈希 + +对于每个引用,我们添加其哈希(我们上面计算过),所以结果是 \`57b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f957b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f9 + +5. SHA256计算 + +连接前面步骤的字节,我们得到 `020600000b0000000057b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f957b520dbcb9d135863fc33963cde9f6db2ded1430d88056810a2c9434a3860f9` +,其SHA256为 `f345277cc6cfa747f001367e1e873dcfa8a936b8492431248b7a3eeafa8030e7` - 这就是cell representation hash + +### 更高哈希的计算 + +普通cell`c`的更高哈希与其 representation hash 的计算类似,但使用其引用的更高哈希而不是它们的 representation hash。 + +特殊cell有其自己的计算更高哈希的规则,这些规则在[此文](/develop/data-formats/exotic-cells)中有描述。 + +## 参阅 + +[//]: # "* [原文RU](https://github.com/xssnick/ton-deep-doc/blob/master/Cells-BoC.md)" + +- [特殊cell](/develop/data-formats/exotic-cells) +- [默克尔证明验证](/develop/data-formats/proofs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/crc32.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/crc32.md new file mode 100644 index 0000000000..79243dc01c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/crc32.md @@ -0,0 +1,60 @@ +# CRC32 + +## 概述 + +CRC代表循环冗余检查,这是一种常用的方法,用于验证数字数据的完整性。它是一种用于检测在数据传输或存储过程中是否发生错误的算法。CRC生成一个数据的简短校验和或哈希,附加在数据上。当数据被接收或检索时,重新计算CRC并与原始校验和比较。如果两个校验和匹配,则假定数据未被损坏。如果它们不匹配,则表明发生了错误,需要重新发送或再次检索数据。 + +CRC32 IEEE版本用于TL-B方案。通过查看此[NFT操作码](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#tl-b-schema)示例,可以更清楚地理解各种消息的TL-B计算。 + +## 工具 + +### 在线计算器 + +- [在线计算器示例](https://emn178.github.io/online-tools/crc32.html) +- [Tonwhales Introspection ID 生成器](https://tonwhales.com/tools/introspection-id) + +### VS Code扩展 + +- [crc32-opcode-helper](https://marketplace.visualstudio.com/items?itemName=Gusarich.crc32-opcode-helper) + +### Python + +```python +import zlib +print(zlib.crc32(b'') & 0x7FFFFFFF) +``` + +### Go + +```python +func main() { + + var schema = "some" + + schema = strings.ReplaceAll(schema, "(", "") + schema = strings.ReplaceAll(schema, ")", "") + data := []byte(schema) + var crc = crc32.Checksum(data, crc32.MakeTable(crc32.IEEE)) + + var b_data = make([]byte, 4) + binary.BigEndian.PutUint32(b_data, crc) + var res = hex.EncodeToString(b_data) + fmt.Println(res) +} +``` + +### TypeScript + +```typescript +import * as crc32 from 'crc-32'; + +function calculateRequestOpcode_1(str: string): string { + return (BigInt(crc32.str(str)) & BigInt(0x7fffffff)).toString(16); +} + +function calculateResponseOpcode_2(str: string): string { + const a = BigInt(crc32.str(str)); + const b = BigInt(0x80000000); + return ((a | b) < 0 ? (a | b) + BigInt('4294967296') : a | b).toString(16); +} +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/exotic-cells.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/exotic-cells.md new file mode 100644 index 0000000000..554aead651 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/exotic-cells.md @@ -0,0 +1,137 @@ +# 特殊cell + +每个cell都有其自己的类型,由一个从 -1 到 255 的整数编码。 +类型为 -1 的cell是`普通`cell,所有其他类型的cell称为`异构`或`特殊`cell。 +特殊cell的类型存储在其数据的前八位中。如果特殊cell的数据位数少于八位,那么它是无效的。 +目前,有 4 种特殊cell类型: + +```json +{ + Prunned Branch: 1, + Library Reference: 2, + Merkle Proof: 3, + Merkle Update: 4 +} +``` + +### 裁剪分支 + +裁剪分支是代表已删除cell子树的cell。 + +它们可以有 `1 <= l <= 3` 的级别,并且包含恰好 `8 + 8 + 256 * l + 16 * l` 位。 + +第一个字节始终是 `01` - cell类型。第二个字节是裁剪分支级别掩码。然后是 `l * 32` 字节的已删除子树的哈希,之后是 `l * 2` 字节的已删除子树的深度。 + +裁剪分支cell的级别 `l` 可能被称为其德布鲁因指数(De Bruijn index),因为它决定了裁剪分支是在构造哪个外部默克尔证明或默克尔更新时被修剪的。 + +裁剪分支的更高哈希存储在其数据中,可以这样获得: + +```cpp +Hash_i = CellData[2 + (i * 32) : 2 + ((i + 1) * 32)] +``` + +### 库引用 + +库引用cell用于在智能合约中使用库。 + +它们始终具有 0 级,并包含 `8 + 256` 位。 + +第一个字节始终是 `02` - cell类型。接下来的 32 字节是被引用的库cell的[ representation hash ](/develop/data-formats/cell-boc#standard-cell-representation-hash-calculation)。 + +### 默克尔证明 + +默克尔证明cell用于验证cell树数据的一部分属于完整树。这种设计允许验证者不存储树的全部内容,同时仍能通过根哈希验证内容。 + +默克尔证明恰好有一个引用,其级别 `0 <= l <= 3` 必须是 `max(Lvl(ref) - 1, 0)`。这些cell恰好包含 `8 + 256 + 16 = 280` 位。 + +第一个字节始终是 `03` - cell类型。接下来的 32 字节是 `Hash_1(ref)`(如果引用级别为 0,则为 `ReprHash(ref)`)。接下来的 2 字节是被引用替换的已删除子树的深度。 + +默克尔证明cell的更高哈希 `Hash_i` 的计算方式类似于普通cell,但使用 `Hash_i+1(ref)` 代替 `Hash_i(ref)`。 + +### 默克尔更新 + +默克尔更新cell始终有 2 个引用,并且行为类似于两者的默克尔证明。 + +默克尔更新的级别 `0 <= l <= 3` 是 `max(Lvl(ref1) − 1, Lvl(ref2) − 1, 0)`。它们恰好包含 `8 + 256 + 256 + 16 + 16 = 552` 位。 + +第一个字节始终是 `04` - cell类型。接下来的 64 字节是 `Hash_1(ref1)` 和 `Hash_2(ref2)` - 被称为旧哈希和新哈希。然后是 4 字节,表示已删除的旧子树和新子树的实际深度。 + +## 简单证明验证示例 + +假设有一个cell `c`: + +```json +24[000078] -> { + 32[0000000F] -> { + 1[80] -> { + 32[0000000E] + }, + 1[00] -> { + 32[0000000C] + } + }, + 16[000B] -> { + 4[80] -> { + 267[800DEB78CF30DC0C8612C3B3BE0086724D499B25CB2FBBB154C086C8B58417A2F040], + 512[00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064] + } + } +} +``` + +但我们只知道它的哈希 `44efd0fdfffa8f152339a0191de1e1c5901fdcfe13798af443640af99616b977`,我们想证明cell `a` `267[800DEB78CF30DC0C8612C3B3BE0086724D499B25CB2FBBB154C086C8B58417A2F040]` 实际上是 `c` 的一部分,而不接收整个 `c`。因此,我们要求提供者创建一个默克尔证明,将我们不感兴趣的所有分支替换为裁剪分支cell。 + +从 `c` 中无法到达 `a` 的第一个后代是 `ref1`: + +```json +32[0000000F] -> { + 1[80] -> { + 32[0000000E] + }, + 1[00] -> { + 32[0000000C] + } +} +``` + +因此,提供者计算其哈希(`ec7c1379618703592804d3a33f7e120cebe946fa78a6775f6ee2e28d80ddb7dc`),创建一个裁剪分支 `288[0101EC7C1379618703592804D3A33F7E120CEBE946FA78A6775F6EE2E28D80DDB7DC0002]` 并用此裁剪分支替换 `ref1`。 + +第二个是 `512[0000000...00000000064]`,因此提供者也为此cell创建一个裁剪分支: + +```json +24[000078] -> { + 288[0101EC7C1379618703592804D3A33F7E120CEBE946FA78A6775F6EE2E28D80DDB7DC0002], + 16[000B] -> { + 4[80] -> { + 267[800DEB78CF30DC0C8612C3B3BE0086724D499B25CB2FBBB154C086C8B58417A2F040], + 288[0101A458B8C0DC516A9B137D99B701BB60FE25F41F5ACFF2A54A2CA4936688880E640000] + } + } +} +``` + +提供者发送给验证者(在此示例中是我们)的结果默克尔证明看起来是这样的: + +```json +280[0344EFD0FDFFFA8F152339A0191DE1E1C5901FDCFE13798AF443640AF99616B9770003] -> { + 24[000078] -> { + 288[0101EC7C1379618703592804D3A33F7E120CEBE946FA78A6775F6EE2E28D80DDB7DC0002], + 16[000B] -> { + 4[80] -> { + 267[800DEB78CF30DC0C8612C3B3BE0086724D499B25CB2FBBB154C086C8B58417A2F040], + 288[0101A458B8C0DC516A9B137D99B701BB60FE25F41F5ACFF2A54A2CA4936688880E640000] + } + } + } +} +``` + +当我们(验证者)得到证明cell时,我们确保其数据包含 `c` 的哈希,然后从唯一的证明引用计算 `Hash_1`:`44efd0fdfffa8f152339a0191de1e1c5901fdcfe13798af443640af99616b977`,并将其与 `c` 的哈希进行比较。 + +现在,当我们检查哈希是否匹配后,我们需要深入cell并验证是否存在我们感兴趣的cell `a`。 + +这种证明反复减少了计算负载和需要发送给或存储在验证者中的数据量。 + +## 参阅 + +- [高级证明验证示例](/develop/data-formats/proofs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/msg-tlb.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/msg-tlb.mdx new file mode 100644 index 0000000000..79befbec1a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/msg-tlb.mdx @@ -0,0 +1,371 @@ +import ThemedImage from '@theme/ThemedImage'; + +# 消息 TL-B 方案 + +本节详细解释消息的 TL-B 方案。 + +## 消息 TL-B +### TL-B +主要消息 TL-B 方案声明为几个嵌套结构的组合 + +```tlb +message$_ {X:Type} info:CommonMsgInfo +init:(Maybe (Either StateInit ^StateInit)) +body:(Either X ^X) = Message X; + +message$_ {X:Type} info:CommonMsgInfoRelaxed +init:(Maybe (Either StateInit ^StateInit)) +body:(Either X ^X) = MessageRelaxed X; + +_ (Message Any) = MessageAny; +``` + +这里 `Message X` - 是常见消息结构,`MessageRelaxed X` 是带有 CommonMsgInfoRelaxed 体的附加类型,而 `Message Any` 是两者的并集。消息结构与 X:Type 统一,换句话说就是一个 Cell。根据 TL-B,如果数据能够适应 1023 位,我们可以将所有数据组合在一个cell中,或者使用带有插入符号 `^` 的引用。 + +序列化的 `Message X` 通过 FunC 方法 send_raw_message() 放置到动作列表中,然后智能合约执行此动作并发送消息。 + +### 显式序列化的定义 +根据 TL-B 结构构建有效的二进制数据,我们应该进行序列化,这对每种类型都是递归定义的。这意味着,要序列化 Message X,我们需要知道如何序列化 `StateInit`、`CommonMsgInfo` 等。 + +我们应该根据递归链接从另一个 TL-B 方案中获取每个嵌套结构,直到顶层结构的序列化是显式的 - 每个位由布尔或类似位的类型(比特,uint,varuint)定义。 + +目前在常规开发中不使用的结构将在 Type 列中标记为 `*`,例如 *Anycast 通常在序列化中被跳过。 + +### message$_ + +这是整个消息 `Message X` 的顶层 TL-B 方案: + +```tlb +message$_ {X:Type} info:CommonMsgInfo +init:(Maybe (Either StateInit ^StateInit)) +body:(Either X ^X) = Message X; +``` + +| 结构 | 类型 | 必需 | 描述 | +|-----------|------------------------------------|----------|----------------------------------------------------------------------------------------------------------------| +| message$_ | 构造函数 | | 按照构造函数规则定义。空标记 `$_` 表示我们不会在开头添加任何位 | +| info | [CommonMsgInfo](#commonmsginfo) | 必需 | 详细的消息属性定义目的地及其值。始终放置在消息的根cell中。 | +| init | [StateInit](#stateinit-tl-b) | 可选 | 通用结构,用于 TON 中初始化新合约。可以写在cell引用或根cell中。 | +| body | X | 必需 | 消息有效载荷。可以写在cell引用或根cell中。 | + + +```tlb +nothing$0 {X:Type} = Maybe X; +just$1 {X:Type} value:X = Maybe X; +left$0 {X:Type} {Y:Type} value:X = Either X Y; +right$1 {X:Type} {Y:Type} value:Y = Either X Y; +``` + +回想一下 `Maybe` 和 `Either` 的工作方式,我们可以序列化不同的情况: + +* `[CommonMsgInfo][10][StateInit][0][X]` - `Message X` 在一个cell中 + + +

+ +

+ +* `[CommonMsgInfo][11][^StateInit][1][^X]` - `Message X` 带引用 + + +

+ +

+ + +## CommonMsgInfo TL-B + +### CommonMsgInfo + +`CommonMsgInfo` 是一系列参数的列表,定义了消息在 TON 区块链中的传递方式。 + +```tlb +//内部消息 +int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddressInt dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfo; + +//外部传入消息 +ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt + import_fee:Grams = CommonMsgInfo; + +//外部传出消息 +ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfo; +``` + +### int_msg_info$0 + +`int_msg_info` 是内部消息的一种情况。这意味着它们可以在合约之间发送,且只能在合约之间发送。 +用例 - 普通的跨合约消息。 + + +```tlb +nanograms$_ amount:(VarUInteger 16) = Grams; +//内部消息 +int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddressInt dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfo; +``` + +| 结构 | 类型 | 必需 | 描述 | +|----------------|------------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------| +| int_msg_info$0 | 构造函数 | 必需 | $0 标记意味着序列化 CommonMsgInfo 以 0 位开始描述内部消息。 | +| ihr_disabled | 布尔 | 必需 | 超立方体路由标志位。 | +| bounce | 布尔 | 必需 | 如果处理过程中出现错误,消息应该被弹回。如果消息的 flat bounce = 1,它被称为可弹回。 | +| bounced | 布尔 | 必需 | 描述消息本身是弹回结果的标志位。 | +| src | [MsgAddressInt](#msgaddressint-tl-b) | 必需 | 消息发送者智能合约的地址。 | +| dest | [MsgAddressInt](#msgaddressint-tl-b) | 必需 | 消息目的地智能合约的地址。 | +| value | [CurrencyCollection](#currencycollection)| 必需 | 描述货币信息的结构,包括消息中转移的总资金。 | +| ihr_fee | [VarUInteger 16](#varuinteger-n) | 必需 | 超路由交付费用 | +| fwd_fee | [VarUInteger 16](#varuinteger-n) | 必需 | 验证者指定的转发消息费用 | +| created_lt | uint64 | 必需 | 验证者指定的发送消息的逻辑时间。用于对智能合约中的动作进行排序。 | +| created_at | uint32 | 必需 | Unix 时间 | + + + +### ext_in_msg_info$10 + +`ext_in_msg_info$10` 是外部传入消息的一种情况。这意味着这种类型的消息是从合约发送到链下空间的。\ +用例 - 钱包应用请求钱包合约。 + + +```tlb +nanograms$_ amount:(VarUInteger 16) = Grams; +//外部传入消息 +ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt + import_fee:Grams = CommonMsgInfo; +``` + +| 结构 | 类型 | 必需 | 描述 | + + +|--------------------|--------------------------------------|----------|------------------------------------------------------------------------------------------------------------------------| +| ext_out_msg_info$10| 构造函数 | 必需 | `$10` 标记意味着序列化 CommonMsgInfo 以 `10` 位开始描述外部传入消息。 | +| ihr_disabled | 布尔 | 必需 | 超路由标志位。(目前始终为真) | +| src | [MsgAddressExt](#msgaddressext-tl-b) | 必需 | 消息的外部发送者地址。 | +| dest | [MsgAddressInt](#msgaddressint-tl-b) | 必需 | 消息目的地智能合约的地址。 | +| import_fee | [VarUInteger 16](#varuinteger-n) | 必需 | 执行和传递消息的费用。 | + +### ext_out_msg_info$11 + +`ext_out_msg_info$11` 是外部传出消息的一种情况。这意味着它们可以从合约发送到链外空间。 +用例 - 日志。 + +```tlb +//内部消息 +ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfo; +``` + +| 结构 | 类型 | 必需 | 描述 | +|---------------------|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------| +| ext_out_msg_info$11 | 构造函数 | 必需 | `$11` 标记意味着序列化 CommonMsgInfo 以 `11` 位开始描述外部传出消息。 | +| src | [MsgAddressInt](#msgaddressint-tl-b) | 必需 | 超路由标志位。 | +| dest | [MsgAddressExt](#msgaddressext-tl-b) | 必需 | 用于 TON 中初始化新合约的通用结构。可以写在cell引用或根cell中。 | +| created_lt | uint64 | 必需 | 验证者指定的发送消息的逻辑时间。用于对智能合约中的动作进行排序。 | +| created_at | uint32 | 必需 | Unix 时间 | + + +## StateInit TL-B + +StateInit 用于向合约传递初始数据,并在合约部署中使用。 + +```tlb +_ split_depth:(Maybe (## 5)) special:(Maybe TickTock) + code:(Maybe ^Cell) data:(Maybe ^Cell) + library:(HashmapE 256 SimpleLib) = StateInit; +``` + + +| 结构 | 类型 | 必需 | 描述 | +|-------------|-------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------| +| split_depth | (## 5) | 可选 | 用于高负载合约的参数,定义了在不同分片中分裂为多个实例的行为。目前 StateInit 没有使用它。 | +| special | TickTock* | 可选 | 用于在区块链的每个新区块中调用智能合约。仅在主链中可用。普通用户合约没有使用它。 | +| code | Cell | 可选 | 合约的序列化代码。 | +| data | Cell | 可选 | 合约初始数据。 | +| library | HashmapE 256 SimpleLib* | 可选 | 目前使用的 StateInit 没有库 | + +[哈希映射的一般详细解释](../data-formats/tl-b#hashmap) + + + +## MsgAddressExt TL-B + + +```tlb +addr_none$00 = MsgAddressExt; +addr_extern$01 len:(## 9) external_address:(bits len) += MsgAddressExt; +``` + +`MsgAddress` 是地址的各种序列化方案。根据消息发送者(链下参与者或智能合约)的不同,使用不同的结构。 + +### addr_none$00 + +`addr_none$00` - 用于定义链下参与者的空地址。这意味着我们可以向合约发送外部消息,而不需要唯一的发件人地址。 + +```tlb +addr_none$00 = MsgAddressExt; +``` + +| 结构 | 类型 | 必需 | 描述 | +|----------------|--------------|----------|----------------------------------------------------------------------------------------------------------------| +| addr_none$00 | 构造函数 | 必需 | `$00` 标记意味着序列化 MsgAddressExt 以 `00` 位开始。这意味着整个外部地址是 `00`。 | + + +### addr_extern$01 + +```tlb +addr_extern$01 len:(## 9) external_address:(bits len) += MsgAddressExt; +``` + + +| 结构 | 类型 | 必需 | 描述 | +|-------------------|------------|----------|--------------------------------------------------------------------------------------------------------------| +| addr_extern$01 + + | 构造函数 | 必需 | `$01` 标记意味着序列化 MsgAddressExt 以 `01` 位开始描述外部地址。 | +| len | ## 9 | 必需 | 与 uintN 相同 - 表示无符号 N 位数字 | +| external_address | (bits len) | 必需 | 地址是长度等于前面的 `len` 的位串 | + + +## MsgAddressInt TL-B + +```tlb +addr_std$10 anycast:(Maybe Anycast) +workchain_id:int8 address:bits256 = MsgAddressInt; + +addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) +workchain_id:int32 address:(bits addr_len) = MsgAddressInt; +``` + +### addr_std$10 + +```tlb +addr_std$10 anycast:(Maybe Anycast) +workchain_id:int8 address:bits256 = MsgAddressInt; +``` + + +| 结构 | 类型 | 必需 | 描述 | +|-------------|------------|----------|--------------------------------------------------------------------------------------------------------------| +| addr_std$10 | 构造函数 | 必需 | `$10` 标记意味着序列化 MsgAddressExt 以 `10` 位开始描述外部地址。 | +| anycast | Anycast* | 可选 | 额外的地址数据,目前普通内部消息中未使用 | +| workchain_id| int8 | 必需 | 目的地地址的智能合约所在的工作链。目前始终为零。 | +| address | (bits256) | 必需 | 智能合约账户 ID 号 | + + + +### addr_var$11 + +```tlb +addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) +workchain_id:int32 address:(bits addr_len) = MsgAddressInt; +``` + + + +| 结构 | 类型 | 必需 | 描述 | +|-------------|------------|----------|-----------------------------------------------------------------------------------------------------------------------| +| addr_var$11 | 构造函数 | 必需 | `$11` 标记意味着序列化 MsgAddressInt 以 `11` 位开始描述内部合约地址。 | +| anycast | Anycast* | 可选 | 额外的地址数据,目前普通内部消息中未使用 | +| addr_len | ## 9 | 必需 | 与 uintN 相同 - 表示无符号 N 位数字 | +| workchain_id| int32 | 必需 | 目的地地址的智能合约所在的工作链。目前始终为零。 | +| address | (bits256) | 必需 | 有效载荷地址(可以是账户 ID) | + + +## 基本使用类型 + + +### CurrencyCollection + +```tlb +nanograms$_ amount:(VarUInteger 16) = Grams; +currencies$_ grams:Grams other:ExtraCurrencyCollection += CurrencyCollection; +``` + + + +| 结构 | 类型 | 必需 | 描述 | +|-------------|-------------------------|----------|--------------------------------------------------------------------------------------------------------| +| currencies$_| 构造函数 | 必需 | `$_` 空标记意味着序列化 CurrencyCollection 时我们不会在开头添加任何位 | +| grams | (VarUInteger 16) | 必需 | 以nanoTons表示的消息价值 | +| other | ExtraCurrencyCollection | 可选 | ExtraCurrencyCollection 是一个为附加货币设计的字典,通常为空 | + +* ExtraCurrencyCollection 是一种复杂类型,通常在消息中为空字典 + + +### VarUInteger n + +```tlb +var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) += VarUInteger n; +var_int$_ {n:#} len:(#< n) value:(int (len * 8)) += VarInteger n; +``` + + + +| 结构 | 类型 | 必需 | 描述 | +|------------|------------------|----------|----------------------------------------------------------------------------------------------------------| +| var_uint$_ | 构造函数 | 必需 | `var_uint$_` 空标 + +记意味着序列化 CurrencyCollection 时我们不会在开头添加任何位 | +| len | uintN | 必需 | 下一个值的位长度参数 | +| value | (uint (len * 8)) | 可选 | 以 (len * 8) 位写入的整数值 | + + +## 消息示例 + +### 常规 func 内部消息 + +```func + var msg = begin_cell() + .store_uint(0, 1) ;; 标记 + .store_uint(1, 1) ;; ihr_disabled + .store_uint(1, 1) ;; 允许弹回 + .store_uint(0, 1) ;; 本身不是弹回 + .store_slice(source) + .store_slice(destination) + ;; 序列化 CurrencyCollection(见下文) + .store_coins(amount) + .store_dict(extra_currencies) + .store_coins(0) ;; ihr_fee + .store_coins(fwd_value) ;; fwd_fee + .store_uint(cur_lt(), 64) ;; 交易的 lt + .store_uint(now(), 32) ;; 交易的 unixtime + .store_uint(0, 1) ;; 没有 init-field 标志位(Maybe) + .store_uint(0, 1) ;; 原位消息体标志位(Either) + .store_slice(msg_body) + .end_cell(); +``` +### 常规 func 消息简短形式 + +验证者总是覆盖的消息部分可以跳过(填充零位)。此处的消息发送者也被跳过,序列化为 `addr_none$00`。 + +```func + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_slice(message_body) +.end_cell(); +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/proofs.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/proofs.mdx new file mode 100644 index 0000000000..4fbe256f09 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/proofs.mdx @@ -0,0 +1,908 @@ +import ThemedImage from '@theme/ThemedImage'; + +# 证明验证(低层级) + +## 概述 +:::caution +本节描述了与TON进行低层级交互的指令和手册。假设您已经熟悉了[特殊cell](/develop/data-formats/exotic-cells)、[TL-B 语言](/develop/data-formats/tl-b-language)并 +理解了[简单证明验证](/develop/data-formats/exotic-cells#simple-proof-verifying-example)示例。 +::: + +本文描述了从Liteservers验证证明的高级示例。 + +从节点接收任何数据时,核实数据的真实性对于与区块链进行无信任交互非常重要。然而,文章仅涵盖了与Liteserver无信任通信的一部分,因为它假设您已经验证了从Liteserver(或其他任何人)收到的区块哈希。区块哈希验证更为高级,因为您需要同步关键区块和(或)检查区块签名, +将来会在另一篇文章中描述。但无论如何,即使只使用这些示例,您也降低了Liteserver发送错误数据而您相信的可能性。 + +## 区块头 + +假设我们知道一个区块ID: +```json + +``` +我们向Liteserver请求此区块的头部。Liteserver的[响应](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tl/generate/scheme/lite_api.tl#L35)包含一个`header_proof` boc。 + +
+ + 显示boc + +```boc + +b5ee9c72010207010001470009460351ed3b9e728e7c548b15a5e5ce988b4a74984c3f8374f3f1a52c7b1f46c26406001601241011ef55aaffffff110204050601a09bc7a98700000000040101dc65010000000100ffffffff000000000000000064b6c356000023d38ba64000000023d38ba64004886d00960007028101dc64fd01dc42bec400000003000000000000002e030098000023d38b96fdc401dc650048a3971c46472b85c8d761060a6e7ae9f13a90cdda815915a89597cfecb393a6b568807adfb3c1c5efc920907225175db61ca384e4f8b313799e3cbb8b7b4085284801018c6053c1185700c0fe4311d5cf8fa533ea0382e361a7b76d0cf299b75ac0356c000328480101741100d622b0d5264bcdb86a14e36fc8c349b82ae49e037002eb07079ead8b060015284801015720b6aefcbf406209522895faa6c0d10cc3315d90bcaf09791b19f595e86f8f0007 + +``` +
+ +解序列化boc后,我们得到Cell: + +```json +280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> { + 64[11EF55AAFFFFFF11] -> { + 640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> { + 608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085] + }, + 288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003], + 288[0101741100D622B0D5264BCDB86A14E36FC8C349B82AE49E037002EB07079EAD8B060015], + 288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007] + } +} +``` +我们应根据区块[Tlb 方案](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L442)进行反序列化: + +```python +{ + 'global_id': -239, + 'info': + { + 'version': 0, + 'not_master': 0, + 'after_merge': 0, + 'before_split': 0, + 'after_split': 0, + 'want_split': False, + 'want_merge': True, + 'key_block': False, + 'vert_seqno_incr': 0, + 'flags': 1, + 'seqno': 31220993, + 'vert_seqno': 1, + 'shard': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0}, + 'gen_utime': 1689699158, + 'start_lt': 39391488000000, + 'end_lt': 39391488000004, + 'gen_validator_list_hash_short': 2288844950, + 'gen_catchain_seqno': 459393, + 'min_ref_mc_seqno': 31220989, + 'prev_key_block_seqno': 31212222, + 'gen_software': {'version': 3, 'capabilities': 46}, + 'master_ref': None, + 'prev_ref': {'type_': 'prev_blk_info', 'prev': {'end_lt': 39391487000004, 'seqno': 31220992, 'root_hash': b'H\xa3\x97\x1cFG+\x85\xc8\xd7a\x06\nnz\xe9\xf1:\x90\xcd\xda\x81Y\x15\xa8\x95\x97\xcf\xec\xb3\x93\xa6', 'file_hash': b'\xb5h\x80z\xdf\xb3\xc1\xc5\xef\xc9 \x90r%\x17]\xb6\x1c\xa3\x84\xe4\xf8\xb3\x13y\x9e<\xbb\x8b{@\x85'}}, + 'prev_vert_ref': None + }, + 'value_flow': None, + 'state_update': None, + 'extra': None +} +``` + +现在,我们应该检查反序列化区块中的`seqno`是否与我们所知的区块`seqno`匹配,然后计算唯一的Merkle Proof引用的hash_1,并将其与我们所知的区块哈希进行比较: +```python +assert h_proof.refs[0].get_hash(0) == block_id.root_hash +``` +现在,我们可以信任该Cell包含的所有其他数据 + +_检查证明示例:_ [Python](https://github.com/yungwine/pytoniq-core/blob/873a96aa2256db33b8f35fbe2ab8fe8cf8ae49c7/pytoniq_core/proof/check_proof.py#L19), [Kotlin](https://github.com/andreypfau/ton-kotlin/blob/b1edc4b134e89ccf252149f27c85fd530377cebe/ton-kotlin-liteclient/src/commonMain/kotlin/CheckProofUtils.kt#L15), [C++](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/check-proof.cpp#L34) + +## 完整区块 + +对于`liteserver.getBlock`方法,证明验证与上述相同,但它包含完整的Cells,而不是裁剪过的分支,用于[值流](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L464)、[状态更新](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L412-L413)和[区块额外信息](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L452)的方案。 + +## 分片区块 + +分片证明是指分片引用实际存储在我们提供给Liteserver的主链区块中的证明。当我们调用`liteServer.getShardInfo`、`liteServer.getAccountState`和`liteServer.runSmcMethod`方法时,我们需要检查这些证明。 + +让我们向Liteserver请求上面提到的主链区块的分片信息: +```python +await client.raw_get_shard_info(master, wc=0) +``` + +Liteserver响应包含了分片区块的BlockIdExt: +```json + +``` +分片证明boc: + +
+ + 显示boc + +```boc + +b5ee9c72010219020004b9010009460332bf3592969931ca4fbc7715494b50597f1884c0d847456029d8cf0e526e6046016f0209460351ed3b9e728e7c548b15a5e5ce988b4a74984c3f8374f3f1a52c7b1f46c26406001611245b9023afe2ffffff1100ffffffff000000000000000001dc65010000000164b6c356000023d38ba6400401dc64fd600304050628480101affe84cdd73951bce07eeaad120d00400295220d6f66f1163b5fa8668202d72b000128480101faed0dd3ca110ada3d22980e3795d2bdf15450e9159892bbf330cdfd13a3b880016e22330000000000000000ffffffffffffffff820ce9d9c3929379c82807082455cc26aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac23519b11eddc69b7e090a0b0c28480101a5a7d24057d8643b2527709d986cda3846adcb3eddc32d28ec21f69e17dbaaef000128480101deab5a5aaf79c5e24f8dcbbe51747d6804104f75f58ed5bed4702c353545c6ac00110103d0400d284801015394592e3a3f1e3bc2d4249e993d0ec1e33ca18f49533991274ebc65276cd9a5001122bf0001aaa0161d000702816000047a7172dfb88800011e8b625908200ee215f71061846393a08c682e87bc3a12aff2d246eb97a09164f5657f96f9a252ef71580fe5309a823f73f3c4c3f8ab73f5a85bbf204bfd22e68d36d0efab1818e7b428be0f1028480101b20e36a3b36a4cdee601106c642e90718b0a58daf200753dbb3189f956b494b6000101db50119963380ee3280800011e9c5cb7ee0000011e9c5cb7ee29cf2e5e52dfb4ba85aecc4bc3961d06bd1f30a7290e29f26f3947d7f69c6e713f8f872e6e25c50967921c6e55b07a38968ee0279bc958eb97928065fb204a45b88000381abc00000000000000000ee327eb25b61a8a0e001343c9b67a721dcd6500202848010150fcc05bd9723571b83316a5f650be31edb131d05fdc78d271486e5d4ef077e1001928480101e5be728200b172cf7e2356cba2ae1c6e2c790be7c03cd7814c6e6fe3080b944b0011241011ef55aaffffff111213141501a09bc7a98700000000040101dc65010000000100ffffffff000000000000000064b6c356000023d38ba64000000023d38ba64004886d00960007028101dc64fd01dc42bec400000003000000000000002e16284801018c6053c1185700c0fe4311d5cf8fa533ea0382e361a7b76d0cf299b75ac0356c00032a8a0478e0f0e601ba1161ecc1395e9a0475c4f80aadbd6c483f210e96e29cf36789e432bf3592969931ca4fbc7715494b50597f1884c0d847456029d8cf0e526e6046016f016f1718284801015720b6aefcbf406209522895faa6c0d10cc3315d90bcaf09791b19f595e86f8f00070098000023d38b96fdc401dc650048a3971c46472b85c8d761060a6e7ae9f13a90cdda815915a89597cfecb393a6b568807adfb3c1c5efc920907225175db61ca384e4f8b313799e3cbb8b7b4085688c010378e0f0e601ba1161ecc1395e9a0475c4f80aadbd6c483f210e96e29cf36789e46492304dfb6ef9149781871464af686056a9627f882f60e3b24f8c944a75ebaf016f0014688c010332bf3592969931ca4fbc7715494b50597f1884c0d847456029d8cf0e526e6046da58493ccb5da3876129b0190f3c375e69e59c3ad9ff550be708999dad1f6f39016f0014 + +``` +
+ +以及我们可以使用的`shard_descr` boc,如果我们信任Liteserver。 + +
+ + 显示boc + +```boc + +b5ee9c7201010201007d0001db50119963380ee3280800011e9c5cb7ee0000011e9c5cb7ee29cf2e5e52dfb4ba85aecc4bc3961d06bd1f30a7290e29f26f3947d7f69c6e713f8f872e6e25c50967921c6e55b07a38968ee0279bc958eb97928065fb204a45b88000381abc00000000000000000ee327eb25b61a8a01001343c9b67a721dcd650020 + +``` +
+ +分片证明boc反序列化后我们得到2个根: +```json +[ 1 refs>, 1 refs>] +``` + +第一个是主链区块的Merkle证明,我们应该检查(使用`check_block_header`函数): +```json +280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> { + 64[11EF55AAFFFFFF11] -> { + 640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> { + 608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085] + }, + 288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003], + 552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> { + 560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014], + 560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014] + }, + 288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007] + } +} +``` + +Cell +```json +552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> { + 560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014], + 560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014] +} +``` + + +是[ShardState](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L412-L413) TLB方案的Merkle更新,所以我们需要记住新的哈希。 + +在我们确认唯一的Merkle证明Cell引用的Hash_1与我们所知的区块哈希匹配,并记住了新的ShardState哈希后,我们检查第二个`shard proof` Cell: + +```json +280[0332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F] -> { + 362[9023AFE2FFFFFF1100FFFFFFFF000000000000000001DC65010000000164B6C356000023D38BA6400401DC64FD40] -> { + 288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001], + 288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E], + 204[0000000000000000FFFFFFFFFFFFFFFF820CE9D9C3929379C820] -> { + 288[0101A5A7D24057D8643B2527709D986CDA3846ADCB3EDDC32D28EC21F69E17DBAAEF0001], + 288[0101DEAB5A5AAF79C5E24F8DCBBE51747D6804104F75F58ED5BED4702C353545C6AC0011] + }, + 342[CC26AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC23519B11EDDC69B7C] -> { + 9[D000] -> { + 878[50119963380EE3280800011E9C5CB7EE0000011E9C5CB7EE29CF2E5E52DFB4BA85AECC4BC3961D06BD1F30A7290E29F26F3947D7F69C6E713F8F872E6E25C50967921C6E55B07A38968EE0279BC958EB97928065FB204A45B88000381ABC00000000000000000EE327EB25B61A88] -> { + 74[43C9B67A721DCD650000] + } + }, + 288[01015394592E3A3F1E3BC2D4249E993D0EC1E33CA18F49533991274EBC65276CD9A50011], + 766[0001AAA0161D000702816000047A7172DFB88800011E8B625908200EE215F71061846393A08C682E87BC3A12AFF2D246EB97A09164F5657F96F9A252EF71580FE5309A823F73F3C4C3F8AB73F5A85BBF204BFD22E68D36D0EFAB1818E7B428BC] -> { + 288[010150FCC05BD9723571B83316A5F650BE31EDB131D05FDC78D271486E5D4EF077E10019], + 288[0101E5BE728200B172CF7E2356CBA2AE1C6E2C790BE7C03CD7814C6E6FE3080B944B0011] + }, + 288[0101B20E36A3B36A4CDEE601106C642E90718B0A58DAF200753DBB3189F956B494B60001] + } + } +} +``` + +正如我们所见,唯一的Merkle证明引用前缀为`9023AFE2`,这是[ShardStateUnsplit](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L410)TLB方案的前缀,所以我们需要将这个引用的Hash_1与上一步中记住的进行比较: + +```python +""" +这里mc_block_cell是第一个分片证明根,mc_state_root是第二个。 +check_block_header_proof函数返回ShardState Merkle更新的新哈希。 +""" + +mc_state_hash = mc_state_root[0].get_hash(0) +state_hash = check_block_header_proof(mc_block_cell[0], blk.root_hash, True) + +if mc_state_hash != state_hash: + raise ProofError('mc state hashes mismatch') +``` + +* _为什么?_ - 因为我们已经检查了区块头证明,这意味着我们可以信任其他Cell数据。所以现在我们信任ShardState Merkle更新的新哈希,要信任第二个Cell数据,我们需要检查哈希是否匹配。 + +现在,让我们反序列化第二个Cell: + +```python +{ + 'global_id': -239, + 'shard_id': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0}, + 'seq_no': 31220993, + 'vert_seq_no': 1, + 'gen_utime': 1689699158, + 'gen_lt': 39391488000004, + 'min_ref_mc_seqno': 31220989, + 'out_msg_queue_info': 0 refs>, + 'before_split': 0, + 'accounts': 0 refs>, + 'overload_history': 0, + 'underload_history': 18446744073709551615, + 'total_balance': {'grams': 2364000148715550620, 'other': None}, + 'total_validator_fees': {'grams': 0, 'other': None}, + 'libraries': None, + 'master_ref': None, + 'custom': { + 'shard_hashes': { + 0: {'list': [{ + 'seq_no': 36908135, + 'reg_mc_seqno': 31220993, + 'start_lt': 39391487000000, + 'end_lt': 39391487000005, + 'root_hash': b"9\xe5\xcb\xca[\xf6\x97P\xb5\xd9\x89xr\xc3\xa0\xd7\xa3\xe6\x14\xe5!\xc5>M\xe7(\xfa\xfe\xd3\x8d\xce'", + 'file_hash': b'\xf1\xf0\xe5\xcd\xc4\xb8\xa1,\xf2C\x8d\xca\xb6\x0fG\x12\xd1\xdc\x04\xf3y+\x1dr\xf2P\x0c\xbfd\tH\xb7', + 'before_split': False, + 'before_merge': False, + 'want_split': False, + 'want_merge': True, + 'nx_cc_updated': False, + 'flags': 0, + 'next_catchain_seqno': 459607, + 'next_validator_shard': 9223372036854775808, + 'min_ref_mc_seqno': 31220989, + 'gen_utime': 1689699153, + 'split_merge_at': None, + 'fees_collected': {'grams': 1016817575, 'other': None}, 'funds_created': {'grams': 1000000000, 'other': None} + }] + } + }, + 'config': {'config_addr': '5555555555555555555555555555555555555555555555555555555555555555', 'config': None}, + 'flags': 1, + 'validator_info': {'validator_list_hash_short': 2862618141, 'catchain_seqno': 459393, 'nx_cc_updated': False}, + 'prev_blocks': None, + 'after_key_block': True, + 'last_key_block': {'end_lt': 39382372000004, 'seqno': 31212222, 'root_hash': b'\xe2\x0c0\x8crt\x11\x8d\x05\xd0\xf7\x87BU\xfeZH\xddr\xf4\x12,\x9e\xac\xaf\xf2\xdf4J]\xee+', 'file_hash': b'\x01\xfc\xa6\x13PG\xee~x\x98\x7f\x15n~\xb5\x0bw\xe4\t\x7f\xa4\\\xd1\xa6\xda\x1d\xf5c\x03\x1c\xf6\x85'}, + 'block_create_stats': {'type_': 'block_create_stats', 'counters': None}, + 'global_balance': {'grams': 5089971531496870767, 'other': None} + } +} +``` + +由于我们信任这个Cell,我们可以信任分片区块数据(`ShardStateUnsplit` -> `custom` -> `shard_hashes` -> `0 (shrdblk wc)` -> `leaf`)。 + +_检查证明示例:_ [Python](https://github.com/yungwine/pytoniq/blob/master/pytoniq/proof/check_proof.py#L43), [C++](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/check-proof.cpp#L104) + +## 账户状态 + +让我们证明账户`EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG`在文章开头我们开始时使用的同一个主链区块的状态。 + +Liteserver的[响应](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tl/generate/scheme/lite_api.tl#L37)包含主链区块id(必须与我们发送给ls的相同)、分片区块id、我们应该按照上述描述检查的`shard_proof` boc、`proof` boc和`state` boc。 + +
+ + 显示bocs + +```boc +Proof boc: + b5ee9c7201023d020008480100094603f93fe5eda41a6ce9ecb353fd589842bd3f5d5e73b846cb898525293fc742fd6902190209460339e5cbca5bf69750b5d9897872c3a0d7a3e614e521c53e4de728fafed38dce27001d34235b9023afe2ffffff110000000000000000000000000002332c670000000164b6c351000023d38b96fdc501dc64fd200304052848010138f8d1c6e9f798a477d13aa26cb4d6cfe1a17949ac276b2f1e0ce037a521b9bc0001221382097522af06ffaff1f0063321d90000000000000000ffffffffffffffff825d48abc1bfebfc7bc2df8993c189361000023d38b69370401dc64fd2fa78ec529bcf9931e14f9d8b27ec1469290c0baef8256d657ce573b9679c5997431fcda6bf2d0be39344a9336cfe0ae9c844a88d2bd8022102e4012a760d4db83323130104ba9157837fd7f8f8070833231301032030fdc45f2d3838090a0b284801013e38e2548c5236a9652c45e553ced677f76550097b94138a4576f122443944d400692848010159e1a18ee4e5670306b5203912c87dffc17898f0999bd128a6965027d53b6fa40215231301013fa38088aaea2b780c0d10284801016f315f25b4a39ac12c85fea4ecfe7a83e5e59d1f059783fa0c3ef2797308806100002848010188d5f8a73382aea73dede03fc3bcda2634a717ef50e7428d5a4a44c771b014b90066231301005ecd9e51e5d22a380e0f1023130100303b3b607d7ffc781112132848010182eb0e24c842092ec2705486cbbe98de8016d55f5cff4ea910471a4c3a7a1cf1003b28480101ed7e26bd36efa6d5d9b4f6aaab9813af0742a84244977f74fd4074c9c98908be000028480101ca85960e3fc3dfb6d26e83ae87a837ae5c2faf7c8d43ea177393c602fadaa0300039221100e0f41ada252e2f08141528480101d7acbb602338c86d610f35cfb362fd76fc18b1812476b6fca99a0678e665fcf50000284801014fae109c41f3d5e2be0a3ff00a007f2e50a796797700d18a7aa663e531c37180002d221100e05c33225b78bce8161728480101545b5925b3ab2a8df2470fe22a5a3c9cc64e3cb24407c26167e0fbb476e05309002c221100e03480847f372168181928480101844a14c99695506e920635d18e76d9e685adee74e5fba6f6d3b371ca77e348130029220f00d0b1cce62aecc81a1b220f00c625c7e90dfc681c1d284801019ca2157c92d49b9d051388de45d07072c78a3aa65a5b05547d94e0369aa6bdee002a284801010326812b62712345473070d679bc38cdbbce58b7a2bf6c5c6f091fc8d36e81cd001f220f00c279d628dbf2081e1f220f00c0b8f29f9d04e82021284801019143abf2a72662054eda4f4949d010c897aff4383b514b387cff790408231c6c001a28480101de5072f46a0e0ecab2bbfc2cfc62a3fe200f12d5d457df833a46eb747fa004e30059220f00c03fa2ec9ad848222328480101baee90fd11a130d6d2e2ded21ae4a7b86553116015b7e7ebfc52369534d298b20017220f00c02e722bded7282425220d00ab138e7f18482627284801017f1df311101e472b1d443334d2426fd339539f558694c60e3428221dcb1a5478001628480101e1fc242c29e519f9740ca2570d85779aed0c593cc36b59119852945988e186960015220d00a21324d3ff2828292848010199fe288fdce2606d39f9b6af72f9c2643ef06e6bacc15dd72cfa84d63c9e44a40013220d00a1e877ec8ba82a2b284801019e019e92be76a5ae7aee239299f561682afbe445dc42ee57ccc31ecb427fdf42000e220d00a1db848431a82c2d284801012345b80e66c025fb62c41261b5d230616303ec47f3bb7a255872fada62a1e8bf0010220d00a1d633bc10682e2f220d00a02ca3ddc468303128480101654781e5d466ec4ca50cb2983b20170bb5d90e2e6ab83ed7d42a829651a5eec1000a219abb19e61b8190c2587677c010ce49a93364b965f7762a9810d916b082f45e080a02bc35ebaa649b46ac72e6e4d4c1293b66d58d9ed7a54902beefd97f5bff7977dd85998b3d000023c5643934413228480101edced2278013ea497dd2e286f495b4f7f8df6ea73e08e85414fc43a611c17797000b284801018282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488000828480101b3e9649d10ccb379368e81a3a7e8e49c8eb53f6acc69b0ba2ffa80082f70ee390001241011ef55aaffffff113536373802a09bc7a98700000000840102332c67000000010000000000000000000000000064b6c351000023d38b96fdc0000023d38b96fdc5d41c6e3c0007035701dc64fd01dc42bec400000003000000000000002e393a28480101cb54530ac857df730e82ee239b2150528c6e5f6ed3678eab6e1e789f0e3c7a5300032a8a04f2ad1ede336a68623ddabf36cb8fa405dbe70a38c453f711000f9a9f92592db0f93fe5eda41a6ce9ecb353fd589842bd3f5d5e73b846cb898525293fc742fd69021902193b3c28480101d0cf03a1058c2fd6029288951051a0d82733953c1e9181a67c502ce59b180200000b0098000023d38b69370401dc64fd2fa78ec529bcf9931e14f9d8b27ec1469290c0baef8256d657ce573b9679c5997431fcda6bf2d0be39344a9336cfe0ae9c844a88d2bd8022102e4012a760d4db0098000023d38b87bb8402332c662b4e96320f9d0afb02e5d55b6b42c3349e33540620ecc07b399211fd56e4de3e2555617cdde457cd65a0ad033aafc0c6c25df716b04e455f49179668a46300db688c0103f2ad1ede336a68623ddabf36cb8fa405dbe70a38c453f711000f9a9f92592db04a4ff9713b206e420baaee4dd21febbeb426fcd9ce158db2a56dce9188fc313e0219001b688c0103f93fe5eda41a6ce9ecb353fd589842bd3f5d5e73b846cb898525293fc742fd6987d796744ca386906016c56921370d01f72cb004a1d7c294752afe4446da07bb0219001b +State boc: + b5ee9c720102160100033c000271c006f5bc67986e06430961d9df00433926a4cd92e597ddd8aa6043645ac20bd178222c859043259e0d9000008f1590e4d10d405786bd75534001020114ff00f4a413f4bcf2c80b030051000000e929a9a317c1b3226ce226d6d818bafe82d3633aa0f06a6c677272d1f9b760ff0d0dcf56d8400201200405020148060704f8f28308d71820d31fd31fd31f02f823bbf264ed44d0d31fd31fd3fff404d15143baf2a15151baf2a205f901541064f910f2a3f80024a4c8cb1f5240cb1f5230cbff5210f400c9ed54f80f01d30721c0009f6c519320d74a96d307d402fb00e830e021c001e30021c002e30001c0039130e30d03a4c8cb1f12cb1fcbff1213141502e6d001d0d3032171b0925f04e022d749c120925f04e002d31f218210706c7567bd22821064737472bdb0925f05e003fa403020fa4401c8ca07cbffc9d0ed44d0810140d721f404305c810108f40a6fa131b3925f07e005d33fc8258210706c7567ba923830e30d03821064737472ba925f06e30d08090201200a0b007801fa00f40430f8276f2230500aa121bef2e0508210706c7567831eb17080185004cb0526cf1658fa0219f400cb6917cb1f5260cb3f20c98040fb0006008a5004810108f45930ed44d0810140d720c801cf16f400c9ed540172b08e23821064737472831eb17080185005cb055003cf1623fa0213cb6acb1fcb3fc98040fb00925f03e20201200c0d0059bd242b6f6a2684080a06b90fa0218470d4080847a4937d29910ce6903e9ff9837812801b7810148987159f31840201580e0f0011b8c97ed44d0d70b1f8003db29dfb513420405035c87d010c00b23281f2fff274006040423d029be84c6002012010110019adce76a26840206b90eb85ffc00019af1df6a26840106b90eb858fc0006ed207fa00d4d422f90005c8ca0715cbffc9d077748018c8cb05cb0222cf165005fa0214cb6b12ccccc973fb00c84014810108f451f2a7020070810108d718fa00d33fc8542047810108f451f2a782106e6f746570748018c8cb05cb025006cf165004fa0214cb6a12cb1fcb3fc973fb0002006c810108d718fa00d33f305224810108f459f2a782106473747270748018c8cb05cb025005cf165003fa0213cb6acb1f12cb3fc973fb00000af400c9ed54 +``` +
+ +当我们检查了`Shard Proof`后,我们需要反序列化`proof`和`state` cells。首先,`proof`证明Cell必须有两个根: + +```json + +[ 1 refs>, 1 refs>] + +``` + +第一个根是分片区块的Merkle证明(我们已经证明并信任它的哈希): + +```json +280[0339E5CBCA5BF69750B5D9897872C3A0D7A3E614E521C53E4DE728FAFED38DCE27001D] -> { + 64[11EF55AAFFFFFF11] -> { + 640[9BC7A98700000000840102332C67000000010000000000000000000000000064B6C351000023D38B96FDC0000023D38B96FDC5D41C6E3C0007035701DC64FD01DC42BEC400000003000000000000002E] -> { + 608[000023D38B69370401DC64FD2FA78EC529BCF9931E14F9D8B27EC1469290C0BAEF8256D657CE573B9679C5997431FCDA6BF2D0BE39344A9336CFE0AE9C844A88D2BD8022102E4012A760D4DB], + 608[000023D38B87BB8402332C662B4E96320F9D0AFB02E5D55B6B42C3349E33540620ECC07B399211FD56E4DE3E2555617CDDE457CD65A0AD033AAFC0C6C25DF716B04E455F49179668A46300DB] + }, + 288[0101CB54530AC857DF730E82EE239B2150528C6E5F6ED3678EAB6E1E789F0E3C7A530003], + 552[04F2AD1EDE336A68623DDABF36CB8FA405DBE70A38C453F711000F9A9F92592DB0F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD6902190219] -> { + 560[0103F2AD1EDE336A68623DDABF36CB8FA405DBE70A38C453F711000F9A9F92592DB04A4FF9713B206E420BAAEE4DD21FEBBEB426FCD9CE158DB2A56DCE9188FC313E0219001B], + 560[0103F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD6987D796744CA386906016C56921370D01F72CB004A1D7C294752AFE4446DA07BB0219001B] + }, + 288[0101D0CF03A1058C2FD6029288951051A0D82733953C1E9181A67C502CE59B180200000B] + } +} +``` + +如同在`Shard Proof`验证中所做的,我们需要使用`check_block_header`函数:检查Block Cell是否有效并记住新的`StateUpdate`哈希。 + +然后我们反序列化第二个根(我们称之为`state_cell`)并检查它的Hash_1是否与我们记住的哈希匹配: +```python +proof_cells = Cell.from_boc(proof) +if len(proof_cells) != 2: + raise ProofError('expected 2 root cells in account state proof') + +state_cell = proof_cells[1] + +state_hash = check_block_header_proof(proof_cells[0][0], shrd_blk.root_hash, True) + +if state_cell[0].get_hash(0) != state_hash: + raise ProofError('state hashes mismatch') +``` + +现在我们可以信任`state_cell`,它看起来是这样的: + +
+ + 显示Cell + +```json +280[03F93FE5EDA41A6CE9ECB353FD589842BD3F5D5E73B846CB898525293FC742FD690219] -> { + 362[9023AFE2FFFFFF110000000000000000000000000002332C670000000164B6C351000023D38B96FDC501DC64FD00] -> { + 288[010138F8D1C6E9F798A477D13AA26CB4D6CFE1A17949AC276B2F1E0CE037A521B9BC0001], + 75[82097522AF06FFAFF1E0] -> { + 76[0104BA9157837FD7F8F0] -> { + 76[01032030FDC45F2D3830] -> { + 288[010159E1A18EE4E5670306B5203912C87DFFC17898F0999BD128A6965027D53B6FA40215], + 76[01013FA38088AAEA2B70] -> { + 288[010188D5F8A73382AEA73DEDE03FC3BCDA2634A717EF50E7428D5A4A44C771B014B90066], + 76[01005ECD9E51E5D22A30] -> { + 76[0100303B3B607D7FFC70] -> { + 288[0101CA85960E3FC3DFB6D26E83AE87A837AE5C2FAF7C8D43EA177393C602FADAA0300039], + 68[00E0F41ADA252E2F00] -> { + 288[01014FAE109C41F3D5E2BE0A3FF00A007F2E50A796797700D18A7AA663E531C37180002D], + 68[00E05C33225B78BCE0] -> { + 288[0101545B5925B3AB2A8DF2470FE22A5A3C9CC64E3CB24407C26167E0FBB476E05309002C], + 68[00E03480847F372160] -> { + 288[0101844A14C99695506E920635D18E76D9E685ADEE74E5FBA6F6D3B371CA77E348130029], + 60[00D0B1CCE62AECC0] -> { + 60[00C625C7E90DFC60] -> { + 288[01010326812B62712345473070D679BC38CDBBCE58B7A2BF6C5C6F091FC8D36E81CD001F], + 60[00C279D628DBF200] -> { + 60[00C0B8F29F9D04E0] -> { + 288[0101DE5072F46A0E0ECAB2BBFC2CFC62A3FE200F12D5D457DF833A46EB747FA004E30059], + 60[00C03FA2EC9AD840] -> { + 288[0101BAEE90FD11A130D6D2E2DED21AE4A7B86553116015B7E7EBFC52369534D298B20017], + 60[00C02E722BDED720] -> { + 52[00AB138E7F1840] -> { + 288[0101E1FC242C29E519F9740CA2570D85779AED0C593CC36B59119852945988E186960015], + 52[00A21324D3FF20] -> { + 288[010199FE288FDCE2606D39F9B6AF72F9C2643EF06E6BACC15DD72CFA84D63C9E44A40013], + 52[00A1E877EC8BA0] -> { + 288[01019E019E92BE76A5AE7AEE239299F561682AFBE445DC42EE57CCC31ECB427FDF42000E], + 52[00A1DB848431A0] -> { + 288[01012345B80E66C025FB62C41261B5D230616303EC47F3BB7A255872FADA62A1E8BF0010], + 52[00A1D633BC1060] -> { + 52[00A02CA3DDC460] -> { + 616[BB19E61B8190C2587677C010CE49A93364B965F7762A9810D916B082F45E080A02BC35EBAA649B46AC72E6E4D4C1293B66D58D9ED7A54902BEEFD97F5BFF7977DD85998B3D000023C564393441] -> { + 288[01018282D13BF66B9ACE1FBF5D3ABD1C59CC46D61AF1D47AF1665D3013D8F9E474880008] + }, + 288[0101EDCED2278013EA497DD2E286F495B4F7F8DF6EA73E08E85414FC43A611C17797000B] + }, + 288[0101654781E5D466EC4CA50CB2983B20170BB5D90E2E6AB83ED7D42A829651A5EEC1000A] + } + } + } + } + }, + 288[01017F1DF311101E472B1D443334D2426FD339539F558694C60E3428221DCB1A54780016] + } + } + }, + 288[01019143ABF2A72662054EDA4F4949D010C897AFF4383B514B387CFF790408231C6C001A] + } + }, + 288[01019CA2157C92D49B9D051388DE45D07072C78A3AA65A5B05547D94E0369AA6BDEE002A] + } + } + } + }, + 288[0101D7ACBB602338C86D610F35CFB362FD76FC18B1812476B6FCA99A0678E665FCF50000] + }, + 288[010182EB0E24C842092EC2705486CBBE98DE8016D55F5CFF4EA910471A4C3A7A1CF1003B], + 288[0101ED7E26BD36EFA6D5D9B4F6AAAB9813AF0742A84244977F74FD4074C9C98908BE0000] + }, + 288[0101ED7E26BD36EFA6D5D9B4F6AAAB9813AF0742A84244977F74FD4074C9C98908BE0000] + }, + 288[01016F315F25B4A39AC12C85FEA4ECFE7A83E5E59D1F059783FA0C3EF279730880610000] + }, + 288[01013E38E2548C5236A9652C45E553CED677F76550097B94138A4576F122443944D40069], + 288[0101B3E9649D10CCB379368E81A3A7E8E49C8EB53F6ACC69B0BA2FFA80082F70EE390001] + }, + 288[0101B3E9649D10CCB379368E81A3A7E8E49C8EB53F6ACC69B0BA2FFA80082F70EE390001] + }, + 868[0000000000000000FFFFFFFFFFFFFFFF825D48ABC1BFEBFC7BC2DF8993C189361000023D38B69370401DC64FD2FA78EC529BCF9931E14F9D8B27EC1469290C0BAEF8256D657CE573B9679C5997431FCDA6BF2D0BE39344A9336CFE0AE9C844A88D2BD8022102E4012A760D4DB0] -> { + 288[0101B3E9649D10CCB379368E81A3A7E8E49C8EB53F6ACC69B0BA2FFA80082F70EE390001] + } + } +} +``` + +
+ +同样,唯一的Merkle证明引用前缀为`9023AFE2`,这是[ShardStateUnsplit](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L410) +TLB方案的前缀,所以我们要根据TLB方案进行反序列化: + +```python +{ + 'global_id': -239, + 'shard_id': {'shard_pfx_bits': 0, 'workchain_id': 0, 'shard_prefix': 0}, + 'seq_no': 36908135, + 'vert_seq_no': 1, + 'gen_utime': 1689699153, + 'gen_lt': 39391487000005, + 'min_ref_mc_seqno': 31220989, + 'out_msg_queue_info': 0 refs>, + 'before_split': 0, + 'accounts': ( + { + 50368879097771769677871174881221998657607998794347754829932074327482686052226: { + 'account': None, + 'last_trans_hash': b'd\x9bF\xacr\xe6\xe4\xd4\xc1);f\xd5\x8d\x9e\xd7\xa5I\x02\xbe\xef\xd9\x7f[\xffyw\xdd\x85\x99\x8b=', + 'last_trans_lt': 39330697000001, + 'cell': 1 refs> + } + }, + [ + {'split_depth': 0, 'balance': {'grams': 5873792469, 'other': None}}, + {'split_depth': 0, 'balance': {'grams': 5991493155, 'other': None}}, + {'split_depth': 0, 'balance': {'grams': 63109456003, 'other': None}}, + {'split_depth': 0, 'balance': {'grams': 63822897549, 'other': None}}, + ... + {'split_depth': 0, 'balance': {'grams': 21778458402704, 'other': None}}, + {'split_depth': 0, 'balance': {'grams': 54074699968483, 'other': None}}, + {'split_depth': 0, 'balance': {'grams': 2725956214994157511, 'other': None}} + ] + ), + 'overload_history': 0, + 'underload_history': 18446744073709551615, + 'total_balance': {'grams': 2725956214994157511, 'other': None}, + 'total_validator_fees': {'grams': 37646260890702444, 'other': None}, + 'libraries': None, + 'master_ref': {'master': {'end_lt': 39391484000004, 'seqno': 31220989, 'root_hash': b'/\xa7\x8e\xc5)\xbc\xf9\x93\x1e\x14\xf9\xd8\xb2~\xc1F\x92\x90\xc0\xba\xef\x82V\xd6W\xceW;\x96y\xc5\x99', 'file_hash': b't1\xfc\xdak\xf2\xd0\xbe94J\x936\xcf\xe0\xae\x9c\x84J\x88\xd2\xbd\x80"\x10.@\x12\xa7`\xd4\xdb'}}, + 'custom': None +} +``` + +我们需要`account`字段,它具有[ShardAccounts](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L261)类型。 +`ShardAccounts`是一个HashmapAugE,其中键是地址的hash_part,值具有`ShardAccount`类型,额外数据具有`DeepBalanceInfo`类型。 + +解析地址`EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG`,我们得到hash_part等于`50368879097771769677871174881221998657607998794347754829932074327482686052226`,因此我们需要从Hashmap中获取这个键的值: + +```python +{ + 50368879097771769677871174881221998657607998794347754829932074327482686052226: { + 'account': None, + 'last_trans_hash': b'd\x9bF\xacr\xe6\xe4\xd4\xc1);f\xd5\x8d\x9e\xd7\xa5I\x02\xbe\xef\xd9\x7f[\xffyw\xdd\x85\x99\x8b=', + 'last_trans_lt': 39330697000001, + 'cell': 1 refs> + } +} +``` + +我们需要记住`last_trans_hash`和`last_trans_lt`,因为我们可以使用它们来获取账户交易,并检查这些数据的整个Cell: + +```json +320[649B46AC72E6E4D4C1293B66D58D9ED7A54902BEEFD97F5BFF7977DD85998B3D000023C564393441] -> { + 288[01018282D13BF66B9ACE1FBF5D3ABD1C59CC46D61AF1D47AF1665D3013D8F9E474880008] +} +``` + +我们看到Cell是一个普通的Cell,级别为1,只有一个引用 - 被剪裁的账户数据,因此我们计算这个剪裁分支的Hash_1 - 我们可以信任的账户状态哈希:`8282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488`。 + +现在最后一步是反序列化`state` boc: + +```json +449[C006F5BC67986E06430961D9DF00433926A4CD92E597DDD8AA6043645AC20BD178222C859043259E0D9000008F1590E4D10D405786BD755300] -> { + 80[FF00F4A413F4BCF2C80B] -> { + 2[00] -> { + 4[40] -> { + 920[D001D0D3032171B0925F04E022D749C120925F04E002D31F218210706C7567BD22821064737472BDB0925F05E003FA403020FA4401C8CA07CBFFC9D0ED44D0810140D721F404305C810108F40A6FA131B3925F07E005D33FC8258210706C7567BA923830E30D03821064737472BA925F06E30D] -> { + 480[01FA00F40430F8276F2230500AA121BEF2E0508210706C7567831EB17080185004CB0526CF1658FA0219F400CB6917CB1F5260CB3F20C98040FB0006], + 552[5004810108F45930ED44D0810140D720C801CF16F400C9ED540172B08E23821064737472831EB17080185005CB055003CF1623FA0213CB6ACB1FCB3FC98040FB00925F03E2] + }, + 2[00] -> { + 2[00] -> { + 4[50] -> { + 242[B29DFB513420405035C87D010C00B23281F2FFF274006040423D029BE84C40], + 2[00] -> { + 97[ADCE76A26840206B90EB85FF80], + 97[AF1DF6A26840106B90EB858F80] + } + }, + 68[B8C97ED44D0D70B1F0] + }, + 357[BD242B6F6A2684080A06B90FA0218470D4080847A4937D29910CE6903E9FF9837812801B7810148987159F3180] + } + }, + 992[F28308D71820D31FD31FD31F02F823BBF264ED44D0D31FD31FD3FFF404D15143BAF2A15151BAF2A205F901541064F910F2A3F80024A4C8CB1F5240CB1F5230CBFF5210F400C9ED54F80F01D30721C0009F6C519320D74A96D307D402FB00E830E021C001E30021C002E30001C0039130E30D03A4C8CB1F12CB1FCBFF] -> { + 440[D207FA00D4D422F90005C8CA0715CBFFC9D077748018C8CB05CB0222CF165005FA0214CB6B12CCCCC973FB00C84014810108F451F2A702], + 448[810108D718FA00D33FC8542047810108F451F2A782106E6F746570748018C8CB05CB025006CF165004FA0214CB6A12CB1FCB3FC973FB0002], + 432[810108D718FA00D33F305224810108F459F2A782106473747270748018C8CB05CB025005CF165003FA0213CB6ACB1F12CB3FC973FB00], + 40[F400C9ED54] + } + } + }, + 321[000000E929A9A317C1B3226CE226D6D818BAFE82D3633AA0F06A6C677272D1F9B760FF0D0DCF56D800] +} +``` + +计算其 representation hash ,并确保它与我们从被剪裁的Cell中得到的匹配:`8282d13bf66b9ace1fbf5d3abd1c59cc46d61af1d47af1665d3013d8f9e47488`。 + +并根据[Account](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L231-L233) TLB方案进行反序列化: + +```python +{ + 'addr': Address, + 'storage_stat': {'used': {'cells': 22, 'bits': 5697, 'public_cells': None}, 'last_paid': 1689502130, 'due_payment': None}, + 'storage': { + 'last_trans_lt': 39330697000003, + 'balance': {'grams': 5873792469, 'other': None}, + 'state': { + 'type_': 'account_active', + 'state + +_init': {'split_depth': None, 'special': None, 'code': 1 refs>, 'data': 0 refs>, 'library': None} + } + } +} +``` + +现在我们可以信任这个账户状态数据。 + +_检查证明示例:_ [Python](https://github.com/yungwine/pytoniq/blob/master/pytoniq/proof/check_proof.py#L87), [Kotlin](https://github.com/andreypfau/ton-kotlin/blob/b1edc4b134e89ccf252149f27c85fd530377cebe/ton-kotlin-liteclient/src/commonMain/kotlin/CheckProofUtils.kt#L37), [C++](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/check-proof.cpp#L161) +``` + +## 账户交易 + +对于 [liteServer.getTransactions](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tl/generate/scheme/lite_api.tl#L71) 请求,我们必须提供用于开始的交易的 `lt` 和 `hash`。如果我们想获取最后的账户交易,我们可以从 `ShardAccount`(上文描述过)获取它们,并信任这些 `lt` 和 `hash`。 + +当我们从Liteserver接收到交易时,我们会得到一个包含我们所请求交易数量的boc根。每个根都是一个cell(Cell),我们应该根据 [Transaction](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L263-L269) TLB方案进行反序列化。对于第一个交易cell,我们应该检查其哈希是否与我们从账户状态获取的 `last_trans_hash` 匹配。然后我们记住 `prev_trans_hash` 字段的值,并将其与第二个根的哈希进行比较,依此类推。 + +## 区块交易 + +让我们向Liteserver询问属于我们在文章开头提到的区块的交易。LiteServer 的[回应](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl#L46)包含了包含交易的 `ids` 字段和 `proof` boc。首先,让我们反序列化 `proof`: + +```json +280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> { + 64[11EF55AAFFFFFF11] -> { + 288[0101F8039FE65901BE422094ED29FA05DD4A9406708D7C54EBF7F6010F2E8A9DCBB10001], + 288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003], + 288[0101741100D622B0D5264BCDB86A14E36FC8C349B82AE49E037002EB07079EAD8B060015], + 545[4A33F6FD11224E018A0801116DBA929FAA60F8B9DFB39286C07FDE613D4F158E4031612597E23F312DA061732C2DB7C7C7F0BCA6295EF25D04F46FA21A055CF213A1270A80] -> { + 288[0101E057F7AA0545EF9E6BF187542A5141298303A33BA7C9CE26C71FFD9C7D2050600004], + 6[00], + 6[80] -> { + 9[4000] -> { + 605[BFB333333333333333333333333333333333333333333333333333333333333333029999999999999999999999999999999999999999999999999999999999999999CF800008F4E2E9900000] -> { + 9[5000] -> { + 288[01015EF0532AF460BCF3BECF1A94597C1EC04879E0F26BF58269D319121376AAD4730002] + }, + 9[4000] -> { + 288[0101B1E091FCB9DF53917EAA0CAE05041B3D0956242871E3CA8D6909D0AA31FF36040002] + }, + 520[7239A4AED4308E2E6AC11C880CCB29DFEE407A3E94FC1EDBDD4D29AF3B5DFEEE58A9B07203A0F457150A2BF7972DA7E2A79642DEBE792E919DE5E2FC284D2B158A] + }, + 607[BF955555555555555555555555555555555555555555555555555555555555555502AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0000008F4E2E99000C0] -> { + 288[0101924B5992DF95114196994A6D449D89E1C002CB96C14D11C4A667F843A3FAF4410002], + 520[72899B3A210DDD28D905C583FF8559BCF73D0CF0C05C11210BD7059BAB2AB453E03524184B116C9E39D9D5293179588F4B7D8F5D8192FEFE66B9FE40A71518DBC7] + } + } + }, + 288[01010FC5CF36DC84BC46E7175768AB3EC0F94988D454F2C496DC1AC32E638CD3C23D0005] + } + } +} +``` + +现在我们应该检查区块头证明(以信任这个cell数据)并根据区块TLB方案进行反序列化: + +```python +{ + 'global_id': -239, + 'info': None, + 'value_flow': None, + 'state_update': None, + 'extra': { + 'in_msg_descr': 0 refs>, + 'out_msg_descr': ({}, [ 0 refs>]), + 'account_blocks': ( + { + 23158417847463239084714197001737581570653996933128112807891516801582625927987: { + 'account_addr': '3333333333333333333333333333333333333333333333333333333333333333', + 'transactions': ( + { + 39391488000001: 0 refs>, + 39391488000002: 0 refs> + }, + [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}] + ), + 'state_update': {'old_hash': b'9\xa4\xae\xd40\x8e.j\xc1\x1c\x88\x0c\xcb)\xdf\xee@z>\x94\xfc\x1e\xdb\xddM)\xaf;]\xfe\xeeX', 'new_hash': b'\xa9\xb0r\x03\xa0\xf4W\x15\n+\xf7\x97-\xa7\xe2\xa7\x96B\xde\xbey.\x91\x9d\xe5\xe2\xfc(M+\x15\x8a'} + }, + 38597363079105398474523661669562635951089994888546854679819194669304376546645: { + 'account_addr': '5555555555555555555555555555555555555555555555555555555555555555', + 'transactions': ( + { + 39391488000003: 0 refs> + }, + [{'grams': 0, 'other': None}] + ), + 'state_update': {'old_hash': b'\x89\x9b:!\r\xdd(\xd9\x05\xc5\x83\xff\x85Y\xbc\xf7=\x0c\xf0\xc0\\\x11!\x0b\xd7\x05\x9b\xab*\xb4S\xe0', 'new_hash': b'5$\x18K\x11l\x9e9\xd9\xd5)1yX\x8fK}\x8f]\x81\x92\xfe\xfef\xb9\xfe@\xa7\x15\x18\xdb\xc7'} + } + }, + [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}] + ), + 'rand_seed': b'\x11"N\x01\x8a\x08\x01\x11m\xba\x92\x9f\xaa`\xf8\xb9\xdf\xb3\x92\x86\xc0\x7f\xdea=O\x15\x8e@1a%', + 'created_by': b"\x97\xe2?1-\xa0as,-\xb7\xc7\xc7\xf0\xbc\xa6)^\xf2]\x04\xf4o\xa2\x1a\x05\\\xf2\x13\xa1'\n", + 'custom': None + } +} +``` + +在这种情况下,我们应该记住字段 `block` -> `extra` -> `account_blocks`,它的类型是 [ShardAccountBlocks](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L282),是HashmapAugE,其中键是地址的hash_part,值是 [AccountBlock](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L277-L280) 类型,额外的是 `CurrencyCollection` 类型: + +```python +{ + 23158417847463239084714197001737581570653996933128112807891516801582625927987: { + 'account_addr': '3333333333333333333333333333333333333333333333333333333333333333', + 'transactions': ( + { + 39391488000001: 0 refs>, + 39391488000002: 0 refs> + }, + [{'grams': 0, 'other': None}, {'grams': 0, 'other': None}, {'grams': 0, 'other': None}] + ), + 'state_update': {'old_hash': b'9\xa4\xae\xd40\x8e.j\xc1\x1c\x88\x0c\xcb)\xdf\xee@z>\x94\xfc\x1e\xdb\xddM)\xaf;]\xfe\xeeX', 'new_hash': b'\xa9\xb0r\x03\xa0\xf4W\x15\n+\xf7\x97-\xa7\xe2\xa7\x96B\xde\xbey.\x91\x9d\xe5\xe2\xfc(M+\x15\x8a'} + }, + 38597363079105398474523661669562635951089994888546854679819194669304376546645: { + 'account_addr': '5555555555555555555555555555555555555555555555555555555555555555', + 'transactions': ( + { + 39391488000003: 0 refs> + }, + [{'grams': 0, 'other': None}] + ), + 'state_update': {'old_hash': b'\x89\x9b:!\r\xdd(\xd9\x05\xc5\x83\xff\x85Y\xbc\xf7=\x0c\xf0\xc0\\\x11!\x0b\xd7\x05\x9b\xab*\xb4S\xe0', 'new_hash': b'5$\x18K\x11l\x9e9\xd9\xd5)1yX\x8fK}\x8f]\x81\x92\xfe\xfef\xb9\xfe@\xa7\x15\x18\xdb\xc7'} + } +} +``` + +现在让我们检查 `ids`: + +```python +[ + {'mode': 39, 'account': '3333333333333333333333333333333333333333333333333333333333333333', 'lt': 39391488000001, 'hash': '5ef0532af460bcf3becf1a94597c1ec04879e0f26bf58269d319121376aad473'}, + {'mode': 39, 'account': '3333333333333333333333333333333333333333333333333333333333333333', 'lt': 39391488000002, 'hash': 'b1e091fcb9df53917eaa0cae05041b3d0956242871e3ca8d6909d0aa31ff3604'}, + {'mode': 39, 'account': '5555555555555555555555555555555555555555555555555555555555555555', 'lt': 39391488000003, 'hash': '924b5992df95114196994a6d449d89e1c002cb96c14d11c4a667f843a3faf441'} +] +``` + +对于这里的每个交易,我们需要在我们记住的 `account_block` 中找到它,并比较哈希: + +```python +block_trs: dict = acc_block.get(int(tr['account'], 16)).transactions[0] +block_tr: Cell = block_trs.get(tr['lt']) +assert block_tr.get_hash(0) == tr['hash'] +``` + +:::note + +在这个例子中,检查 `ids` 字段是不必要的,我们可以直接从账户块中取交易。 +但是当你请求 [liteServer.listBlockTransactionsExt](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl#L80) 方法时, +你需要类似地检查证明,但在那种情况下,你确实需要比较哈希。 + +::: + +## 配置 + +让我们向Liteserver请求1、4、5、7、8和15号[配置参数](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl#L83)(对于 [liteServer.getConfigAll](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl#L82) 方法,您可以获取所有参数,验证证明的方式是相同的)。 +[回应](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/lite_api.tl#L53)包含 `state_proof` 和 `config_proof`。 + +首先,让我们反序列化 `state_proof` cell: + +```json +280[0351ED3B9E728E7C548B15A5E5CE988B4A74984C3F8374F3F1A52C7B1F46C264060016] -> { + 64[11EF55AAFFFFFF11] -> { + 640[9BC7A98700000000040101DC65010000000100FFFFFFFF000000000000000064B6C356000023D38BA64000000023D38BA64004886D00960007028101DC64FD01DC42BEC400000003000000000000002E] -> { + 608[000023D38B96FDC401DC650048A3971C46472B85C8D761060A6E7AE9F13A90CDDA815915A89597CFECB393A6B568807ADFB3C1C5EFC920907225175DB61CA384E4F8B313799E3CBB8B7B4085] + }, + 288[01018C6053C1185700C0FE4311D5CF8FA533EA0382E361A7B76D0CF299B75AC0356C0003], + 552[0478E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E432BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F016F] -> { + 560[010378E0F0E601BA1161ECC1395E9A0475C4F80AADBD6C483F210E96E29CF36789E46492304DFB6EF9149781871464AF686056A9627F882F60E3B24F8C944A75EBAF016F0014], + 560[010332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046DA58493CCB5DA3876129B0190F3C375E69E59C3AD9FF550BE708999DAD1F6F39016F0014] + }, + 288[01015720B6AEFCBF406209522895FAA6C0D10CC3315D90BCAF09791B19F595E86F8F0007] + } +} +``` + +对于此cell,我们应该检查区块头证明,并记住 `StateUpdate` 的新哈希值。 + +现在,让我们反序列化 `config_proof` cell: + +
+ + 显示cell + +```json +280[0332BF3592969931CA4FBC7715494B50597F1884C0D847456029D8CF0E526E6046016F] -> { + 362[9023AFE2FFFFFF1100FFFFFFFF000000000000000001DC65010000000164B6C356000023D38BA6400401DC64FD40] -> { + 288[0101AFFE84CDD73951BCE07EEAAD120D00400295220D6F66F1163B5FA8668202D72B0001], + 288[0101FAED0DD3CA110ADA3D22980E3795D2BDF15450E9159892BBF330CDFD13A3B880016E], + 204[0000000000000000FFFFFFFFFFFFFFFF820CE9D9C3929379C820] -> { + 288[0101A5A7D24057D8643B2527709D986CDA3846ADCB3EDDC32D28EC21F69E17DBAAEF0001], + 288[0101DEAB5A5AAF79C5E24F8DCBBE51747D6804104F75F58ED5BED4702C353545C6AC0011] + }, + 342[CC26AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC23519B11EDDC69B7C] -> { + 288[0101C7DAE90A1FCEAD235CACC318A048986B2E12D0F68C136845669E02C4E28F018D0002], + 2[00] -> { + 8[D8] -> { + 2[00] -> { + 2[00] -> { + 2[00] -> { + 2[00] -> { + 2[00] -> { + 2[00] -> { + 288[0101F89085ED347F5F928A0DF7B1271F906F6E1EF43D89B5912774C8B42D0E24AB120001], + 2[00] -> { + 256[3333333333333333333333333333333333333333333333333333333333333333] + } + }, + 4[40] -> { + 256[0000000000000000000000000000000000000000000000000000000000000000] + } + }, + 2[00] -> { + 2[00] -> { + 2[00] -> { + 256[E56754F83426F69B09267BD876AC97C44821345B7E266BD956A7BFBFB98DF35C] + }, + 2[00] -> { + 329[01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000008000000100] + } + }, + 4[50] -> { + 1[80] -> { + 2[00] -> { + 83[BE000003BCB3670DC15540], + 83[BFFFFFFFBCBD1A94A20000] + } + } + } + } + }, + 2[00] -> { + 2[00] -> { + 2[00] -> { + 2[00] -> { + 104[C400000002000000000000002E] + }, + 288[0101C1F3C2ADA12BD901BBA1552C0C090CC3989649807C2B764D02548C1F664C20890007] + }, + 288[010187DADFBB3AE954E7F5472C46A729ED80AD087C5D9CEBB8D644D16DD73F88DF390009] + }, + 2[00] -> { + 288[01017CF937AF64AED1AB2CDD1435F8FF79F86E521320CC7B0CB30C9AAE81748124090002], + 2[00] -> { + 288[0101BEE8EB75C37500A75962E4FD99AFC62B3C9245948D2AC56061B0E21DDD6E9E840001], + 2[00] -> { + 128[00010000000080000000200000008000] + } + } + } + } + }, + 288[0101289F7704162F68EF3CC5B4865BD72067277E25B21514AB741396C54BD92294FA0009] + }, + 288[0101EF6962F43C1C86B216773B443F61829550DD9E956EE54EA3AC5C60E127DADD51000E] + }, + 288[0101112A0556A091DC4F72BD31FF2790783FB3238CE2AA41E1C137424D279664D7E3000A] + }, + 288[010124D21CF7AE96B1C55A1230E823DB0317CE24EC33E3BF2585C79605684304FAF20007] + }, + 766[0001AAA0161D000702816000047A7172DFB88800011E8B625908200EE215F71061846393A08C682E87BC3A12AFF2D246EB97A09164F5657F96F9A252EF71580FE5309A823F73F3C4C3F8AB73F5A85BBF204BFD22E68D36D0EFAB1818E7B428BC] -> { + 288[010150FCC05BD9723571B83316A5F650BE31EDB131D05FDC78D271486E5D4EF077E10019], + 288[0101E5BE728200B172CF7E2356CBA2AE1C6E2C790BE7C03CD7814C6E6FE3080B944B0011] + }, + 2[00] -> { + 83[BE000003BCB3670DC15540], + 83[BFFFFFFFBCBD1A94A20000] + } + } + } +} +``` + +
+ +我们需要将此 Merkle 证明的 Hash_1 仅与我们从上面的 `check_block_header` 函数获得的哈希进行比较,这样我们才能信任此cell: + +```python +state_hash = check_block_header_proof(state_proof[0], block.root_hash, True) +if config_proof[0].get_hash(0) != state_hash: + raise LiteClientError('hashes mismatch') +``` + +现在,让我们根据 `ShardStateUnsplit` 方案反序列化cell: + +```python +{ + 'global_id': -239, + 'shard_id': {'shard_pfx_bits': 0, 'workchain_id': -1, 'shard_prefix': 0}, + 'seq_no': 31220993, + 'vert_seq_no': 1, + 'gen_utime': 1689699158, + 'gen_lt': 39391488000004, + 'min_ref_mc_seqno': 31220989, + 'out_msg_queue_info': 0 refs>, + 'before_split': 0, + 'accounts': 0 refs>, + 'overload_history': 0, + 'underload_history': 18446744073709551615, + 'total_balance': {'grams': 2364000148715550620, 'other': None}, + 'total_validator_fees': {'grams': 0, 'other': None}, + 'libraries': None, + 'master_ref': None, + 'custom': { + 'shard_hashes': None, + 'config': { + 'config_addr': '5555555555555555555555555555555555555555555555555555555555555555', + 'config': { + 1: 0 refs>, + 4: 0 refs>, + 5: 0 refs>, + 7: 1 refs>, + 8: 0 refs>, + 15: 0 refs>} + }, + 'flags': 1, + 'validator_info': {'validator_list_hash_short': 2862618141, 'catchain_seqno': 459393, 'nx_cc_updated': False}, + 'prev_blocks': None, + 'after_key_block': True, + 'last_key_block': {'end_lt': 39382372000004, 'seqno': 31212222, 'root_hash': b'\xe2\x0c0\x8crt\x11\x8d\x05\xd0\xf7\x87BU\xfeZH\xddr\xf4\x12,\x9e\xac\xaf\xf2\xdf4J]\xee+', 'file_hash': b'\x01\xfc\xa6\x13PG\xee~x\x98\x7f\x15n~\xb5\x0bw\xe4\t\x7f\xa4\\\xd1\xa6\xda\x1d\xf5c\x03\x1c\xf6\x85'}, + 'block_create_stats': {'type_': 'block_create_stats', 'counters': None}, + 'global_balance': {'grams': 5089971531496870767, 'other': {239: 666666666666, 4294967279: 1000000000000}} + } +} +``` + +并获取 `ShardStateUnsplit` -> `custom` -> `config` -> `config` 字段,这是一个哈希映射,其中键是配置参数编号,值是包含参数值的cell。 + +对所有参数反序列化后,我们得到: + +```python +{ + 1: { + 'elector_addr': b'33333333333333333333333333333333', + }, + 4: { + 'dns_root_addr': b'\xe5gT\xf84&\xf6\x9b\t&{\xd8v\xac\x97\xc4H!4[~&k\xd9V\xa7\xbf\xbf\xb9\x8d\xf3\\', + }, + 5: { + 'blackhole_addr': b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', + 'fee_burn_nom': 1, + 'fee_burn_denom': 2 + }, + 7: { + 'to_mint': {'dict': {239: 666666666666, 4294967279: 1000000000000}} + }, + 8: { + 'version': 2, + 'capabilities': 46 + }, + 15: { + 'validators_elected_for': 65536, + 'elections_start_before': 32768, + 'elections_end_before': 8192, + 'stake_held_for': 32768 + } +} +``` + +## 参阅 + +* [特殊cell](/develop/data-formats/exotic-cells) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-language.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-language.mdx new file mode 100644 index 0000000000..b75ebfac46 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-language.mdx @@ -0,0 +1,605 @@ +import ThemedImage from '@theme/ThemedImage'; + +# TL-B 语言 + +TL-B(类型语言 - 二进制)用于描述类型系统、构造函数和现有功能。例如,我们可以使用 TL-B 方案构建与 TON 区块链关联的二进制结构。特殊的 TL-B 解析器可以读取方案,将二进制数据反序列化为不同的对象。TL-B 描述了 `Cell` 对象的数据方案。如果您不熟悉 `Cells`,请阅读 [Cell & Bag of Cells(BOC)](https://docs.ton.org/develop/data-formats/cell-boc#cell) 文章。 + +## 概述 + +我们将任何一组 TL-B 构造称为 TL-B 文档。一个 TL-B 文档通常包括类型的声明(即它们的构造函数)和功能组合子。每个组合子的声明都以分号 (`;`) 结尾。 + +这是一个可能的组合子声明示例: + +

+ +

+ +## 构造函数 + +每个等式的左侧描述了定义或序列化右侧指示类型的值的方式。这样的描述以构造函数的名称开始。 + + +

+ +

+ +构造函数用于指定组合器的类型,包括在序列化时的状态。例如,当你想在向 TON 智能合约查询时指定一个 `op`(操作码)的时候,也可以使用构造函数。 + +```tlb +// .... +transfer#5fcc3d14 <...> = InternalMsgBody; +// .... +``` + +* 构造函数名称:`transfer` +* 构造函数前缀代码:`#5fcc3d14` + +注意,每个构造函数名称后面紧跟一个可选的构造函数标签,例如 `#_` 或 `$10`,它描述了用于编码(序列化)所讨论的构造函数的位串(bitstring)。 + +```tlb +message#3f5476ca value:# = CoolMessage; +bool_true$0 = Bool; +bool_false$1 = Bool; +``` + +每个等式的左侧描述了定义或序列化右侧指示类型的值的方式。这样的描述以构造函数的名称开始,如 `message` 或 `bool_true`,紧接着是一个可选的构造函数标签,例如 `#3f5476ca` 或 `$0`,它描述了用于编码(序列化)所讨论的构造函数的位。 + +| 构造函数 | 序列化 | +|-------------------------------|------------------------------------------| +| `some#3f5476ca` | 从十六进制值序列化为 32 位 uint | +| `some#5fe` | 从十六进制值序列化为 12 位 uint | +| `some$0101` | 序列化 `0101` 原始位 | +| `some` 或 `some#` | 序列化 `crc32(equation) \| 0x80000000` | +| `some#_` 或 `some$_` 或 `_` | 不进行序列化 | + +构造函数名称(此示例中的 `some`)在代码生成中用作变量。例如: + +```tlb +bool_true$1 = Bool; +bool_false$0 = Bool; +``` + +类型 `Bool` 有两个标签 `0` 和 `1`。代码生成伪代码可能看起来像: + +```python3 + +class Bool: + tags = [1, 0] + tags_names = ['bool_true', 'bool_false'] +``` + +如果你不想为当前构造函数定义任何名称,只需传递 `_`,例如 `_ a:(## 32) = 32Int;` + +构造函数标签可以用二进制(在美元符号后)或十六进制表示法(在井号后)给出。如果未明确提供标签,则 TL-B 解析器必须通过用 CRC32 算法散列定义此构造函数的“等式”文本,并带有 `| 0x80000000` 来计算默认的 32 位构造函数标签。因此,必须通过 `#_` 或 `$_` 明确提供空标签。 + +此标签将用于在反序列化过程中猜测当前位串的类型。例如,我们有 1 位位串 `0`,如果我们告诉 TLB 将此位串解析为 `Bool` 类型,它将被解析为 `Bool.bool_false`。 + +假设我们有更复杂的示例: + +```tbl +tag_a$10 val:(## 32) = A; +tag_b$00 val(## 64) = A; +``` + +如果我们在 TLB 类型 `A` 中解析 `1000000000000000000000000000000001`(1 和 32 个零和 1)- 首先我们需要获取前两位来定义标签。在此示例中,前两位 `10` 代表 `tag_a`。所以现在我们知道接下来的 32 位是 `val` 变量,在我们的示例中是 `1`。一些“解析”的伪代码变量可能看起来像: + +```python3 +A.tag = 'tag_a' +A.tag_bits = '10' +A.val = 1 +``` + +所有构造函数名称必须是不同的,并且同一类型的构造函数标签必须构成一个前缀码(否则反序列化将不是唯一的);即,同一类型中的任何标签都不能是任何其他标签的前缀。 + +每种类型的构造函数最大数量:`64` +标签的最大位数:`63` + +二进制示例: + +```tlb +example_a$10 = A; +example_b$01 = A; +example_c$11 = A; +example_d$00 = A; +``` + +代码生成伪代码可能看起来像: + +```python3 + +class A: + tags = [2, 1, 3, 0] + tags_names = ['example_a', 'example_b', 'example_c', 'example_d'] +``` + +十六进制标签示例: + +```tlb +example_a#0 = A; +example_b#1 = A; +example_c#f = A; +``` + +代码生成伪代码可能看起来像: + +```python3 + +class A: + tags = [0, 1, 15] + tags_names = ['example_a', 'example_b', 'example_c'] +``` + +如果使用 `hex` 标签,请记住,它将以每个十六进制符号 4 位进行序列化。最大值是 63 位无符号整数。这意味着: + +```tlb +a#32 a:(## 32) = AMultiTagInt; +b#1111 a:(## 32) = AMultiTagInt; +c#5FE a:(## 32) = AMultiTagInt; +d#3F5476CA a:(## 32) = AMultiTagInt; +``` + +| 构造函数 | 序列化 | +|---------------|----------------------------------------| +| `a#32` | 从十六进制值序列化为 8 位 uint | +| `b#1111` | 从十六进制值序列化为 16 位 uint | +| `c#5FE` | 从十六进制值序列化为 12 位 uint | +| `d#3F5476CA` | 从十六进制值序列化为 32 位 uint | + +十六进制值允许使用大写和小写。 + +#### 关于十六进制标签的更多信息 + +除了经典的十六进制标签定义外,十六进制数字后面可以跟一个下划线字符。这意味着标签等于指定的十六进制数字,但没有最低有效位。例如,有一个方案: +```tlb +vm_stk_int#0201_ value:int257 = VmStackValue; +``` +实际上,标签并不等于 `0x0201`。为了计算它,我们需要从 `0x0201` 的二进制表示中删除 LSb: + +``` +0000001000000001 -> 000000100000000 +``` + +因此,标签等于 15 位二进制数 `0b000000100000000`。 + +## 字段定义 + +构造函数及其可选标签后面跟着字段定义。每个字段定义的形式为 `ident:type-expr`,其中 ident 是字段名称的标识符(匿名字段用下划线替代),type-expr 是字段的类型。这里提供的类型是类型表达式,可能包括简单类型、带有适当参数的参数化类型或复杂表达式。 + +总之,在类型中定义的所有字段总数不得超过 Cell(`1023` 位和 `4` 引用) + +### 简单类型 + +- `_ a:# = Type;` - 这里 `Type.a` 是 32 位整数 +- `_ a:(## 64) = Type;` - 这里 `Type.a` 是 64 位整数 +- `_ a:Owner = NFT;` - 这里 `NFT.a` 是 `Owner` 类型 +- `_ a:^Owner = NFT;` - 这里 `NFT.a` 是指向 `Owner` 类型的cell引用,意味着 `Owner` 存储在下一个cell引用中。 + +### 匿名字段 + +- `_ _:# = A;` - 第一个字段是匿名的 32 位整数 + +### 通过引用扩展cell + +```tlb +_ a:(##32) ^[ b:(##32) c:(## 32) d:(## 32)] = A; +``` + +- 如果出于某种原因我们想将一些字段分离到另一个cell,我们可以使用 `^[ ... ]` 语法。 + 在这个示例中,`A.a` / `A.b` / `A.c` / `A.d` 是 32 位无符号整数,但 `A.a` 存储在第一个cell中, + 而 `A.b` / `A.c` / `A.d` 存储在下一个cell(1 个引用)中。 + +```tlb +_ ^[ a:(## 32) ^[ b:(## 32) ^[ c:(## 32) ] ] ] = A; +``` + +- 链式引用也是允许的。在这个示例中,每个变量(`a`、`b`、`c`)都存储在分离的cell中。 + +### 参数化类型 + +假设我们有 `IntWithObj` 类型: + +```tlb +_ {X:Type} a:# b:X = IntWithObj X; +``` + +现在我们可以在其他类型中使用它: + +```tlb +_ a:(IntWithObj uint32) = IntWithUint32; +``` + +### 复杂表达式 + +- 条件字段(仅适用于 `Nat`)(`E?T` 表示如果表达式 `E` 为真,则字段类型为 `T`) + ```tlb + _ a:(## 1) b:a?(## 32) = Example; + ``` + 在 `Example` 类型中,变量 `b` 仅在 `a` 为 `1` 时序列化。 + +- 元组创建的乘法表达式(`x * T` 表示创建类型为 `T` 的长度为 `x` 的元组): + ```tlb + a$_ a:(## 32) = A; + b$_ b:(2 * A) = B; + ``` + + ```tlb + _ (## 1) = Bit; + _ 2bits:(2 * Bit) = 2Bits; + ``` + +- 位选择(仅适用于 `Nat`)(`E . B` 表示获取 `Nat` `E` 的位 `B`) + ```tlb + _ a:(## 2) b:(a . 1)?(## 32) = Example; + ``` + 在 `Example` 类型中,变量 `b` 仅在 `a` 的第二个位是 `1` 时序列化。 + +- 其他 `Nat` 运算符也允许(查看 `允许的约束`) + +注意:您可以组合几种复杂表达式: + +```tlb +_ a:(## 1) b:(## 1) c:(## 2) d:(a?(b?((c . 1)?(## 64)))) = A; +``` + +## 内置类型 + +- `#` - `Nat` 32 位无符号整数 +- `## x` - `Nat` 有 `x` 位 +- `#< x` - `Nat` 小于 `x` 位无符号整数,以 `lenBits(x - 1)` 位存储,最多 31 位 +- `#<= x` - `Nat` 小于等于 `x` 位无符号整数,以 `lenBits(x)` 位存储,最多 32 位 +- `Any` / `Cell` - cell剩余的位数和引用 +- `Int` - 257 位 +- `UInt` - 256 位 +- `Bits` - 1023 位 +- `uint1` - `uint256` - 1 - 256 位 +- `int1` - `int257` - 1 - 257 位 +- `bits1` - `bits1023` - 1 - 1023 位 +- `uint X` / `int X` / `bits X` - 与 `uintX` 相同,但您可以在这些类型中使用参数化的 `X` + +## 约束 + +```tlb +_ flags:(## 10) { flags <= 100 } = Flag; +``` + +`Nat` 字段允许在约束中使用。在这个示例中,`{ flags <= 100 }` 约束意味着 `flags` 变量小于等于 `100`。 + +允许的约束:`E` | `E = E` | `E <= E` | `E < E` | `E >= E` | `E > E` | `E + E` | `E * E` | `E ? E` + +## 隐式字段 + +一些字段可能是隐式的。它们的定义被花括号(`{`、`}`)包围,表示该字段实际上并不存在于序列化中,而是必须根据其他数据(通常是正在序列化的类型的参数)推断其值。示例: + +```tlb +nothing$0 {X:Type} = Maybe X; +just$1 {X:Type} value:X = Maybe X; +``` + +```tlb +_ {x:#} a:(## 32) { ~x = a + 1 } = Example; +``` + +## 参数化类型 + +变量 — 即先前定义的字段的(标识符)类型 `#`(自然数)或 `Type`(类型的类型) — 可用作参数化类型的参数。序列化过程递归地根据其类型序列化每个字段,而一个值的序列化最终由表示构造器(即构造器标签)和字段值的位的串联构成。 + +### 自然数(`Nat`) + +```tlb +_ {x:#} my_val:(## x) = A x; +``` + +意味着 `A` 由 `x` `Nat` 参数化。在反序列化过程中,我们将获取 `x` 位无符号整数。例如: + +```tlb +_ value:(A 32) = My32UintValue; +``` + +意味着在 `My32UintValue` 类型的反序列化过程中,我们将获取 32 位无符号整数(因为 `32` 参数应用于 `A` 类型)。 + +### 类型 + +```tlb +_ {X:Type} my_val:(## 32) next_val:X = A X; +``` + +意味着 `A` 由 `X` 类型参数化。在反序列化过程中,我们将获取 32 位无符号整数,然后解析类型 `X` 的 bits&refs。 + +这种参数化类型的使用示例如下: + +```tlb +_ bit:(## 1) = Bit; +_ 32intwbit:(A Bit) = 32IntWithBit; +``` + +在这个示例中,我们将类型 `Bit` 作为参数传递给 `A`。 + +如果您不想定义类型,但想按照这个方案进行反序列化,可以使用 `Any` 关键词: + +```tlb +_ my_val:(A Any) = Example; +``` + +意味着如果我们反序列化 `Example` 类型,我们将获取 32 位整数,然后将cell的剩余部分(bits&refs)传递给 `my_val`。 + +您可以创建带有多个参数的复杂类型: + +```tlb +_ {X:Type} {Y:Type} my_val:(## 32) next_val:X next_next_val:Y = A X Y; +_ bit:(## 1) = Bit; +_ a_with_two_bits:(A Bit Bit) = AWithTwoBits; +``` + +您也可以对此类参数化类型进行部分应用: + +```tlb +_ {X:Type} {Y:Type} v1:X v2:Y = A X Y; +_ bit:(## 1) = Bit; +_ {X:Type} bits:(A Bit X) = BitA X; +``` + +甚至可以对参数化类型本身进行参数化: + +```tlb +_ {X:Type} v1:X = A X; +_ {X:Type} d1:X = B X; +_ {X:Type} bits:(A (B X)) = AB X; +``` + +### 在参数化类型中使用 NAT 字段 + +您可以像参数那样使用先前定义的字段。序列化将在运行时确定。 + +一个简单的示例: + +```tlb +_ a:(## 8) b:(## a) = A; +``` + +这意味着我们在 `a` 字段内存储了 `b` 字段的大小。因此,当我们想序列化类型 `A` 时,需要加载 `a` 字段的 8 位无符号整数,然后使用这个数字来确定 `b` 字段的大小。 + +这种策略也适用于参数化类型: + +```tlb +_ {input:#} c:(## input) = B input; +_ a:(## 8) c_in_b:(B a) = A; +``` + +### 在参数化类型中使用表达式 + +```tlb +_ {x:#} value:(## x) = Example (x * 2); +_ _:(Example 4) = 2BitInteger; +``` + +在这个示例中,`Example.value` 类型在运行时确定。 + +在 `2BitInteger` 定义中,我们设置了 `Example 4` 类型。要确定这个类型,我们使用 `Example (x * 2)` 定义,并根据公式计算 `x`(`y = 2`,`z = 4`): + +```c++ +static inline bool mul_r1(int& x, int y, int z) { + return y && !(z % y) && (x = z / y) >= 0; +} +``` + +我们还可以使用加法运算符: + +```tlb +_ {x:#} value:(## x) = Example + +Sum (x + 3); +_ _:(ExampleSum 4) = 1BitInteger; +``` + +在 `1BitInteger` 定义中,我们设置了 `ExampleSum 4` 类型。要确定这个类型,我们使用 `ExampleSum (x + 3)` 定义,并根据公式计算 `x`(`y = 3`,`z = 4`): + +```c++ +static inline bool add_r1(int& x, int y, int z) { + return z >= y && (x = z - y) >= 0; +} +``` + +## 取反运算符(`~`) + +一些“变量”的出现(即已定义的字段)前面加上波浪号(`~`)。这表明该变量的出现与默认行为相反:在等式的左侧,它意味着该变量将基于此出现推断(计算),而不是替代其先前计算的值;在右侧相反,它意味着该变量不会从正在序列化的类型中推断出来,而是将在反序列化过程中计算。换句话说,波浪号将“输入参数”转换为“输出参数”,反之亦然。 + +取反运算符的一个简单示例是基于另一个变量定义新变量: + +```tlb +_ a:(## 32) { b:# } { ~b = a + 100 } = B_Calc_Example; +``` + +定义后,您可以将新变量用于传递给 `Nat` 类型: + +```tlb +_ a:(## 8) { b:# } { ~b = a + 10 } + example_dynamic_var:(## b) = B_Calc_Example; +``` + +`example_dynamic_var` 的大小将在运行时计算,当我们加载 `a` 变量并使用它的值来确定 `example_dynamic_var` 的大小。 + +或者传递给其他类型: + +```tlb +_ {X:Type} a:^X = PutToRef X; +_ a:(## 32) { b:# } { ~b = a + 100 } + my_ref: (PutToRef b) = B_Calc_Example; +``` + +您还可以在加法或乘法复杂表达式中使用带有取反运算符的变量定义: + +```tlb +_ a:(## 32) { b:# } { ~b + 100 = a } = B_Calc_Example; +``` + +```tlb +_ a:(## 32) { b:# } { ~b * 5 = a } = B_Calc_Example; +``` + +### 在类型定义中使用取反运算符(`~`) + +```tlb +_ {m:#} n:(## m) = Define ~n m; +_ {n_from_define:#} defined_val:(Define ~n_from_define 8) real_value:(## n_from_define) = Example; +``` + +假设我们有一个类 `Define ~n m`,它接受 `m` 并从 `m` 位无符号整数中加载 `n`。 + +在 `Example` 类型中,我们将 `Define` 类型计算出的变量存储到 `n_from_define` 中,我们也知道它是 8 位无符号整数,因为我们应用了 `Define ~n_from_define 8` 类型。现在我们可以在其他类型中使用 `n_from_define` 变量来确定序列化过程。 + +这种技术可以导致更复杂的类型定义(如联合体、哈希映射)。 + +```tlb +unary_zero$0 = Unary ~0; +unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); +_ u:(Unary Any) = UnaryChain; +``` + +这个示例在 [TL-B 类型](https://docs.ton.org/develop/data-formats/tl-b-types#unary) 文章中有很好的解释。主要思想是 `UnaryChain` 将递归反序列化,直到达到 `unary_zero$0`(因为我们通过定义 `unary_zero$0 = Unary ~0;` 和 `X` 在运行时由 `Unary ~(n + 1)` 定义计算得知 `Unary X` 类型的最后一个元素)。 + +注意:`x:(Unary ~n)` 意味着 `n` 在序列化 `Unary` 类的过程中被定义。 + +## 特殊类型 + +目前,TVM允许以下cell类型: + +- 普通(Ordinary) +- 裁剪分支(Prunned Branch) +- 库引用(Library Reference) +- Merkle证明(Merkle Proof) +- Merkle更新(Merkle Update) + +默认情况下,所有cell都是`普通`类型。并且在tlb中描述的所有cell都是`普通`类型。 + +要在构造函数中允许加载特殊类型,需要在构造函数前添加`!`。 + +示例: + +```tlb +!merkle_update#02 {X:Type} old_hash:bits256 new_hash:bits256 + old:^X new:^X = MERKLE_UPDATE X; + +!merkle_proof#03 {X:Type} virtual_hash:bits256 depth:uint16 virtual_root:^X = MERKLE_PROOF X; +``` + +这种技术允许在打印结构时标记`SPECIAL`cell的代码生成代码,也允许正确验证带有特殊cell的结构。 + +## 不检查构造函数唯一性标签的同一类型多个实例 + +允许仅根据类型参数创建同一类型的多个实例。在这种定义方式下,不会应用构造函数标签唯一性检查。 + +示例: + +```tlb +_ = A 1; +a$01 = A 2; +b$01 = A 3; +_ test:# = A 4; +``` + +意味着实际的反序列化标签将由`A`类型参数确定: + +```python3 +# class for type `A` +class A(TLBComplex): + class Tag(Enum): + a = 0 + b = 1 + cons1 = 2 + cons4 = 3 + + cons_len = [2, 2, 0, 0] + cons_tag = [1, 1, 0, 0] + + m_: int = None + + def __init__(self, m: int): + self.m_ = m + + def get_tag(self, cs: CellSlice) -> Optional["A.Tag"]: + tag = self.m_ + + if tag == 1: + return A.Tag.cons1 + + if tag == 2: + return A.Tag.a + + if tag == 3: + return A.Tag.b + + if tag == 4: + return A.Tag.cons4 + + return None +``` + +带有多个参数的情况也是一样的: + +```tlb +_ = A 1 1; +a$01 = A 2 1; +b$01 = A 3 3; +_ test:# = A 4 2; +``` + +请记住,当您添加参数化类型定义时,预定义类型定义(如我们示例中的 `a` 和 `b`)和参数化类型定义(如我们示例中的 `c`)之间的标签必须是唯一的: + +*无效示例:* + +``` +a$01 = A 2 1; +b$11 = A 3 3; +c$11 {X:#} {Y:#} = A X Y; +``` + +*有效示例:* + +```tlb +a$01 = A 2 1; +b$01 = A 3 3; +c$11 {X:#} {Y:#} = A X Y; +``` + +## 注释 + +注释与C++中的相同: + +```tlb +/* +这是 +一个注释 +*/ + +// 这是单行注释 +``` + +## IDE 支持 + +[intellij-ton](https://github.com/andreypfau/intellij-ton) 插件支持 Fift、FunC 以及 TL-B。 +TL-B 语法在 [TlbParser.bnf](https://github.com/ton-blockchain/intellij-ton/blob/main/src/main/grammar/TlbParser.bnf) 文件中描述。 + +## 有用的资源 + +- [TL的旧版本描述](https://core.telegram.org/mtproto/TL) +- [block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) +- [tlbc 工具](https://github.com/ton-blockchain/ton/blob/master/crypto/tl/tlbc.cpp) +- [CPP 代码生成](https://github.com/ton-blockchain/ton/blob/master/crypto/tl/tlbc-gen-cpp.cpp) +- [tonpy tlb 测试](https://github.com/disintar/tonpy/blob/main/src/tonpy/tests/test_tlb.py) +- [tonpy py 代码生成](https://github.com/disintar/ton/blob/master/crypto/tl/tlbc-gen-py.cpp) + +
+ +文档由 [Disintar](https://dton.io/) 团队提供。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-types.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-types.mdx new file mode 100644 index 0000000000..de77ad5b38 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tl-b-types.mdx @@ -0,0 +1,353 @@ +import ThemedImage from '@theme/ThemedImage'; + +# TL-B 类型 +:::caution advanced level +此信息属于**非常底层**的内容,对新手来说可能难以理解。 +因此,您可以稍后再阅读。 +::: + +在本节中,我们将分析复杂和非传统的类型化语言二进制(TL-B)结构。开始之前,我们建议先阅读[此文档](/develop/data-formats/tl-b-language),以更熟悉该主题。 + +tlb structure + +## Either +```tlb +left$0 {X:Type} {Y:Type} value:X = Either X Y; +right$1 {X:Type} {Y:Type} value:Y = Either X Y; +``` +Either类型用于可能出现两种结果类型之一的情况。在这种情况下,类型选择取决于所示的前缀位。如果前缀位是0,则序列化左类型,而如果使用1前缀位,则序列化右类型。 + +例如,在序列化消息时使用它,当消息体要么是主cell的一部分,要么链接到另一个cell。 + +## Maybe +```tlb +nothing$0 {X:Type} = Maybe X; +just$1 {X:Type} value:X = Maybe X; +``` +Maybe类型与可选值一起使用。在这些情况下,如果第一位是0,则不会序列化该值(实际上被跳过),而如果值是1,则会被序列化。 + +## Both +```tlb +pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y; +``` +Both类型变体仅与普通对一起使用,两种类型依次序列化,没有条件。 + +## Unary +Unary函数类型通常用于动态大小的结构,例如[hml_short](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L29)。 + +Unary提供两个主要选项: + +```tlb +unary_zero$0 = Unary ~0; +unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); +``` +### Unary序列化 + +通常,使用`unary_zero`变体相当简单:如果第一位是0,则Unary反序列化的结果为0。 + +然而,`unary_succ`变体更复杂,因为它是递归加载的,具有`~(n + 1)`的值。这意味着它会顺序调用自身,直到达到`unary_zero`。换句话说,所需的值将等于一连串单位的数量。 + +例如,让我们分析位串`110`的序列化。 + +调用链如下: +```tlb +unary_succ$1 -> unary_succ$1 -> unary_zero$0 +``` + +一旦到达`unary_zero`,值就会返回到序列化位串的末尾,类似于递归函数调用。 + +现在,为了更清楚地理解结果,让我们检索返回值路径,显示如下: + +```0 -> ~(0 + 1) -> ~(1 + 1) -> 2```,这意味着我们将`110`序列化为`Unary 2`。 + +### Unary反序列化 + +假设我们有`Foo`类型: +```tlb +foo$_ u:(Unary 2) = Foo; +``` + +根据上述内容,`Foo`将被反序列化为: + + +

+ +

+ + +```tlb +foo u:(unary_succ x:(unary_succ x:(unnary_zero))) +``` + +## Hashmap + +Hashmap复杂类型用于存储FunC智能合约代码中的字典(`dict`)。 + +以下TL-B结构用于序列化具有固定键长度的Hashmap: + +```tlb +hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) + {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; + +hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; +hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) + right:^(Hashmap n X) = HashmapNode (n + 1) X; + +hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; +hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; +hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; + +unary_zero$0 = Unary ~0; +unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); + +hme_empty$0 {n:#} {X:Type} = HashmapE n X; +hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X; +``` + +这意味着根结构使用`HashmapE`及其两种状态之一:包括`hme_empty`或`hme_root`。 + +### Hashmap解析示例 + +例如,考虑以下以二进制形式给出的Cell。 +```json +1[1] -> { + 2[00] -> { + 7[1001000] -> { + 25[1010000010000001100001001], + 25[1010000010000000001101111] + }, + 28[1011100000000000001100001001] + } +} +``` + +此Cell使用了`HashmapE`结构类型,并具有8位键大小,其值使用`uint16`数字框架(`HashmapE 8 uint16`)。HashmapE使用3种不同的键类型: + +``` +1 = 777 +17 = 111 +128 = 777 +``` + +为了解析此Hashmap,我们需要预先知道使用哪种结构类型,`hme_empty`还是`hme_root`。这是通过识别`正确前缀`来确定的。hme empty变体使用一个位0(`hme_empty$0`),而hme root使用一个位1(`hme_root$1`)。读取第一位后,确定它等于一(`1[1]`),意味着它是`hme_root`变体。 + +现在,让我们用已知值填充结构变量,初始结果为: +`hme_root$1 {n:#} {X:Type} root:^(Hashmap 8 uint16) = HashmapE 8 uint16;` + +这里已经读取了一位前缀,而`{}`中的条件不需要读取。条件`{n:#}`表示n是任何uint32数字,而`{X:Type}`表示X可以使用任何类型。 + +接下来需要读取的部分是`root:^(Hashmap 8 uint16)`,而`^`符号表示必须加载的链接。 + +```json +2[00] -> { + 7[1001000] -> { + 25[1010000010000001100001001], + 25[1010000010000000001101111] + }, + 28[1011100000000000001100001001] + } +``` +#### 初始化分支解析 + +根据我们的架构,这是正确的`Hashmap 8 uint16`结构。接下来,我们用已知的值填充它,并得到如下结果: + +```tlb +hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l 8) + {8 = (~m) + l} node:(HashmapNode m uint16) = Hashmap 8 uint16; +``` + +如上所示,现在出现了条件变量`{l:#}`和`{m:#}`,但我们不知道这两个变量的值。此外,在读取相应的`label`之后,很明显`n`涉及等式`{n = (~m) + l}`,在这种情况下我们计算`l`和`m`,符号`~`告诉我们结果值。 + +为了确定`l`的值,我们必须加载`label:(HmLabel ~l uint16)`序列。如下所示,`HmLabel`有三种基本结构选项: + +```tlb +hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; +hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; +hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; +``` + +每个选项都由相应的前缀确定。目前,我们的根cell由两个零位组成,显示为:(`2[00]`)。因此,唯一合理的选项是以0开头的前缀`hml_short$0`。 + +用已知值填充`hml_short`: +```tlb +hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= 8} s:(n * Bit) = HmLabel ~n 8 +``` +在这种情况下,我们不知道`n`的值,但由于它有一个`~`字符,可以计算它。为此,我们加载`len:(Unary ~n)`,[关于Unary的更多信息](#unary)。 + +在这种情况下,我们从`2[00]`开始,但在定义了`HmLabel`类型之后,两个位中只剩下一个。 + +因此,我们加载它并看到它的值是0,这意味着它显然使用了`unary_zero$0`变体。这意味着使用`HmLabel`变体的n值为零。 + +接下来,需要使用计算出的n值完成`hml_short`变体序列: + +```tlb +hml_short$0 {m:#} {n:#} len:0 {n <= 8} s:(0 * Bit) = HmLabel 0 8 +``` +结果我们得到一个空的`HmLabel`,表示为s = 0,因此没有什么可以下载的。 + +接下来,我们用计算出的`l`值补充我们的结构,如下所示: + +```tlb +hm_edge#_ {n:#} {X:Type} {l:0} {m:#} label:(HmLabel 0 8) + {8 = (~m) + 0} node:(HashmapNode m uint16) = Hashmap 8 uint16; +``` + +现在我们已经计算出`l`的值,我们可以使用等式`n = (~m) + 0`来计算`m`,即`m = n - 0`,m = n = 8。 + +确定所有未知值后,现在可以加载`node:(HashmapNode 8 uint16)`。 + +至于HashmapNode,我们有以下选项: +```tlb +hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; +hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) + right:^(Hashmap n X) = HashmapNode (n + 1) X; +``` + +在这种情况下,我们不 + +是通过前缀来确定选项,而是通过参数来确定。这意味着如果n = 0,则正确的最终结果将是`hmn_leaf`或`hmn_fork`。在这个例子中,结果是n = 8(`hmn_fork`变体)。我们使用`hmn_fork`变体,并填入已知的值: + +```tlb +hmn_fork#_ {n:#} {X:uint16} left:^(Hashmap n uint16) + right:^(Hashmap n uint16) = HashmapNode (n + 1) uint16; +``` + +输入已知值后,我们必须计算`HashmapNode (n + 1) uint16`。这意味着n的结果值必须等于我们的参数,即8。 +要计算本地n值,我们需要使用以下公式计算:`n = (n_local + 1)` -> `n_local = (n - 1)` -> `n_local = (8 - 1)` -> `n_local = 7`。 + +```tlb +hmn_fork#_ {n:#} {X:uint16} left:^(Hashmap 7 uint16) + right:^(Hashmap 7 uint16) = HashmapNode (7 + 1) uint16; +``` +现在我们知道了上述公式是必需的,获取最终结果就很简单了。 +接下来我们加载左右分支,并对每个后续分支[重复该过程](#initiating-branch-parsing)。 + +#### 分析加载的Hashmap值 +继续前面的例子,让我们来看看加载分支的过程是如何工作的(对于dict值),即`28[1011100000000000001100001001]` + +最终结果再次成为`hm_edge`,接下来的步骤是按照以下方式用正确的已知值填充序列: +```tlb +hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l 7) + {7 = (~m) + l} node:(HashmapNode m uint16) = Hashmap 7 uint16; +``` + +接下来使用`HmLabel`变体加载`HmLabel`响应,因为前缀是`10`。 +```tlb +hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; +``` +现在,让我们填充序列: +```tlb +hml_long$10 {m:#} n:(#<= 7) s:(n * Bit) = HmLabel ~n 7; +``` +新构造 - `n:(#<= 7)`,清楚地表示一个与数字7对应的大小值,实际上是这个数字+1的log2。但为了简单起见,我们可以计算写数字7需要的位数。 +相关地,数字7的二进制形式是`111`;因此需要3位,意味着`n = 3`的值。 +```tlb +hml_long$10 {m:#} n:(## 3) s:(n * Bit) = HmLabel ~n 7; +``` +接下来我们将`n`加载到序列中,最终结果为`111`,如上所述,这与7相符。接下来,我们将`s`加载到序列中,7位 - `0000000`。记住,`s`是键的一部分。 + +接下来我们回到序列的顶部并填充结果的`l`: +```tlb +hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel 7 7) + {7 = (~m) + 7} node:(HashmapNode m uint16) = Hashmap 7 uint16; +``` + +然后我们计算`m`的值,`m = 7 - 7`,因此`m`的值为0。 +由于`m`的值为0,该结构非常适合用于HashmapNode: +```tlb +hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; +``` + +接下来我们替换我们的uint16类型并加载值。剩余的16位`0000001100001001`以十进制形式为777,因此这就是我们的值。 + +现在让我们恢复键,我们必须将之前计算的所有键部分的有序列表组合起来。两个相关的键部分根据使用的分支类型合并为一位。对于右分支,添加‘1’位,对于左分支添加‘0’位。如果上面有完整的HmLabel,则将其位添加到键中。 + +在这个特定的例子中,从HmLabel中取出7位`0000000`,并在零序列之前添加‘1’位,因为该值是从右分支获得的。最终结果是总共8位或`10000000`,意味着键值等于`128`。 + +## 其他Hashmap类型 +现在我们已经讨论了Hashmaps及如何加载标准化的Hashmap类型,让我们解释其他Hashmap类型是如何工作的。 + +### HashmapAugE +```tlb +ahm_edge#_ {n:#} {X:Type} {Y:Type} {l:#} {m:#} + label:(HmLabel ~l n) {n = (~m) + l} + node:(HashmapAugNode m X Y) = HashmapAug n X Y; + +ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y; + +ahmn_fork#_ {n:#} {X:Type} {Y:Type} left:^(HashmapAug n X Y) + right:^(HashmapAug n X Y) extra:Y = HashmapAugNode (n + 1) X Y; + +ahme_empty$0 {n:#} {X:Type} {Y:Type} extra:Y + = HashmapAugE n X Y; + +ahme_root$1 {n:#} {X:Type} {Y:Type} root:^(HashmapAug n X Y) + extra:Y = HashmapAugE n X Y; +``` +`HashmapAugE`与普通`Hashmap`的主要区别在于每个节点(不仅仅是带有值的叶子)都有一个`extra:Y`字段。 + +### PfxHashmap +```tlb +phm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) + {n = (~m) + l} node:(PfxHashmapNode m X) + = PfxHashmap n X; + +phmn_leaf$0 {n:#} {X:Type} value:X = PfxHashmapNode n X; +phmn_fork$1 {n:#} {X:Type} left:^(PfxHashmap n X) + right:^(PfxHashmap n X) = PfxHashmapNode (n + 1) X; + +phme_empty$0 {n:#} {X:Type} = PfxHashmapE n X; +phme_root$1 {n:#} {X:Type} root:^(PfxHashmap n X) + = PfxHashmapE n X; +``` +PfxHashmap与普通Hashmap的主要区别在于它能够存储不同键长,这是由于存在`phmn_leaf$0`和`phmn_fork$1`节点。 + +### VarHashmap +```tlb +vhm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) + {n = (~m) + l} node:(VarHashmapNode m X) + = VarHashmap n X; +vhmn_leaf$00 {n:#} {X:Type} value:X = VarHashmapNode n X; +vhmn_fork$01 {n:#} {X:Type} left:^(VarHashmap n X) + right:^(VarHashmap n X) value:(Maybe X) + = VarHashmapNode (n + 1) X; +vhmn_cont$1 {n:#} {X:Type} branch:Bit child:^(VarHashmap n X) + value:X = VarHashmapNode (n + 1) X; + +// nothing$0 {X:Type} = Maybe X; +// just$1 {X:Type} value + +:X = Maybe X; + +vhme_empty$0 {n:#} {X:Type} = VarHashmapE n X; +vhme_root$1 {n:#} {X:Type} root:^(VarHashmap n X) + = VarHashmapE n X; +``` +VarHashmap与普通Hashmap的主要区别在于它能够存储不同键长,这是由于存在`vhmn_leaf$00`和`vhmn_fork$01`节点。此外,VarHashmap能够在`vhmn_cont$1`的代价下形成共同值前缀(子映射)。 + +### BinTree +```tlb +bta_leaf$0 {X:Type} {Y:Type} extra:Y leaf:X = BinTreeAug X Y; +bta_fork$1 {X:Type} {Y:Type} left:^(BinTreeAug X Y) + right:^(BinTreeAug X Y) extra:Y = BinTreeAug X Y; +``` +二叉树键生成机制与标准化Hashmap框架类似,但不使用标签,只包括分支前缀。 + + +## 地址 +TON地址是使用TL-B StateInit结构通过sha256哈希机制形成的。这意味着地址可以在网络合约部署之前计算出来。 + +### 序列化 +标准地址,例如`EQBL2_3lMiyywU17g-or8N7v9hDmPCpttzBPE2isF2GTzpK4`,使用base64 uri对字节进行编码。通常它们长度为36字节,其中最后两个字节是使用XMODEM表计算的crc16校验和,而第一个字节表示标志,第二个表示工作链。中间的32字节是地址本身的数据(也称为AccountID),通常在诸如int256之类的架构中表示。 + +[解码示例](https://github.com/xssnick/tonutils-go/blob/3d9ee052689376061bf7e4a22037ff131183afad/address/addr.go#L156) + +## 参考 + +_这里是[Oleg Baranov](https://github.com/xssnick)的[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/TL-B.md)。_ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tlb-ide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tlb-ide.md new file mode 100644 index 0000000000..2499f05dc0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/tlb-ide.md @@ -0,0 +1,17 @@ +# IDE 支持 + +### 强调 +[intellij-ton](https://github.com/andreypfau/intellij-ton) 插件支持 Fift 和 FunC 编程语言以及类型化语言二进制(TL-B)格式。 + +此外,正确的 TL-B 语法规范在 [TlbParser.bnf](https://github.com/andreypfau/intellij-ton/blob/main/src/main/grammars/TlbParser.bnf) 文件中有描述。 + +### TL-B 解析器 + +TL-B 解析器有助于执行基本 [TL-B 类型](/develop/data-formats/tl-b-types) 的序列化。每种类型都以对象的形式实现 TL-B 类型,并返回序列化的二进制数据。 + +| 语言 | SDK | 社交网址 | +|-------------|-------------------------------------------------------------------------------------------------------------|------------------------| +| Kotlin | [ton-kotlin](https://github.com/andreypfau/ton-kotlin/tree/main/ton-kotlin-tlb)(+ 解析 `.tlb` 文件) | https://t.me/tonkotlin | +| Go | [tonutils](https://github.com/xssnick/tonutils-go/tree/master/tlb) | https://t.me/tonutils | +| Go | [tongo](https://github.com/tonkeeper/tongo/tree/master/tlb)(+ 解析 `.tlb` 文件) | https://t.me/tongo_lib | +| TypeScript | [tlb-parser](https://github.com/ton-community/tlb-parser) | - | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/transaction-layout.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/transaction-layout.md new file mode 100644 index 0000000000..fc7750ad3c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/data-formats/tlb/transaction-layout.md @@ -0,0 +1,236 @@ +# 交易布局 + +:::info +为了最大限度地理解这个页面,强烈建议您熟悉[TL-B 语言](/develop/data-formats/cell-boc)。 +::: + +TON 区块链运作依赖于三个关键部分:账户、消息和交易。本页面描述了交易的结构和布局。 + +交易是一种操作,处理与特定账户相关的进出消息,改变其状态,并可能为验证者生成费用。 + +## 交易 + +```tlb +transaction$0111 account_addr:bits256 lt:uint64 + prev_trans_hash:bits256 prev_trans_lt:uint64 now:uint32 + outmsg_cnt:uint15 + orig_status:AccountStatus end_status:AccountStatus + ^[ in_msg:(Maybe ^(Message Any)) out_msgs:(HashmapE 15 ^(Message Any)) ] + total_fees:CurrencyCollection state_update:^(HASH_UPDATE Account) + description:^TransactionDescr = Transaction; +``` + +| 字段 | 类型 | 必需 | 描述 | +| ------------------- | ---------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `account_addr` | bits256 | 是 | 执行交易的地址的哈希部分。[更多关于地址](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) | +| `lt` | uint64 | 是 | 代表 _逻辑时间_。[更多关于逻辑时间](https://docs.ton.org/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time) | +| `prev_trans_hash` | bits256 | 是 | 该账户上一个交易的哈希。 | +| `prev_trans_lt` | uint64 | 是 | 该账户上一个交易的 `lt`。 | +| `now` | uint32 | 是 | 执行此交易时设置的 `now` 值。它是以秒为单位的Unix时间戳。 | +| `outmsg_cnt` | uint15 | 是 | 执行此交易时创建的输出消息数量。 | +| `orig_status` | [AccountStatus](#accountstatus) | 是 | 执行交易前该账户的状态。 | +| `end_status` | [AccountStatus](#accountstatus) | 是 | 执行交易后该账户的状态。 | +| `in_msg` | (Message Any) | 否 | 触发执行交易的输入消息。存储在一个引用中。 | +| `out_msgs` | HashmapE 15 ^(Message Any) | 是 | 包含执行此交易时创建的输出消息列表的字典。 | +| `total_fees` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | 是 | 执行此交易时收集的总费用。它包括_Toncoin_值和可能的一些[额外代币](https://docs.ton.org/develop/dapps/defi/coins#extra-currencies)。 | +| `state_update` | [HASH_UPDATE](#hash_update) Account | 是 | `HASH_UPDATE` 结构。存储在一个引用中。 | +| `description` | [TransactionDescr](#transactiondescr-types) | 是 | 交易执行过程的详细描述。存储在一个引用中。 | + +## AccountStatus + +```tlb +acc_state_uninit$00 = AccountStatus; +acc_state_frozen$01 = AccountStatus; +acc_state_active$10 = AccountStatus; +acc_state_nonexist$11 = AccountStatus; +``` + +- `[00]`:账户未初始化 +- `[01]`:账户被冻结 +- `[10]`:账户活跃 +- `[11]`:账户不存在 + +## HASH_UPDATE + +```tlb +update_hashes#72 {X:Type} old_hash:bits256 new_hash:bits256 + = HASH_UPDATE X; +``` + +| 字段 | 类型 | 描述 | +| ----------- | ------- + + | ------------------------------------------------- | +| `old_hash` | bits256 | 执行交易前账户状态的哈希。 | +| `new_hash` | bits256 | 执行交易后账户状态的哈希。 | + +## TransactionDescr 类型 + +- [普通](#ordinary) +- [存储](#storage) +- [Tick-tock](#tick-tock) +- [拆分准备](#split-prepare) +- [拆分安装](#split-install) +- [合并准备](#merge-prepare) +- [合并安装](#merge-install) + +## 普通 + +这是最常见的交易类型,满足大多数开发人员的需求。此类交易恰好有一个输入消息,并且可以创建多个输出消息。 + +```tlb +trans_ord$0000 credit_first:Bool + storage_ph:(Maybe TrStoragePhase) + credit_ph:(Maybe TrCreditPhase) + compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) + aborted:Bool bounce:(Maybe TrBouncePhase) + destroyed:Bool + = TransactionDescr; +``` + +| 字段 | 类型 | 必需 | 描述 | +| --------------- | -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `credit_first` | Bool | 是 | 与输入消息的 `bounce` 标志相关的标志位。`credit_first = !bounce` | +| `storage_ph` | TrStoragePhase | 否 | 包含有关交易执行的 Storage Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `credit_ph` | TrCreditPhase | 否 | 包含有关交易执行的 Credit Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `compute_ph` | TrComputePhase | 是 | 包含有关交易执行的 Compute Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `action` | TrActionPhase | 否 | 包含有关交易执行的 Action Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases)。存储在一个引用中。 | +| `aborted` | Bool | 是 | 表明交易执行是否被中止。 | +| `bounce` | TrBouncePhase | 否 | 包含有关交易执行的 Bounce Phase 的信息。[更多信息](https://docs.ton.org/develop/smart-contracts/guidelines/non-bouncable-messages) | +| `destroyed` | Bool | 是 | 表明在执行期间账户是否被销毁。 | + +## 存储 + +此类交易可由验证者酌情插入。它们不处理任何输入消息,也不调用任何代码。它们唯一的效果是从一个账户收集存储支付,影响其存储统计和余额。如果账户的结果 _Toncoin_ 余额低于某个数量,账户可能被冻结,其代码和数据被它们的组合哈希所替代。 + +```tlb +trans_storage$0001 storage_ph:TrStoragePhase + = TransactionDescr; +``` + +| 字段 | 类型 | 描述 | +| ------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `storage_ph` | TrStoragePhase | 包含有关交易执行的存储阶段的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | + +## Tick-tock + +`Tick` 和 `Tock` 交易保留给特殊的系统智能合约,这些合约需要在每个区块中自动调用。`Tick` 交易在每个主链区块的开始时调用,而 `Tock` 交易在结束时调用。 + +```tlb +trans_tick_tock$001 is_tock:Bool storage_ph:TrStoragePhase + compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) + aborted:Bool destroyed:Bool = TransactionDescr; +``` + +| 字段 | 类型 | 必需 | 描述 | +| ------------- | -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `is_tock` | Bool | 是 | 表明交易类型的标志。用于区分 `Tick` 和 `Tock` 交易 | +| `storage_ph` | TrStoragePhase | 是 | 包含有关交易执行的 Storage Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `compute_ph` | TrComputePhase | 是 | 包含有关交易执行的 Compute Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `action` | TrActionPhase | 否 | 包含有关交易执行的 Action Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases)。存储在一个引用中。 | +| `aborted` | Bool | 是 | 表明交易执行是否被中止。 | +| `destroyed` | Bool | 是 | 表明在执行期间账户是否被销毁。 | + +## 拆分准备 + +:::note +目前此类型的交易未被使用。关于此过程的信息有限。 +::: + +在需要因高负载而拆分的大型智能合约上启动拆分交易。合约应支持此类型的交易并管理拆分过程以平衡负载。 + +当需要拆分智能合约时,将启动拆分准备交易。智能合约应生成一个新实例的状态,以便部署。 + +```tlb +trans_split_prepare$0100 split_info:SplitMergeInfo + storage_ph:(Maybe TrStoragePhase) + compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) + aborted:Bool destroyed:Bool + = TransactionDescr; +``` + +| 字段 | 类型 | 必需 | 描述 | +| ------------- | -------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `split_info` | SplitMergeInfo | 是 | 拆分过程的信息。 | +| `storage_ph` | TrStoragePhase | 否 | 包含有关交易执行的 Storage Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `compute_ph` | TrComputePhase | 是 | 包含有关交易执行的 Compute Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `action` | TrActionPhase | 否 | 包含有关交易执行的 Action Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases)。存储在一个引用中。 | +| `aborted` | Bool | 是 | 表明交易执行是否被中止。 | +| `destroyed` | Bool | 是 | 表明在执行期间账户是否被销毁。 | + +## 拆分安装 + +:::note +目前此类型的交易未被使用。关于此过程的信息有限。 +::: + +拆分安装交易用于创建大型智能合约的新实例。新智能合约的状态由[拆分准备](#split-prepare)交易生成。 + +```tlb +trans_split_install$0101 split_info:SplitMergeInfo + prepare_transaction:^Transaction + installed:Bool = TransactionDescr; +``` + +| 字段 | 类型 | 描述 | +| --------------------- | --------------------------- | --------------------------------------------------------------------------------------------------- | +| `split_info` | SplitMergeInfo | 拆分过程的信息。 | +| `prepare_transaction` | [Transaction](#transaction) | 有关为拆分操作准备的[交易](#split-prepare)的信息。存储在一个引用中。 | +| `installed` | Bool | 表明交易是否已安装。 | + +## 合并准备 + +:::note +目前此类型的交易未被使用。关于此过程的信息有限。 +::: + +在需要因高负载而重新组合的大型智能合约上启动合并交易。合约应支持此类型的交易并管理合并过程以平衡负载。 + +当两个智能合约需要合并时,将启动合并准备交易。智能合约应生成一个信息,以便与另一个实例合并。 + +```tlb +trans_merge_prepare$0110 split_info:SplitMergeInfo + storage_ph:TrStoragePhase aborted:Bool + = TransactionDescr; +``` + +| 字段 | 类型 | 描述 | +| ------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `split_info` | SplitMergeInfo | 合并过程的信息。 | +| `storage_ph` | TrStoragePhase | 包含有关交易执行的 Storage Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) + + | +| `aborted` | Bool | 表明交易执行是否被中止。 | + +## 合并安装 + +:::note +目前此类型的交易未被使用。关于此过程的信息有限。 +::: + +合并安装交易用于合并大型智能合约的实例。促进合并的特殊信息由[合并准备](#merge-prepare)交易生成。 + +```tlb +trans_merge_install$0111 split_info:SplitMergeInfo + prepare_transaction:^Transaction + storage_ph:(Maybe TrStoragePhase) + credit_ph:(Maybe TrCreditPhase) + compute_ph:TrComputePhase action:(Maybe ^TrActionPhase) + aborted:Bool destroyed:Bool + = TransactionDescr; +``` + +| 字段 | 类型 | 必需 | 描述 | +| --------------------- | --------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `split_info` | SplitMergeInfo | 是 | 合并过程的信息。 | +| `prepare_transaction` | [Transaction](#transaction) | 是 | 有关为合并操作准备的[交易](#merge-prepare)的信息。存储在一个引用中。 | +| `storage_ph` | TrStoragePhase | 否 | 包含有关交易执行的 Storage Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `credit_ph` | TrCreditPhase | 否 | 包含有关交易执行的 Credit Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `compute_ph` | TrComputePhase | 是 | 包含有关交易执行的 Compute Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases) | +| `action` | TrActionPhase | 否 | 包含有关交易执行的 Action Phase 的信息。[更多信息](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases)。存储在一个引用中。 | +| `aborted` | Bool | 是 | 表明交易执行是否被中止。 | +| `destroyed` | Bool | 是 | 表明在执行期间账户是否被销毁。 | + +## 参阅 + +- 原始描述 [交易布局](https://ton.org/docs/tblkch.pdf#page=75&zoom=100,148,290) 来自白皮书 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/faq.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/faq.md index 4a60218a7a..dd75f8c7fb 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/faq.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/faq.md @@ -1,84 +1,56 @@ -# Frequently Asked Questions +# 常见问题解答 -This section covers the most popular questions about TON Blockchain. +本节涵盖了关于TON区块链最受欢迎的问题。 -## Overview +## 概述 -### Could you share a brief overview of TON? +### 能分享一下关于 TON 的简要概述吗? -- [Introduction to The Open Network](/v3/concepts/dive-into-ton/introduction) -- [The TON Blockchain is based on PoS consensus](https://blog.ton.org/the-ton-blockchain-is-based-on-pos-consensus) -- [TON Whitepapers](/v3/documentation/whitepapers/overview) +- [The Open Network简介](/learn/introduction) +- [TON区块链基于PoS共识](https://blog.ton.org/the-ton-blockchain-is-based-on-pos-consensus) +- [TON白皮书](/learn/docs) -### What are some of the main similarities and differences to EVM blockchains? +### TON 与 EVM 区块链的主要相似之处和不同之处是什么? -- [Ethereum to TON](/v3/concepts/dive-into-ton/introduction#ethereum-to-ton) -- [Comparison of TON, Solana and Ethereum 2.0](https://ton.org/comparison_of_blockchains.pdf) +- [从以太坊到TON](/learn/introduction#ethereum-to-ton) +- [TON、Solana和以太坊2.0的比较](https://ton.org/comparison_of_blockchains.pdf) +### TON 有测试环境吗? -### Does TON have a test environment? +- [Testnet测试网](/develop/smart-contracts/environment/testnet) -- [Testnet](/v3/documentation/smart-contracts/getting-started/testnet) +## 区块 -## TON and L2 +### 获取区块信息的RPC方法是什么? -### Why is workchains better than L1 → L2? +验证者生产区块。现有区块通过Liteservers可用。Liteservers通过轻客户端访问。在轻客户端之上构建了第三方工具,如钱包、浏览器、dapps等。 -Workchains in TON, offer a number of advantages over traditional L1 and L2 layer architecture. +- 要访问轻客户端核心,请查看我们GitHub的这个部分:[ton-blockchain/tonlib](https://github.com/ton-blockchain/ton/tree/master/tonlib) -1. One of the key advantages of a blockchain is the instantaneous processing of transactions. In traditional L2 solutions, there can be delays in moving assets between layers. Workchains eliminate this problem by providing seamless and instantaneous transactions between different parts of the network. This is especially important for applications requiring high speed and low latency. -2. Workchains support cross-shard activity, which means that users can interact between different shardchains or workchains within the same network. In current L2 solutions, cross-shard operations are often complex and require additional bridges or interoperability solutions. In TON, for example, users can easily exchange tokens or perform other transactions between different shardchains without complex procedures. -3. Scalability is one of the main challenges for modern blockchain systems. In traditional L2 solutions, scalability is limited by the capacity of the sequencer. If the TPS (transactions per second) on L2 exceeds the sequencer's capacity, it can lead to problems. In workchains in TON, this problem is solved by dividing the shard. When the load on a shard exceeds its capacity, the shard is automatically divided into two or more shards, allowing the system to scale almost without limit. - -### Is there a need for L2 on the TON? - -At any transaction cost, there will always be applications that cannot sustain such a fee but can function at a much lower cost. Similarly, regardless of the latency achieved, there will always be applications that require even lower latency. Therefore, it is conceivable that there might eventually be a need for L2 solutions on the TON platform to cater to these specific requirements. - -## MEV - -### Is front running possible in TON? - -In the TON blockchain, deterministic transaction order plays a key role in preventing frontrunning. This means that the order of transactions within a blockchain is predetermined and deterministic. No participant can change this order once transactions have entered the pool. This system eliminates the possibility of manipulating the order of transactions for profit, which differentiates TON from other blockchains such as Ethereum, where validators can change the order of transactions within a block, creating opportunities for MEV (maximum extractable value). - -In addition, the current TON architecture lacks a market-based mechanism for determining transaction fees. Commissions are fixed and not subject to change depending on transaction priorities, which makes frontrunning less attractive. Because of the fixed fees and deterministic order of transactions, it is non-trivial to do frontrunning in TON. - -## Block - -### What is the RPC method used to retrieve block information? - -Blocks produced by Validators. Existing blocks available via Liteservers. Liteservers accessible via Lite Clients. On top of Lite Client built 3rd-party tools like wallets, explorers, dapps, etc. - -- To access the Lite Client core check out this section of our GitHub: [ton-blockchain/tonlib](https://github.com/ton-blockchain/ton/tree/master/tonlib) - - -Additionally, here are three high-level third-party block explorers: +此外,这里有三个高级第三方区块浏览器: - https://explorer.toncoin.org/last - https://toncenter.com/ - https://tonwhales.com/explorer -Read more in the [Explorers in TON](/v3/concepts/dive-into-ton/ton-ecosystem/explorers-in-ton) section of our documentation. +在我们文档的[Explorers in TON](/participate/explorers)部分阅读更多。 -### Block time +### 区块时间 -_2-5s_ +_2-5秒_ :::info -Compare TON's on-chain metrics, including block time and time-to-finality, to Solana and Ethereum by reading our analysis at: -* [Comparison of Blockchains document](https://ton.org/comparison_of_blockchains.pdf) -* [Comparison of Blockchains table (much less informative than the document, but more visual)](/v3/concepts/dive-into-ton/ton-blockchain/blockchain-comparison) - ::: +通过阅读我们在[analysis page](https://ton.org/comparison_of_blockchains.pdf)上的分析,比较TON的链上指标,包括区块时间和最终确定时间。 +::: -### Time-to-finality +### 最终确定时间 -_Under 6 sec._ +_小于6秒_ :::info -Compare TON's on-chain metrics, including block time and time-to-finality, to Solana and Ethereum by reading our analysis at: -* [Comparison of Blockchains document](https://ton.org/comparison_of_blockchains.pdf) -* [Comparison of Blockchains table (much less informative than the document, but more visual)](/v3/concepts/dive-into-ton/ton-blockchain/blockchain-comparison) - ::: +通过阅读我们在[analysis page](https://ton.org/comparison_of_blockchains.pdf)上的分析,比较TON的链上指标,包括区块时间和最终确定时间。 +::: -### Average block size +### 平均区块大小 ```bash max block size param 29 @@ -86,193 +58,173 @@ max_block_bytes:2097152 ``` :::info -Find more actual params in [Network Configs](/v3/documentation/network/configs/network-configs). +在[Network Configs](/develop/howto/network-configs)中找到更多实际参数。 ::: -### What is the layout of blocks on TON? +### TON 上的区块布局是怎样的? -Detailed explanations on each field of the layout: +对布局中每个字段的详细解释: -- [Block layout](/v3/documentation/data-formats/tlb/block-layout) +- [区块布局](/develop/data-formats/block-layout) -## Transactions +## 交易 -### RPC method to get transactions data +### 获取交易数据的RPC方法是什么? -- [see answer above](/v3/documentation/faq#are-there-any-standardized-protocols-for-minting-burning-and-transferring-fungible-and-non-fungible-tokens-in-transactions) +- [请参见上面的答案](/develop/howto/faq#are-there-any-standardized-protocols-for-minting-burning-and-transferring-fungible-and-non-fungible-tokens-in-transactions) -### Is TON transaction asynchronous or synchronous? Is it possible to access documentation that show how this system works? +### TON 交易是异步的还是同步的?是否有文档显示这个系统是如何工作的? -TON Blockchain messages asynchronous: -- sender prepares the transaction body(message boc) and broadcasts it via Lite Client (or higher-level tool) -- Lite Client returns status of broadcast, not result of executing the Transaction -- sender checks desired result by listening target account(address) state or the whole blockchain state +TON区块链消息是异步的: +- 发送者准备交易正文(消息boc)并通过轻客户端(或更高级工具)广播 +- 轻客户端返回广播状态,而非执行交易的结果 +- 发送者通过监听目标账户(地址)状态或整个区块链状态来检查期望结果 -An explanation of how TON asynchronous messaging works is explained using an example related to Wallet smart contracts: -- [How TON wallets work and how to access them using JavaScript](https://blog.ton.org/how-ton-wallets-work-and-how-to-access-them-from-javascript#1b-sending-a-transfer) +使用一个与钱包智能合约相关的例子来解释TON异步消息传递是如何工作的: +- [TON钱包如何工作,以及如何使用JavaScript访问它们](https://blog.ton.org/how-ton-wallets-work-and-how-to-access-them-from-javascript#1b-sending-a-transfer) -Example for Wallet contract transfer (low-level): +钱包合约转账的示例(低层级): - https://github.com/xssnick/tonutils-go/blob/master/example/wallet/main.go -### Is it possible to determine if a transaction is 100% finalized? Is querying the transaction level data sufficient to obtain this information? +### 是否可以确定交易100%完成?查询交易级数据是否足以获得这些信息? -**Short answer:** To ensure the transaction is finalized, the receiver's account must be checked. +**简短回答:**要确保交易已完成,必须检查接收者的账户。 -To learn more about transaction verification, please see the following examples: -- Go: [Wallet example](https://github.com/xssnick/tonutils-go/blob/master/example/wallet/main.go) -- Python: [Storefront bot with payments in TON](/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot) -- JavaScript: [Bot being used for dumpling sales](/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js) +要了解有关交易验证的更多信息,请参阅以下示例: +- Go: [钱包示例](https://github.com/xssnick/tonutils-go/blob/master/example/wallet/main.go) +- Python: [带支付的Storefront bot](/develop/dapps/tutorials/accept-payments-in-a-telegram-bot) +- JavaScript: [饺子销售机器人](/develop/dapps/tutorials/accept-payments-in-a-telegram-bot-js) -### What is the layout of a transaction in TON? +### TON 中交易的布局是怎样的? -Detailed explanations on each field of the layout: -- [Transaction layout](/v3/documentation/data-formats/tlb/transaction-layout) +对布局中每个字段的详细解释: +- [交易布局](/develop/data-formats/transaction-layout) -### Is transaction batching possible? +### 是否可以批量处理交易? -Yes, transaction batching on TON can be accomplished in two distinct ways: -- By utilizing the asynchronous nature of TON, i.e. sending independent transactions to the network -- By making use of smart contracts which receive task and execute it as a batch +是的,TON上可以通过两种不同的方式实现交易批量处理: +- 通过利用TON的异步特性,即向网络发送独立的交易 +- 通过使用接收任务并将其作为批处理执行的智能合约 -Example of using batch-featured contract (high-load wallet): +使用批量处理特性的合约示例(高负载钱包): - https://github.com/tonuniverse/highload-wallet-api -Default wallets (v3/v4) also support sending multiple messages (up to 4) in one transaction. +默认钱包(v3/v4)也支持在一笔交易中发送多达4条消息。 -## Standards +## 标准 -### What accuracy of currencies is available for TON? +### TON 的货币精度是多少? -_9 digits_ +_9位小数_ :::info -Number of decimal places supported by Mainnet : 9 digits. +Mainnet支持的小数位数:9位。 ::: -### Are there any standardized protocols for minting, burning, and transferring fungible and non-fungible tokens in transactions? +### 是否有标准化的协议用于铸造、销毁和交易中转移可替代和不可替代代币? -Non-fungible tokens (NFTs): -- [TEP-62: NFT standard](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) -- [NFT documentation](/v3/documentation/dapps/defi/tokens#nft) +非同质化代币(NFT): +- [TEP-62:NFT标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) +- [NFT文档](/develop/dapps/defi/tokens#nft) -Jettons (tokens): -- [TEP-74: Jettons standard](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) -- [Distributed tokens overview](https://telegra.ph/Scalable-DeFi-in-TON-03-30) -- [Fungible token documentation(Jettons)](/v3/documentation/dapps/defi/tokens#jettons) +Jettons(代币): +- [TEP-74:Jettons标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) +- [分布式代币概览](https://telegra.ph/Scalable-DeFi-in-TON-03-30) +- [可替代代币文档(Jettons)](/develop/dapps/defi/tokens#jettons) -Other Standards: +其他标准: - https://github.com/ton-blockchain/TEPs -### Are there examples of parsing events with Jettons(Tokens) and NFT? - -On TON, all data is transmitted as boc-messages. This means that using NFTs in transactions is not an exceptional event. Rather, it is a regular message that is sent to or received from a (NFT- or Wallet-)contract, much like a transaction involving a standard wallet. +### 是否有用 Jettons(代币)和 NFT 解析事件的示例? -However, certain indexed APIs allow you to view all messages sent to or from a contract, and filter them based on your specific requirements. +在TON上,所有数据都以boc消息的形式传输。这意味着在交易中使用NFT并不是特殊事件。相反,它是发送给或从(NFT或钱包)合约接收的常规消息,就像涉及标准钱包的交易一样。 -- https://docs.tonconsole.com/tonapi/rest-api +然而,某些索引的API允许您查看发送到或从合约发送的所有消息,并根据您的特定需求对它们进行过滤。 -To better understand how this process works, please refer [Payments Processing](/v3/guidelines/dapps/asset-processing/payments-processing) section. +- https://tonapi.io/swagger-ui +要更好地理解这个过程是如何工作的,请参阅[支付处理](/develop/dapps/asset-processing/)部分。 -## Account Structure +## 账户结构 -### What is the address format? +### 地址格式是什么? -- [Smart Contract Address](/v3/documentation/smart-contracts/addresses) +- [智能合约地址](/learn/overviews/addresses) -### Is it possible to have a named account similar to ENS +### 是否可以拥有类似于 ENS 的命名账户 -Yes, use TON DNS: -- [TON DNS & Domains](/v3/guidelines/web3/ton-dns/dns) +是的,请使用TON DNS: +- [TON DNS和域名](/participate/web3/dns) -### How to distinguish between a normal account and a smart contract? +### 如何区分普通账户和智能合约? -- [Everything is a smart contract](/v3/documentation/smart-contracts/addresses#everything-is-a-smart-contract) +- [一切都是智能合约](/learn/overviews/addresses#everything-is-a-smart-contract) -### How to tell if the address is a token address? +### 如何判断地址是否为代币地址? -For a **Jettons** contract must implement [standard's interface](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) and return data on _get_wallet_data()_ or _get_jetton_data()_ methods. +对于**Jettons**合约必须实现[标准的接口](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md)并在_get_wallet_data()_或_get_jetton_data()_方法上返回数据。 -### Are there any special accounts (e.g. accounts owned by the network) that have different rules or methods from the rest? +### 是否有特殊账户(例如,由网络拥有的账户)与其他账户有不同的规则或方法? -There is a special master blockchain inside a TON called Masterchain. It consists of network-wide contracts with network configuration, validator-related contracts, etc: +TON内有一个特殊的主链叫做Masterchain。它由网络范围内的合约组成,包括网络配置、与验证者相关的合约等: :::info -Read more about masterchain, workchains and shardchains in TON Blockchain overview article: [Blockchain of Blockchains](/v3/concepts/dive-into-ton/ton-blockchain/blockchain-of-blockchains). +在TON区块链概述文章中阅读更多关于masterchain、workchains和shardchains的信息:[Blockchain of Blockchains](/learn/overviews/ton-blockchain)。 ::: -Good example is smart governance contract, which is a part of masterchain: -- [Governance contracts](/v3/documentation/smart-contracts/contracts-specs/governance) - -## Smart Contracts - -### Is it possible to detect contract deployment events on TON? - -[Everything in TON is a smart contract](/v3/documentation/smart-contracts/addresses#everything-is-a-smart-contract). - -Account address is generated deterministically from its _initial state_, which includes _initial code_ and _initial data_ (for wallets, initial data includes public key among other parameters). -When any component changes, the address changes accordingly. - -Smart contract can exist in uninitialized state, meaning that its state is not available in blockchain but contract has non-zero balance. Initial state itself can be sent to the network later with an internal or external message, so those can be monitored to detect contract deployment. - -To protect message chains from being halted at non-existing contracts TON use "bounce" feature. Read more in these articles: - -- [Deploying wallet via TonLib](/v3/guidelines/dapps/asset-processing/payments-processing/#deploying-wallet) -- [Paying for processing queries and sending responses](/v3/documentation/smart-contracts/transaction-fees/forward-fees) - -### Does the upgradability of a smart-contract pose a threat to its users? +一个很好的例子是治理智能合约,它是masterchain的一部分: +- [治理合约](/develop/smart-contracts/governance) -Currently, the ability to update smart contracts is a normal practice and is widely used in most modern protocols. This is because upgradability allows for bug fixes, adding new features and improving security. +## 智能合约 -How to mitigate the risks: +### 是否可以检测到 TON 上的合约部署事件? -1. Pay attention to projects with a good reputation and well-known development teams. -2. Reputable projects always conduct independent code audits to make sure the code is safe and reliable. Look for projects that have several completed audits from reputable auditing firms. -3. An active community and positive feedback can serve as an additional indicator of a project's reliability. -4. Examine exactly how the project implements the update process. The more transparent and decentralized the process, the less risk to users. +[TON中的一切都是智能合约](/learn/overviews/addresses#everything-is-a-smart-contract)。 -### How can users be sure that the contract owner will not change certain conditions (via an update)? +账户地址是从其_初始状态_确定生成的,其中包括_初始代码_和_初始数据_(对于钱包,初始数据包括公钥在内的其他参数)。当任何组件发生变化时,地址相应改变。 -The contract must be verified, this allows you to check the source code and ensure that there is no update logic to ensure it remains unchanged. If the contract does indeed lack mechanisms to change the code, the terms of the contract will remain unchanged after deployment. +智能合约可以存在于未初始化状态,意味着其状态在区块链中不可用但合约有非零余额。初始状态本身可以稍后通过内部或外部消息发送到网络,因此可以监控这些来检测合约部署。 -Sometimes the logic for updating may exist, but the rights to change the code may be moved to an "empty" address, which also precludes changes. +为了防止消息链在不存在的合约处中断,TON使用了“弹回”功能。在这些文章中了解更多信息: -### Is it possible to re-deploy code to an existing address or does it have to be deployed as a new contract? +- [通过TonLib部署钱包](https://ton.org/docs/develop/dapps/asset-processing/#deploying-wallet) +- [支付查询处理费用并发送响应](https://ton.org/docs/develop/smart-contracts/guidelines/processing) -Yes, this is possible. If a smart contract carries out specific instructions (`set_code()`) its code can be updated and the address will remain the same. +### 是否可以将代码重新部署到现有地址,还是必须作为新合约部署? -If the contract cannot initially execute `set_code()` (via its code or execution of other code coming from the outside), then its code cannot be changed ever. No one will be able to redeploy contract with other code at the same address. +是的,这是可能的。如果智能合约执行特定指令(`set_code()`),其代码可以被更新并且地址将保持不变。 -### Can smart contract be deleted? +如果合约最初无法执行`set_code()`(通过其代码或来自外部的其他代码的执行),那么它的代码将永远无法更改。没有人能够在同一地址重新部署带有其他代码的合约。 -Yes, either as a result of storage fee accumulation (contract needs to reach -1 TON balance to be deleted) or by sending a message with [mode 160](v3/documentation/smart-contracts/message-management/sending-messages#message-modes). +### 智能合约可以被删除吗? -### Are smart contract addresses case sensitive? +是的,要么是由于存储费用累积的结果(合约需要达到-1 TON余额才能被删除),要么是通过发送带有[mode 160](https://docs.ton.org/develop/smart-contracts/messages#message-modes)的消息。 -Yes, smart contract addresses are case sensitive because they are generated using the [base64 algorithm](https://en.wikipedia.org/wiki/Base64). You can learn more about smart contract addresses [here](/v3/documentation/smart-contracts/addresses). +### 智能合约地址是否区分大小写? +是的,智能合约地址是区分大小写的,因为它们是使用[base64算法](https://en.wikipedia.org/wiki/Base64)生成的。您可以在[这里](/learn/overviews/addresses)了解更多关于智能合约地址的信息。 -### Is the Ton Virtual Machine (TVM) EVM-compatible? +### Ton 虚拟机(TVM)与 EVM 兼容吗? - The TVM is not compatible with the Ethereum Virtual Machine (EVM) because TON leverages a completely different architecture (TON is asynchronous, while Ethereum is synchronous). + TVM与以太坊虚拟机(EVM)不兼容,因为TON采用了完全不同的架构(TON是异步的,而以太坊是同步的)。 - [Read more on asynchronous smart contracts](https://telegra.ph/Its-time-to-try-something-new-Asynchronous-smart-contracts-03-25). + [了解更多关于异步智能合约](https://telegra.ph/Its-time-to-try-something-new-Asynchronous-smart-contracts-03-25)。 -### Is it possible to write on Solidity for TON? +### 是否可以为 TON 编写 Solidity? - Relatedly, the TON ecosystem does not support development in Ethereum’s Solidity programming language. + 相关地,TON生态系统不支持在以太坊的Solidity编程语言中开发。 - But if you add asynchronous messages to the Solidity syntax and the ability to interact with data at a low level, then you get FunC. FunC features a syntax that is common to most modern programming languages and is designed specifically for development on TON. + 但如果您在Solidity语法中添加异步消息并能够与数据进行低层级交互,那么您可以使用FunC。FunC具有大多数现代编程语言通用的语法,并专为TON上的开发设计。 -## Remote Procedure Calls (RPCs) +## 远程过程调用(RPC) -### Recommended node providers for data extraction include: +### 推荐的节点提供商用于数据提取包括: -API types: -- Read more about different [API Types](/v3/guidelines/dapps/apis-sdks/api-types) (Indexed, HTTP, and ADNL) +API类型: +- 了解更多关于不同[API类型](/develop/dapps/apis/)(索引、HTTP和ADNL) -Node providers partners: +节点提供商合作伙伴: - https://toncenter.com/api/v2/ - [getblock.io](https://getblock.io/) @@ -281,11 +233,11 @@ Node providers partners: - [nownodes.io](https://nownodes.io/nodes) - https://dton.io/graphql -TON directory with projects from TON Community: +TON社区项目目录: - [ton.app](https://ton.app/) -### Provided below are two main resources used to obtain information related to public node endpoints on TON Blockchain (for both TON Mainnet and TON Testnet). +### 以下提供了两个主要资源,用于获取与TON区块链公共节点端点相关的信息(适用于TON Mainnet和TON Testnet)。 -- [Network Configs](/v3/documentation/network/configs/network-configs) -- [Examples and tutorials](/v3/guidelines/dapps/overview#tutorials-and-examples) +- [网络配置](/develop/howto/network-configs) +- [示例和教程](/develop/dapps/#examples) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/bridge-addresses.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/bridge-addresses.md new file mode 100644 index 0000000000..99f30d9b15 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/bridge-addresses.md @@ -0,0 +1,213 @@ +# 跨链桥地址 + +:::caution +要准确获取当前跨链桥智能合约的地址,请直接在[区块链配置](/participate/crosschain/overview#blockchain-configs)中查看。这是最安全的方式。 +::: + +## Toncoin主网 + +### 主网 TON-Ethereum Toncoin桥 + +封装的 TONCOIN 地址 - [0x582d872a1b094fc48f5de31d3b73f2d9be47def1](https://etherscan.io/token/0x582d872a1b094fc48f5de31d3b73f2d9be47def1) + +Bridge 地址 - [Ef_dJMSh8riPi3BTUTtcxsWjG8RLKnLctNjAM4rw8NN-xWdr](https://tonscan.org/address/Ef_dJMSh8riPi3BTUTtcxsWjG8RLKnLctNjAM4rw8NN-xWdr) + +Collector 地址 - [EQCuzvIOXLjH2tv35gY4tzhIvXCqZWDuK9kUhFGXKLImgxT5](https://tonscan.org/address/EQCuzvIOXLjH2tv35gY4tzhIvXCqZWDuK9kUhFGXKLImgxT5) + +Governance 地址 - [Ef87m7_QrVM4uXAPCDM4DuF9Rj5Rwa5nHubwiQG96JmyAjQY](https://tonscan.org/address/Ef87m7_QrVM4uXAPCDM4DuF9Rj5Rwa5nHubwiQG96JmyAjQY) + +### 主网 TON-BSC Toncoin桥 + +封装的 TONCOIN 地址 - [0x76A797A59Ba2C17726896976B7B3747BfD1d220f](https://bscscan.com/token/0x76A797A59Ba2C17726896976B7B3747BfD1d220f) + +Bridge 地址 - [Ef9NXAIQs12t2qIZ-sRZ26D977H65Ol6DQeXc5_gUNaUys5r](https://tonscan.org/address/Ef9NXAIQs12t2qIZ-sRZ26D977H65Ol6DQeXc5_gUNaUys5r) + +Collector 地址 - [EQAHI1vGuw7d4WG-CtfDrWqEPNtmUuKjKFEFeJmZaqqfWTvW](https://tonscan.org/address/EQAHI1vGuw7d4WG-CtfDrWqEPNtmUuKjKFEFeJmZaqqfWTvW) + +Governance 地址 - [Ef8OvX_5ynDgbp4iqJIvWudSEanWo0qAlOjhWHtga9u2YjVp](https://tonscan.org/address/Ef8OvX_5ynDgbp4iqJIvWudSEanWo0qAlOjhWHtga9u2YjVp) + +### 主网Toncoin预言机 + +预言机0 - [Ef_P2CJw784O1qVd8Qbn8RCQc4EgxAs8Ra-M3bDhZn3OfzRb](https://tonscan.org/address/Ef_P2CJw784O1qVd8Qbn8RCQc4EgxAs8Ra-M3bDhZn3OfzRb) + +预言机1 - [Ef8DfObDUrNqz66pr_7xMbUYckUFbIIvRh1FSNeVSLWrvo1M](https://tonscan.org/address/Ef8DfObDUrNqz66pr_7xMbUYckUFbIIvRh1FSNeVSLWrvo1M) + +预言机2 - [Ef8JKqx4I-XECLuVhTqeY1WMgbgTp8Ld3mzN-JUogBF4ZEW-](https://tonscan.org/address/Ef8JKqx4I-XECLuVhTqeY1WMgbgTp8Ld3mzN-JUogBF4ZEW-) + +预言机3 - [Ef8voAFh-ByCeKD3SZhjMNzioqCmDOK6S6IaeefTwYmRhgsn](https://tonscan.org/address/Ef8voAFh-ByCeKD3SZhjMNzioqCmDOK6S6IaeefTwYmRhgsn) + +预言机4 - [Ef_uJVTTToU8b3o7-Jr5pcUqenxWzDNYpyklvhl73KSIA17M](https://tonscan.org/address/Ef_uJVTTToU8b3o7-Jr5pcUqenxWzDNYpyklvhl73KSIA17M) + +预言机5 - [Ef93olLWqh1OuBSTOnJKWZ4NwxNq_ELK55_h_laNPVwxcEro](https://tonscan.org/address/Ef93olLWqh1OuBSTOnJKWZ4NwxNq_ELK55_h_laNPVwxcEro) + +预言机6 - [Ef_iUPZdKLOCrqcNpDuFGNEmiuBwMB18TBXNjDimewpDExgn](https://tonscan.org/address/Ef_iUPZdKLOCrqcNpDuFGNEmiuBwMB18TBXNjDimewpDExgn) + +预言机7 - [Ef_tTGGToGmONePskH_Y6ZG-QLV9Kcg5DIXeKwBvCX4YifKa](https://tonscan.org/address/Ef_tTGGToGmONePskH_Y6ZG-QLV9Kcg5DIXeKwBvCX4YifKa) + +预言机8 - [Ef94L53akPw-4gOk2uQOenUyDYLOaif2g2uRoiu1nv0cWYMC](https://tonscan.org/address/Ef94L53akPw-4gOk2uQOenUyDYLOaif2g2uRoiu1nv0cWYMC) + +EVM地址: 0xC4c9bd836ab8b446519736166919e3d62491E041,0xCF4A7c26186aA41390E246FA04115A0495085Ab9,0x17DcaB1B1481610F6C7a7A98cf0370dC0EC704a6,0x32162CAaEd276E77EF63194820586C942009a962,0x039f4e886432bd4f3cb5062f9861EFef3F6aDA28,0xFf441F9889Aa475d9D3b1C638C59B84c5179846D,0x0933738699dc733C46A0D4CBEbDA2f842e1Ac7d9,0x7F2bbaaC14F0f1834E6D0219F8855A5F619Fe2C4,0xfc5c6A2d01A984ba9eab7CF87A6D169aA9720c0C. + +## Toncoin测试网 + +### 测试网 TON-Ethereum Toncoin桥 + +封装的TONCOIN地址 - [0xDB15ffaf2c88F2d89Db9365a5160D5b8c9448Ea6](https://goerli.etherscan.io/token/0xDB15ffaf2c88F2d89Db9365a5160D5b8c9448Ea6) + +Bridge 地址 - [Ef-56ZiqKUbtp_Ax2Qg4Vwh7yXXJCO8cNJAb229J6XXe4-aC](https://testnet.tonscan.org/address/Ef-56ZiqKUbtp_Ax2Qg4Vwh7yXXJCO8cNJAb229J6XXe4-aC) + +Collector 地址 - [EQCA1W_I267-luVo9CzV7iCcrA1OO5vVeXD0QHACvBn1jIVU](https://testnet.tonscan.org/address/EQCA1W_I267-luVo9CzV7iCcrA1OO5vVeXD0QHACvBn1jIVU) + +Governance 地址 - [kf-OV1dpgFVEzEmyvAETT8gnhqZ1IqHn8RzT6dmEmvnze-9n](https://testnet.tonscan.org/address/kf-OV1dpgFVEzEmyvAETT8gnhqZ1IqHn8RzT6dmEmvnze-9n) + +### 测试网 TON-BSC Toncoin桥 + +封装的TONCOIN地址 - [0xdb15ffaf2c88f2d89db9365a5160d5b8c9448ea6](https://testnet.bscscan.com/token/0xdb15ffaf2c88f2d89db9365a5160d5b8c9448ea6) + +Bridge 地址 - [Ef_GmJntTDokxfhLGF1jRvMGC8Jav2V5keoNj4El2jzhHsID](https://testnet.tonscan.org/address/Ef_GmJntTDokxfhLGF1jRvMGC8Jav2V5keoNj4El2jzhHsID) + +Collector 地址 - [EQDBNfV4DQzSyzNMw6BCTSZSoUi-CzWcYNsfhKxoDqfrwFtS](https://testnet.tonscan.org/address/EQDBNfV4DQzSyzNMw6BCTSZSoUi-CzWcYNsfhKxoDqfrwFtS) + +Governance 地址 - [kf83VnnXuaqQV1Ts2qvUr6agacM0ydOux5NNa1mcU-cEO693](https://testnet.tonscan.org/address/kf83VnnXuaqQV1Ts2qvUr6agacM0ydOux5NNa1mcU-cEO693) + +### 测试网Toncoin预言机 + +> 与Toncoin测试网桥相同 + +* 预言机0 + + TON地址 - [Ef9fwskZLEuGDfYTRAtvt9k-mEdkaIskkUOsEwPw1wzXk7zR](https://testnet.tonscan.org/address/Ef9fwskZLEuGDfYTRAtvt9k-mEdkaIskkUOsEwPw1wzXk7zR) + + EVM地址 - 0xe54cd631c97be0767172ad16904688962d09d2fe + +* 预言机1 + + TON地址 - [Ef8jPzrhTYloKgTCsGgEFNx7OdH-sJ98etJnwrIVSsFxH9mu](https://testnet.tonscan.org/address/Ef8jPzrhTYloKgTCsGgEFNx7OdH-sJ98etJnwrIVSsFxH9mu) + + EVM地址 - 0xeb05E1B6AC0d574eF2CF29FDf01cC0bA3D8F9Bf1 + +* 预言机2 + + TON地址 - [Ef-fxGCPuPKNR6T4GcFxNQuLU5TykLKEAtkxWEfA1wBWy6JE](https://testnet.tonscan.org/address/Ef-fxGCPuPKNR6T4GcFxNQuLU5TykLKEAtkxWEfA1wBWy6JE) + + EVM地址 - 0xF636f40Ebe17Fb2A1343e5EEee9D13AA90888b51 + +## Token主网 + +### 主网 TON-Ethereum Token桥 + +以太坊Bridge地址 - [0xb323692b6d4DB96af1f52E4499a2bd0Ded9af3C5](https://etherscan.io/address/0xb323692b6d4DB96af1f52E4499a2bd0Ded9af3C5) + +Bridge 地址 - [Ef-1JetbPF9ubc1ga-57oHoOyDA1IShJt-BVlJnA9rrVTfrB](https://tonscan.org/address/Ef-1JetbPF9ubc1ga-57oHoOyDA1IShJt-BVlJnA9rrVTfrB) + +Collector 地址 - [EQDF6fj6ydJJX_ArwxINjP-0H8zx982W4XgbkKzGvceUWvXl](https://tonscan.org/address/EQDF6fj6ydJJX_ArwxINjP-0H8zx982W4XgbkKzGvceUWvXl) + +Governance 地址 - [Ef8hHxV0v2I9FHh3CMX91WXjKaJav6SQlemEQm8ZvPBJdLde](https://tonscan.org/address/Ef8hHxV0v2I9FHh3CMX91WXjKaJav6SQlemEQm8ZvPBJdLde) + +### 主网Token预言机 + +* 预言机0 + + TON公钥 = a0993546fbeb4e8c90eeab0baa627659aee01726809707008e38d5742ea38aef + + TON地址 - [Ef8WxwYOyAk-H0YGBc70gZFJc6oqUvcHywU-yJNBfSNh-GW9](https://tonscan.org/address/Ef8WxwYOyAk-H0YGBc70gZFJc6oqUvcHywU-yJNBfSNh-GW9) + + ETH地址 - 0x3154E640c56D023a98890426A24D1A772f5A38B2 + +* 预言机1 + + TON公钥 = fe0a78726a82754b62517e4b7a492e1b1a8d4c9014955d2fa8f1345f1a3eafba + + TON地址 = [Ef8CbgwhUMYn2yHU343dcezKkvme3cyFJB7SHVY3FXhU9jqj](https://tonscan.org/address/Ef8CbgwhUMYn2yHU343dcezKkvme3cyFJB7SHVY3FXhU9jqj) + + ETH地址 = 0x8B06A5D37625F41eE9D9F543482b6562C657EA6F + +* 预言机2 + + TON公钥 = 00164233e111509b0486df85d2743defd6e2525820ee7d341c8ad92ee68d41a6 + + TON地址 = [Ef-n3Vdme6nSe4FBDb3inTRF9B6lh3BbIwGlk0dDpUO5oFmH](https://tonscan.org/address/Ef-n3Vdme6nSe4FBDb3inTRF9B6lh3BbIwGlk0dDpUO5oFmH) + + ETH地址 = 0x6D5E361F7E15ebA73e41904F4fB2A7d2ca045162 + +* 预言机3 + + TON公钥 = 9af68ce3c030e8d21aae582a155a6f5c41ad006f9f3e4aacbb0ce579982b9ebb + + TON地址 = [Ef9D1-FOb82pREFPgW7AlzNlZ7f0XnvmGakW23wpWeILAum9](https://tonscan.org/address/Ef9D1-FOb82pREFPgW7AlzNlZ7f0XnvmGakW23wpWeILAum9) + + ETH地址 = 0x43931B8c29e34a8C16695408CD56327F511Cf086 + +* 预言机4 + + TON公钥 = a4fef528b1e841f5fce752feeac0971f7df909e37ffeb3fab71c5ce8deb9f7d4 + + TON地址 = [Ef8TBPHHIowG5pGgSVX8n4KmOaX-EEjvnOSBRlQvVsJWP_WJ](https://tonscan.org/address/Ef8TBPHHIowG5pGgSVX8n4KmOaX-EEjvnOSBRlQvVsJWP_WJ) + + ETH地址 = 0x7a0d3C42f795BA2dB707D421Add31deda9F1fEc1 + +* 预言机5 + + TON公钥 = 58a7ab3e3ff8281b668a86ad9fe8b72f2d14df5dcc711937915dacca1b94c07d + + TON地址 = [Ef8ceN7cTemTe4ZV6AIbg5f8LsHZsYV1UaiGntvkME0KtP45](https://tonscan.org/address/Ef8ceN7cTemTe4ZV6AIbg5f8LsHZsYV1UaiGntvkME0KtP45) + + ETH地址 = 0x88352632350690EF22F9a580e6B413c747c01FB2 + +* 预言机6 + + TON公钥 = db60c3f50cb0302b516cd42833c7e8cad8097ad94306564b057b16ace486fb07 + + TON地址 = [Ef8uDTu2WCcJdtuKmkDmC1yRKVxZrTp83ke5PnMECOccg3w4](https://tonscan.org/address/Ef8uDTu2WCcJdtuKmkDmC1yRKVxZrTp83ke5PnMECOccg3w4) + + ETH地址 = 0xeB8975966dAF0C86721C14b8Bb7DFb89FCBB99cA + +* 预言机7 + + TON公钥 = 98c037c6d3a92d9467dc62c0e3da9bb0ad08c6b3d1284d4a37c1c5c0c081c7df + + TON地址 = [Ef905jDDX87nPDbTSMqFB9ILVGX1zWc66PPrNhkjHrWxAnZZ](https://tonscan.org/address/Ef905jDDX87nPDbTSMqFB9ILVGX1zWc66PPrNhkjHrWxAnZZ) + + ETH地址 = 0x48Bf4a783ECFb7f9AACab68d28B06fDafF37ac43 + +* 预言机8 + + TON公钥 = 5503c54a1b27525376e83d6fc326090c7d9d03079f400071b8bf05de5fbba48d + + TON地址 = [Ef9Ubg96xQ8jVKbl7QQJ1k8pClQLmO1Ci68nuNfbLdm9uS-x](https://tonscan.org/address/Ef9Ubg96xQ8jVKbl7QQJ1k8pClQLmO1Ci68nuNfbLdm9uS-x) + + ETH地址 = 0x954AE64BB0268b06ffEFbb6f454867a5F2CB3177 + +## Token测试网 + +### 测试网 TON-Ethereum Token桥 + +以太坊 Bridge 地址 - [0x4Efd8f04B6fb4CFAF0cfaAC11Fb489b97DBebB60](https://goerli.etherscan.io/address/0x4Efd8f04B6fb4CFAF0cfaAC11Fb489b97DBebB60) + +Bridge 地址 - [Ef-lJBALjXSSwSKiedKzriSHixwQUxJ1BxTE05Ur5AXwZVjp](https://testnet.tonscan.org/address/Ef-lJBALjXSSwSKiedKzriSHixwQUxJ1BxTE05Ur5AXwZVjp) + +Collector 地址 - [EQC1ZeKX1LNrlQ4bwi3je3KVM1AoZ3rkeyHM5hv9pYzmIh4v](https://testnet.tonscan.org/address/EQC1ZeKX1LNrlQ4bwi3je3KVM1AoZ3rkeyHM5hv9pYzmIh4v) + +Governance 地址 - [kf9NLH8CsGUkEKGYzCxaLd9Th6T5YkO-MXsCEU9Rw1fiRhf9](https://testnet.tonscan.org/address/kf9NLH8CsGUkEKGYzCxaLd9Th6T5YkO-MXsCEU9Rw1fiRhf9) + +### 测试网Token预言机 + +> 与Toncoin测试网桥相同 + +* 预言机0 + + TON地址 - [Ef9fwskZLEuGDfYTRAtvt9k-mEdkaIskkUOsEwPw1wzXk7zR](https://testnet.tonscan.org/address/Ef9fwskZLEuGDfYTRAtvt9k-mEdkaIskkUOsEwPw1wzXk7zR) + + EVM地址 - 0xe54cd631c97be0767172ad16904688962d09d2fe + +* 预言机1 + + TON地址 - [Ef8jPzrhTYloKgTCsGgEFNx7OdH-sJ98etJnwrIVSsFxH9mu](https://testnet.tonscan.org/address/Ef8jPzrhTYloKgTCsGgEFNx7OdH-sJ98etJnwrIVSsFxH9mu) + + EVM地址 - 0xeb05E1B6AC0d574eF2CF29FDf01cC0bA3D8F9Bf1 + +* 预言机2 + + TON地址 - [Ef-fxGCPuPKNR6T4GcFxNQuLU5TykLKEAtkxWEfA1wBWy6JE](https://testnet.tonscan.org/address/Ef-fxGCPuPKNR6T4GcFxNQuLU5TykLKEAtkxWEfA1wBWy6JE) + + EVM地址 - 0xF636f40Ebe17Fb2A1343e5EEee9D13AA90888b51 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/overview.md new file mode 100644 index 0000000000..de427ad5db --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/crosschain/overview.md @@ -0,0 +1,57 @@ +# 跨链桥 + +去中心化的跨链桥在TON区块链上运行,允许您将资产从TON区块链转移到其他区块链,反之亦然。 + +## Toncoin 跨链桥 + +Toncoin 跨链桥允许您在TON区块链和以太坊区块链之间,以及TON区块链和BNB智能链之间转移Toncoin。 + +跨链桥由[去中心化预言机](/participate/crosschain/bridge-addresses)管理。 + +### 如何使用? + +跨链桥的前端托管在 https://ton.org/bridge。 + +:::info +[跨链桥前端源代码](https://github.com/ton-blockchain/bridge) +::: + +### TON-以太坊智能合约源代码 + +- [FunC (TON端)](https://github.com/ton-blockchain/bridge-func) +- [Solidity (以太坊端)](https://github.com/ton-blockchain/bridge-solidity/tree/eth_mainnet) + +### TON-BNB智能链智能合约源代码 + +- [FunC (TON端)](https://github.com/ton-blockchain/bridge-func/tree/bsc) +- [Solidity (BSC端)](https://github.com/ton-blockchain/bridge-solidity/tree/bsc_mainnet) + +### 区块链配置 + +您可以通过检查相应的配置来获取实际的跨链桥的智能合约地址和预言机地址: + +TON-以太坊: [#71](https://github.com/ton-blockchain/ton/blob/35d17249e6b54d67a5781ebf26e4ee98e56c1e50/crypto/block/block.tlb#L738)。 + +TON-BSC: [#72](https://github.com/ton-blockchain/ton/blob/35d17249e6b54d67a5781ebf26e4ee98e56c1e50/crypto/block/block.tlb#L739)。 + +TON-Polygon: [#73](https://github.com/ton-blockchain/ton/blob/35d17249e6b54d67a5781ebf26e4ee98e56c1e50/crypto/block/block.tlb#L740)。 + +### 文档 + +- [跨链桥如何工作](https://github.com/ton-blockchain/TIPs/issues/24) + +### 跨链路线图 + +- https://t.me/tonblockchain/146 + +## Tonana 跨链桥 + +### 如何参与? + +:::caution 草案\ +这是一篇概念文章。我们仍在寻找有经验的人来撰写。 +::: + +您可以在这里找到前端:https://tonana.org/ + +源代码在这里:https://github.com/tonanadao diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/minter-flow.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/minter-flow.md new file mode 100644 index 0000000000..e9e92281fa --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/minter-flow.md @@ -0,0 +1,57 @@ +# 额外代币铸造 + +## 额外代币 + +根据 [Ton区块链白皮书 3.1.6](https://ton-blockchain.github.io/docs/tblkch.pdf#page=55),TON区块链允许其用户定义除Toncoin之外的任意加密货币或代币,前提是满足某些条件。这些额外的加密货币由32位的_currency_ids_标识。定义的额外加密货币列表是区块链配置的一部分,存储在主链中。每个内部消息以及账户余额都包含一个`ExtraCurrencyCollection`特殊字段(附加到消息或保留在余额上的额外代币集合): + +```tlb +extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) = ExtraCurrencyCollection; +currencies$_ grams:Grams other:ExtraCurrencyCollection = CurrencyCollection; +``` + +## 额外代币配置 + +所有应该被铸造的代币的字典,准确来说是`ExtraCurrencyCollection`,存储在`ConfigParam7`中: + +```tlb +_ to_mint:ExtraCurrencyCollection = ConfigParam 7; +``` + +`ConfigParam 6`包含与铸造相关的数据: + +```tlb +_ mint_new_price:Grams mint_add_price:Grams = ConfigParam 6; +``` + +`ConfigParam2`包含_Minter_的地址。 + +## 低层级铸币流程 + +在每个区块中,整合者将旧的全局余额(上一个区块结束时所有代币的全局余额)与`ConfigParam7`进行比较。如果`ConfigParam7`中任何代币的任何金额小于全局余额中的金额 - 配置无效。如果`ConfigParam7`中任何代币的任何金额高于全局余额中的金额,将创建一条铸币消息。 + +这条铸币消息的来源是`-1:0000000000000000000000000000000000000000000000000000000000000000`,并且_Minter_从`ConfigParam2`作为目的地,并包含`ConfigParam7`中比旧全局余额多出来的额外代币。 + +这里的问题是铸币消息只包含额外代币,没有TON。这意味着即使_Minter_被设置为基本智能合约(在`ConfigParam31`中呈现),铸币消息也会导致交易中止:`compute_ph:(tr_phase_compute_skipped reason:cskip_no_gas)`。 + +## 高层级铸币流程 + +_Minter_智能合约在收到创建新额外代币或为现有代币铸造额外代币的请求时应: + +1. 检查`ConfigParam6`中确定的费用是否可以从请求消息中扣除 +2. 1. 对于现有代币:检查铸造授权(只有_所有者_可以铸造新的) + 2. 对于创建新代币:检查加密货币的id是否未被占用,并存储新代币的所有者 +3. 向配置合约发送消息(此类消息应导致`ExtraCurrencyCollection`中的`ConfigParam7`添加) +4. 向`0:0000...0000`发送消息(保证在下一个或随后的区块中回弹)并带有extra_currency id + +收到来自`0:0000...0000`的消息后 + +1. 从回弹消息中读取extra_currency id +2. 如果minter余额上有对应id的代币,将它们发送给这个代币的所有者,并附上`ok`消息 +3. 否则向代币所有者发送`fail`消息 + +## 待解决的问题 + +1. 向`0:0000...0000`发送消息以延迟请求处理的方法相当粗糙。 +2. 当铸造失败时,应考虑这种情况。目前看来,唯一可能的情况是代币数量为0,或者当前余额加上铸造的金额不适合`(VarUInteger 32)` +3. 如何燃烧?乍一看,没有办法。 +4. 铸币费用是否应该是禁止性的?换句话说,拥有数百万额外代币是否危险?(大配置下,区块整理过程中由于不受限的字典操作导致潜在的DoS攻击?) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/node-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/node-types.md new file mode 100644 index 0000000000..a1935b9cb2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/node-types.md @@ -0,0 +1,79 @@ +import Button from '@site/src/components/button' + +# TON 节点类型 + +深入了解开放网络(TON)的世界时,理解不同的节点类型及其功能至关重要。本文为希望与TON区块链互动的开发者详细介绍了每种节点类型。 + +## 全节点 + +TON中的**全节点**是与区块链保持同步的节点。 + +它保留了区块链的_当前状态_,可以包含整个区块历史或其部分。这使其成为TON区块链的支柱,促进网络的去中心化和安全。 + + + +## 验证者节点 + +当**验证者节点**持有足够数量的Toncoin作为质押代币时,它将被激活。验证者节点对网络的运行至关重要,参与新网络区块的验证。 + +TON基于权益证明机制运行,其中验证者在维护网络功能方面发挥着关键作用。验证者会因其贡献而[以Toncoin获得奖励](/participate/network-maintenance/staking-incentives),激励网络参与并确保网络安全。 + +[作为验证者运行全节点](/participate/run-nodes/full-node#become-a-validator) + +## 全节点 + Liteserver + +当在全节点上激活端点时,节点将承担**Liteserver**的角色。这种节点类型可以处理并响应来自轻客户端的请求,允许与TON区块链无缝互动。 + +### 轻客户端:与 TON 交互的SDK + +Liteservers使与轻客户端的快速通信成为可能,便于执行检索余额或提交交易等任务,而不需要完整的区块历史。 + +每个支持ADNL协议的SDK都可以使用`config.json`文件作为轻客户端。`config.json`文件包含了可以用来连接TON区块链的端点列表。 + +[选择TON SDK](/develop/dapps/apis/sdk) + +每个不支持ADNL的SDK通常使用HTTP中间件来连接TON区块链。它的安全性和速度不如ADNL,但使用起来更简单。 + +### 与 TON 的互动:公共Liteservers(端点) + +TON基金会提供了几个公共Liteservers,集成到全局配置中,可供普遍使用。这些端点,如标准钱包使用的端点,确保即使不设置个人liteserver,也能与TON区块链进行交互。 + +- [公共Liteserver配置 - 主网](https://ton.org/global-config.json) +- [公共Liteserver配置 - 测试网](https://ton.org/testnet-global.config.json) + +在您的应用程序中使用下载的`config.json`文件与TON SDK。 + +#### 故障排除 + +##### 故障排除 + +如果您看到此错误,这意味着您尝试连接的liteserver不可用。解决公共liteservers问题的正确方法如下: + +1. 从tontech链接下载config.json文件: + +```bash +wget https://api.tontech.io/ton/wallet-mainnet.autoconf.json -O /usr/bin/ton/global.config.json +``` + +它会从配置文件中移除响应慢的liteservers。 + +2. 在您的应用程序中使用下载的`config.json`文件与TON SDK(/participate/nodes/node-types#lite-clients-the-sdks-to-interact-with-ton)。 + +### 作为 Liteserver 运行全节点 + +如果你的项目需要高度_安全_,你可以运行自己的 Liteserver。要将完整节点作为 Liteserver 运行,只需在节点配置文件中启用 Liteserver 模式即可: + +[在节点中启用 Liteserver](/participate/run-nodes/full-node#enable-liteserver-mode) + +## 归档节点 + +[在节点中启用Liteserver](/participate/run-nodes/full-node#enable-liteserver-mode) + +这种节点对于创建需要完整区块链历史的区块链浏览器或其他工具至关重要。 + +**归档节点**本质上是存档整个区块历史的全节点。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/collators.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/collators.md new file mode 100644 index 0000000000..9dee91f8ae --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/collators.md @@ -0,0 +1,125 @@ +# 验证者/收集者分离 + +:::caution 开发中 +此功能目前仅在测试网中!参与风险自负。 +::: + +TON区块链的关键特性是能够将交易处理分散到网络节点上,并从“每个人都检查所有交易”转变为“每笔交易由安全的验证者子集检查”。这种能力在一个工作链分裂为所需数量的*分片链*时,无限横向扩展吞吐量,使TON与其他L1网络区别开来。 + +然而,为了防止串谋,有必要定期轮换处理一个或另一个分片的验证者子集。同时,为了处理交易,验证者显然应该知道交易之前的分片状态。最简单的方法是要求所有验证者了解所有分片的状态。 + +当TON用户数量在几百万范围内且每秒交易数(TPS)在一百以下时,这种方法运行良好。然而,在未来,当TON处理每秒成千上万笔交易并服务于数亿甚至数十亿人时,没有单一服务器能够保持整个网络的实时状态。幸运的是,TON在设计时就考虑到了这种负载,并支持吞吐量和状态更新的分片。 + +这是通过分离两个角色实现的: + +- *收集者(Collator)* - 观察网络的一部分,了解实际状态并*收集*(生成)下一个区块的行为者 +- *验证者(Validator)* - 从*收集者*获得新区块,检查其有效性,并签署以实际保证正确性,否则将冒失去抵押的风险。 + +同时,TON的架构允许*验证者*在不实际存储区块链状态的情况下有效验证新区块,通过检查特别制作的证明。 + +这样,当TON的吞吐量太大而无法由单一机器处理时,网络将由部分收集者子网络组成,每个收集者只处理其能够处理的链的一部分,以及由多个安全集组成的验证者子网络,用于提交新交易。 + +目前,TON测试网正在测试这种*验证者*/*收集者*分离,其中一些验证者像往常一样工作,而一些验证者不为自己收集区块,而是从收集者那里接收。 + +# 加入“轻验证者” + +新的节点软件可在[区块生成](https://github.com/SpyCheese/ton/tree/block-generation)分支中获得。 + +## 收集者 + +要创建新的收集者,您需要设置TON节点;您可以使用`-M`标志强制节点不关注它不处理的分片链。 + +在`validator-engine-console`中为收集者创建新密钥,将adnl类别`0`设置为此密钥,并通过命令添加收集实体: + +```bash +addcollator +``` + +例如: + +```bash +newkey +addadnl 0 +addcollator 0 -9223372036854775808 +``` + +配置为分片 wc:shard_pfx 的收集者可以收集分片 wc:shard_pfx 及其前者和后者的区块;它还会监控所有这些分片,因为这对于收集是必需的。 + +收集者可以通过命令停止: + +```bash +delcollator 0 -9223372036854775808 +``` + +:::info +目前网络中有一个收集者,配置\*\*-41\*\*用于宣布其adnl地址。 +::: + +## 验证者 + +要运行验证者,您需要设置TON节点,使用`--lite-validator`标志强制验证者从收集者请求新区块而不是生成它们,并设置质押过程。轻模式下的验证者从`-41`配置中获取收集者节点。 + +最简单的方式如下: + +- 为测试网设置MyTonCtrl +- 停止验证者 `sudo systemctl stop validator` +- 更新服务文件 `sudo nano /etc/systemd/system/validator.service`:添加`--lite-validator`标志 +- 重载systemctl `sudo systemctl daemon-reload` +- 启动验证者 `sudo systemctl start validator` + +## 轻节点 + +就像收集者一样,Liteservers可以配置为只监控区块链的一部分。可以通过使用`-M`选项运行节点并在`validator-engine-console`中添加分片来实现: + +```bash +addshard 0 -9223372036854775808 +``` + +默认情况下,主链总是被监控的。分片可以使用`delshard 0 -9223372036854775808`移除。 + +### 轻客户端 + +全局配置至少应包含两个部分之一:`liteservers`和`liteservers_v2`。第一个部分包含有关所有分片状态的“全”Liteservers。第二个部分包含有关区块链某些部分的数据的“部分”liteservers。 + +“部分”Liteservers如下描述: + +```json +"liteservers_v2": [ + { + "ip": ..., + "port": ..., + "id": { + "@type": "pub.ed25519", + "key": "..." + }, + "shards": [ + { + "workchain": 0, + "shard": -9223372036854775808 + } + ] + } + ... +] +``` + +Lite Client和Tonlib支持此配置,并可以为每个查询选择合适的Liteserver。请注意,每个Liteserver默认监控主链,`liteservers_v2`中的每个服务器都隐含配置为接受有关主链的查询。配置中的分片`wc:shard_pfx`表示服务器接受有关分片`wc:shard_pfx`、其前者和后者(就像收集者的配置一样)的查询。 + +## 完全收集的数据 + +默认情况下,提议新区块的验证者在验证者集合中不会附加证明“区块之前”状态的数据。这些数据应由其他验证者从本地存储的状态中获取。通过这种方式,旧节点(来自主分支)和新节点可以达成共识,但新的验证者应该关注所有网络状态。 + +当验证者将附有汇总数据的区块共享时,升级到新协议可以通过以下方式完成: + +- 将所有验证者升级到新节点版本 +- 将 [full_collated_data](https://github.com/spycheese/ton/blob/block-generation/crypto/block/block.tlb#L737) 设置为 true + +# 下一步 + +将*验证者*和*收集者*角色分离的实际能力是实现无限吞吐量的主要里程碑,但要创建真正去中心化和抗审查的网络,有必要 + +- 确保*收集者*的独立性和冗余性 +- 确保验证者和收集者之间的稳定和安全的互动方式 +- 为收集者确立适当的财务模型,激励其持续收集新区块 + +目前,这些任务超出了范围。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/staking-incentives.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/staking-incentives.md new file mode 100644 index 0000000000..b21e4997de --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/infra/nodes/validation/staking-incentives.md @@ -0,0 +1,92 @@ +# 质押激励 + +## 选举和质押 + +TON区块链使用权益证明(PoS)共识算法,这意味着与所有PoS网络一样,网络的安全和稳定性由一组网络验证者维护。特别是,验证者提出新区块(由交易批组成)的候选人,而其他验证者通过数字签名_验证_并批准它们。 + +验证者是使用特殊的[选举治理合约](/develop/smart-contracts/governance#elector)选择的。在每个共识轮次中,验证者候选人发送选举申请,连同他们的质押代币和期望的_max_factor_(调节验证者每轮共识维护量的参数)。 + +在验证者选举过程中,治理智能合约选择下一轮验证者,并根据验证者的质押代币和_max_factor_为每个验证者分配投票权重,以最大化他们的总质押代币。在这方面,质押代币和_max_factor_越高,验证者的投票权重越高,反之亦然。 + +被选中的验证者被选为通过参与下一个共识轮次来保护网络。然而,与许多其他区块链不同,为实现水平扩展,每个验证者只验证网络的一部分: + +对于每个分片链和主链,都有专门的验证者集合。主链验证者集合由最高投票权重的多达100个验证者组成(定义为网络参数`Config16:max_main_validators`)。 + +相比之下,每个分片链由一组23个验证者(定义为网络参数`Config28:shard_validators_num`)验证,并且每1000秒(网络参数`Config28:shard_validators_lifetime`)随机轮换一次。 + +## 质押代币的价值:最大有效质押代币 + +当前配置中的`max_factor`为__3__,意味着_最小_验证者的质押代币不能比_最大_验证者的质押代币多三倍。 + +配置参数的公式: + +`max_factor` = [`max_stake_factor`](https://tonviewer.com/config#17) / [`validators_elected_for`](https://tonviewer.com/config#15) + +### (简化的)选择算法 + +这个算法由[选举智能合约](/develop/smart-contracts/governance#elector)运行,根据验证者所承诺的质押代币选择最佳的验证者候选人。以下是它的工作原理: + +1. **初始选择**:选举者考虑所有承诺超过设定最低金额(300K,如[配置](https://tonviewer.com/config#17)所述)的候选人。 + +2. **排序候选人**:这些候选人根据他们的质押代币从高到低进行排列。 + +3. **缩小范围**: + - 如果候选人数量超过允许的最大验证者数量([见配置](https://tonviewer.com/config#16)),质押代币最低的将被排除。 + - 然后选举者评估每个可能的候选人组,从最大组开始逐渐减小: + - 它检查按顺序排列的顶部候选人,一个接一个地增加数量。 + - 对于每个候选人,选举者计算他们的“有效质押代币”。如果候选人的质押代币明显高于最低限额,它会被调整下来(例如,如果某人质押代币310k,最低限额为100k,但有规则限制最多三倍最低限额,那么他们的有效质押代币被视为300k)。 + - 它对这个组中所有候选人的有效质押代币进行求和。 + +4. **最终选择**:有效质押代币总和最高的候选人组被选举者选为验证者。 + +#### 验证者选择算法 + +根据潜在验证者的可用质押代币,确定最小和最大质押代币的最佳值,目的是最大化总质押代币的量级: + +1. 选举者考虑所有质押代币高于最低限额([配置中的300K](https://tonviewer.com/config#17))的申请者。 +2. 选举者按质押代币_降序_排序他们。 +3. 如果参与者数量超过[最大验证者数量](https://tonviewer.com/config#16),选举者将放弃列表的尾部。然后选举者执行以下操作: + + - 对于每个循环__i__从_1至N_(剩余参与者数量),它从排序列表中取出前__i__个申请。 + - 它计算有效质押代币,考虑到`max_factor`。也就是说,如果某人质押代币310k,但`max_factor`为3,列表中的最低质押代币为100k Toncoin,那么有效质押代币将是min(310k, 3\*100k) = 300k。 + - 它计算所有__i__个参与者的总有效质押代币。 + +一旦选举者找到这样的__i__,使得总有效质押代币最大,我们就宣布这些__i__个参与者为验证者。 + +## 积极激励 + +与所有区块链网络一样,TON上的每笔交易都需要一个称为[ gas ](https://blog.ton.org/what-is-blockchain)的计算费用,用于进行网络存储和链上交易处理。在TON上,这些费用积累在选举者合约中的奖励池中。 + +网络还通过向奖励池添加补贴来补贴区块创建,每个主链块1.7 TON,每个基本链块1 TON(网络参数`Config14:masterchain_block_fee`和`Config14:basechain_block_fee`)。请注意,当将基本链分割为多个分片链时,每个分片链块的补贴相应分割。这个过程允许每单位时间的补贴保持接近恒定。 + +:::info +TON区块链计划在2023年第二季度引入通货紧缩机制。特别是,通过网络使用产生的TON的一部分将被销毁,而不是进入奖励池。 +::: + +经过65536秒或约18小时的验证周期轮次(网络参数`Config15:validators_elected_for`),验证者中的质押TON并未立即释放,而是持有额外的32768秒或约9小时(网络参数`Config15:stake_held_for`)。在此期间,可以从验证者中扣除削减(对行为不端验证者的惩罚机制)罚款。在资金释放后,验证者可以提取他们在验证轮次期间累积的奖励池份额,与他们的投票_权重_成比例。 + +截至2023年4月,网络上所有验证者每轮共识的总奖励池约为40,000 TON,每个验证者的平均奖励约为120 TON(投票权重与累积奖励之间的最大差异约为3 TON)。 + +考虑到Toncoin(50亿TON)的总供应量,其年通胀率约为0.3-0.6%。 + +然而,这一通胀率并非始终恒定,可能会根据网络的当前状态而有所偏差。最终,在通货紧缩机制启动和网络利用率增长后,它将趋于通货紧缩。 + +:::info +了解当前TON区块链统计数据[这里](https://tontech.io/stats/)。 +::: + +## 负面激励 + +在TON区块链上,通常有两种方式可以对行为不端的验证者进行处罚:闲置和恶意行为;这两种行为都是被禁止的,可能会因其行为而被罚款(在所谓的削减过程中)。 + +如果验证者在验证轮次期间长时间不参与区块创建和交易签名,它可能会使用_标准罚款_参数被罚款。截至2023年4月,标准罚款累积为101 TON(网络参数`ConfigParam40:MisbehaviourPunishmentConfig`)。 + +在TON上,削减罚款(给验证者的罚款)允许任何网络参与者提出投诉,如果他们认为验证者行为不端。在此过程中,提出投诉的参与者必须附上用于选举者提交的行为不端的密码学证据。在`stake_held_for`争议解决期间,网络上的所有验证者检查投诉的有效性,并投票决定是否集体追究(同时确定行为不端证据的合法性和罚款分配)。 + +一旦获得66%验证者批准(通过相等的投票权重衡量),削减罚款将从验证者中扣除,并从验证者的总质押代币中提取。对于处罚和投诉解决的验证过程通常使用 MyTonCtrl 自动进行。 + +## 参阅 + +- [运行全节点(验证者)](/participate/run-nodes/full-node) +- [交易费用](/develop/smart-contracts/fees) +- [什么是区块链?什么是智能合约?什么是 gas ?](https://blog.ton.org/what-is-blockchain) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/blockchain-configs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/blockchain-configs.md new file mode 100644 index 0000000000..8da19f66ee --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/blockchain-configs.md @@ -0,0 +1,554 @@ +# 配置参数 + +:::info +通过 [tonviewer](https://tonviewer.com/config) 读取实时值 +::: + +## 👋 介绍 + +在这个页面上,你可以找到在TON区块链中使用的配置参数的描述。TON有一个复杂的配置,包含许多技术参数:一些被区块链本身使用,一些被生态系统使用。然而,只有少数人理解这些参数的含义。这篇文章是为了提供给用户一种简单的方式来理解这些参数及其目的。 + +## 💡 必要条件 + +本材料旨在与参数列表一起阅读。你可以在 [当前配置](https://explorer.toncoin.org/config) 中查看参数值,并且它们是如何被写入 [cells](/learn/overviews/cells) 的,在 [TL-B](/develop/data-formats/tl-b-language) 格式的 [block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) 文件中有描述。 + +:::info +TON区块链参数末尾的二进制编码是其配置的序列化二进制表示,使得配置的存储或传输更为高效。序列化的确切细节取决于TON区块链使用的特定编码方案。 +::: + +## 🚀 开始吧! + +所有参数都有序排列,你不会迷路。为方便起见,请使用右侧边栏进行快速导航。 + +## 参数 0 + +此参数是一个特殊智能合约的地址,该合约存储区块链的配置。配置存储在合约中,以简化其在验证者投票期间的加载和修改。 + +:::info +在配置参数中,只记录了地址的哈希部分,因为合约始终位于 [masterchain](/learn/overviews/ton-blockchain#masterchain-blockchain-of-blockchains)(工作链 -1)。因此,合约的完整地址将被写为 `-1:<配置参数的值>`。 +::: + +## 参数 1 + +此参数是 [Elector](/develop/smart-contracts/governance#elector) 智能合约的地址,负责任命验证者、分发奖励和对区块链参数的变更进行投票。 + +## 参数 2 + +此参数代表系统的地址,代表系统铸造新的TON并作为奖励发放给验证区块链的验证者。 + +:::info +如果参数 2 缺失,将使用参数 0 替代(新铸造的TON来自于配置智能合约)。 +::: + +## 参数 3 + +此参数是交易费收集者的地址。 + +:::info +如果参数 3 缺失(截至撰写时的情况),交易费将发送至Elector智能合约(参数 1)。 +::: + +## 参数 4 + +此参数是TON网络的根DNS合约地址。 + +:::info +更多详细信息可以在 [TON DNS & Domains](/participate/web3/dns) 文章中找到,并且在 [这里](https://github.com/ton-blockchain/TEPs/blob/master/text/0081-dns-standard.md) 有更详细的原始描述。该合约不负责销售 .ton 域名。 +::: + +## 参数 6 + +此参数负责新代币的铸造费用。 + +:::info +Currently, minting additional currency is not implemented and does not work. The implementation and launch of the minter are planned. + +你可以在 [相关文章](/develop/research-and-development/minter-flow) 中了解更多关于问题和前景。 +::: + +## 参数 7 + +此参数存储流通中的每种额外代币的数量。数据以 [字典](/develop/data-formats/tl-b-types#hashmap-parsing-example)(二叉树;可能在TON开发过程中这种结构被错误地命名为哈希映射)`extracurrency_id -> amount` 的形式存储,数量以 `VarUint 32` - 从 `0` 到 `2^248` 的整数表示。 + +## 参数 8 + +此参数指示网络版本和验证者支持的额外功能。 + +:::info +验证者是区块链网络中负责创建新块和验证交易的节点。 +::: + +- `version`:此字段指定版本。 + +- `capabilities`:此字段是一组标志,用于指示某些功能或能力的存在或缺失。 + +因此,在更新网络时,验证者将投票改变参数 8。这样,TON网络可以在不停机的情况下进行更新。 + +## 参数 9 + +此参数包含一个强制性参数的列表(二叉树)。它确保某些配置参数始终存在,并且在参数 9 变更之前,不能通过提案被删除。 + +## 参数 10 + +此参数代表一份重要TON参数的列表(二叉树),其变更会显著影响网络,因此会举行更多的投票轮次。 + +## 参数 11 + +此参数指出更改TON配置的提案在何种条件下被接受。 + +- `min_tot_rounds` - 提案可应用前的最小轮次数 +- `max_tot_rounds` - 达到此轮次数时提案将自动被拒绝 +- `min_wins` - 所需的胜利次数(3/4的验证者按质押总和计算必须赞成) +- `max_losses` - 达到此失败次数时提案将自动被拒绝 +- `min_store_sec` 和 `max_store_sec` 确定提案被存储的可能的时间间隔 +- `bit_price` 和 `cell_price` 指出存储提案的一个位或一个cell的价格 + +## 参数 12 + +此参数代表TON区块链中工作链的配置。TON区块链中的工作链被设计为独立的区块链,可以并行运行,使TON能够扩展并处理大量的交易和智能合约。 + +## 工作链配置参数 + +- `enabled_since`:启用此工作链的时刻的UNIX时间戳; + +- `actual_min_split`:验证者支持的此工作链的最小拆分(分片)深度; + +- `min_split`:由配置设置的此工作链的最小拆分深度; + +- `max_split`:此工作链的最大拆分深度; + +- `basic`:一个布尔标志位(1表示真,0表示假),指示此工作链是否基础(处理TON币,基于TON虚拟机的智能合约); + +- `active`:一个布尔标志位,指示此工作链当前是否活跃; + +- `accept_msgs`:一个布尔标志位,指示此工作链目前是否接受消息; + +- `flags`:工作链的附加标志位(保留,当前始终为0); + +- `zerostate_root_hash` 和 `zerostate_file_hash`:工作链第一个区块的哈希; + +- `version`:工作链的版本; + +- `format`:工作链的格式,包括 vm_version 和 vm_mode - 那里使用的虚拟机。 + +## 参数 13 + +此参数定义了在 [Elector](/develop/smart-contracts/governance#elector) 合约中对验证者不正确操作提出投诉的成本。 + +## 参数 14 + +此参数代表TON区块链中区块创建的奖励。Nanograms是nanoTON,因此,masterchain中的区块创建奖励等于1.7 TON,而基本工作链中的区块创建奖励为1.0 TON(同时,如果工作链发生拆分,区块奖励也会拆分:如果工作链中有两个分片链,那么分片区块的奖励将是0.5 TON)。 + +## 参数 15 + +此参数包含TON区块链中不同选举阶段和验证者工作的持续时间。 + +对于每个验证期,都有一个等于验证开始时UNIX格式时间的 `election_id`。 +你可以通过调用Elector合约的相应get方法 `active_election_id` 和 `past_election_ids` 获得当前的 `election_id`(如果选举正在进行中)或过去的一个。 + +## 工作链配置参数 + +- `validators_elected_for`:选举出的验证者集合执行其角色的秒数(一轮)。 + +- `elections_start_before`:当前轮次结束前多少秒将开始下一时期的选举过程。 + +- `elections_end_before`:当前轮次结束前多少秒将选择下一轮的验证者。 + +- `stake_held_for`:在轮次过期后,为处理投诉而持有验证者质押的时期。 + +:::info +参数中的每个值都由 `uint32` 数据类型确定。 +::: + +### 示例 + +在TON区块链中,通常将验证周期分为偶数和奇数。这些轮次相互跟随。由于下一轮的投票在前一轮进行,因此验证者需要将资金分为两个池,以有机会参与两轮。 + +#### 主网 + +当前值: + +```python +constants = { + 'validators_elected_for': 65536, # 18.2 hours + 'elections_start_before': 32768, # 9.1 hours + 'elections_end_before': 8192, # 2.2 hours + 'stake_held_for': 32768 # 9.1 hours +} +``` + +方案: + +![image](/img/docs/blockchain-configs/config15-mainnet.png) + +#### 如何计算周期? + +假设 `election_id = validation_start = 1600032768`。那么: + +```python +election_start = election_id - constants['elections_start_before'] = 1600032768 - 32768 = 1600000000 +election_end = delay_start = election_id - constants['elections_end_before'] = 1600032768 - 8192 = 1600024576 +hold_start = validation_end = election_id + constants['validators_elected_for'] = 1600032768 + 65536 = 1600098304 +hold_end = hold_start + constants['stake_held_for'] = 1600098304 + 32768 = 1600131072 +``` + +因此,目前,一个奇偶性轮次的长度为 `1600131072 - 1600000000 = 131072秒 = 36.40888...小时` + +#### 测试网 + +##### 当前值: + +```python +constants = { + 'validators_elected_for': 7200, # 2 hours + 'elections_start_before': 2400, # 40 minutes + 'elections_end_before': 180, # 3 minutes + 'stake_held_for': 900 # 15 minutes +} +``` + +##### 方案 + +![image](/img/docs/blockchain-configs/config15-testnet.png) + +###### 如何计算周期? + +假设 `election_id = validation_start = 160002400`。那么: + +```python +election_start = election_id - constants['elections_start_before'] = 160002400 - 2400 = 1600000000 +election_end = delay_start = election_id - constants['elections_end_before'] = 160002400 - 180 = 160002220 +hold_start = validation_end = election_id + constants['validators_elected_for'] = 160002400 + 7200 = 160009600 +hold_end = hold_start + constants['stake_held_for'] = 160009600 + 900 = 160010500 +``` + +因此,目前,一个奇偶性轮次的长度为 `160010500 - 1600000000 = 10500秒 = 175分钟 = 2.91666...小时` + +## 参数 16 + +此参数代表TON区块链中验证者数量的限制。它直接被Elector智能合约使用。 + +### 选举中验证者数量的配置参数: + +- `max_validators`:此参数代表任何给定时间可以参与网络运营的验证者的最大数量。 + +- `max_main_validators`:此参数代表主链验证者的最大数量。 + +- `min_validators`:此参数代表必须支持网络运营的最小验证者数量。 + +1. 验证者的最大数量应大于或等于主链验证者的最大数量。 +2. 主链验证者的最大数量必须大于或等于验证者的最小数量。 +3. 验证者的最小数量不得少于1。 + +## 参数 17 + +此参数代表TON区块链中的质押参数配置。在许多区块链系统中,特别是使用权益证明或委托权益证明共识算法的系统中,网络原生加密货币的所有者可以“质押”他们的代币成为验证者并获得奖励。 + +## 配置参数: + +- `min_stake`:此参数代表有兴趣参与验证过程的一方需要质押的TON的最小金额。 + +- `max_stake`:此参数代表有兴趣参与验证过程的一方可以质押的TON的最大金额。 + +- `min_total_stake`:此参数代表被选中的验证者集合必须持有的TON的最小总金额。 + +- `max_stake_factor`:此参数是一个乘数,指示最大有效质押(抵押)可以超过任何其他验证者发送的最小质押的多少倍。 + +:::info +参数中的每个值都由 `uint32` 数据类型确定。 +::: + +## 参数 18 + +此参数代表确定TON区块链中数据存储价格的配置。这作为一种防止垃圾信息的措施,并鼓励网络维护。 + +### 存储费用参数的字典: + +- `utime_since`:此参数提供指定价格适用的初始Unix时间戳。 + +- `bit_price_ps` 和 `cell_price_ps`:这些参数代表TON区块链主工作链上存储一个位或一个cell的信息65536秒的存储价格 + +- `mc_bit_price_ps` 和 `mc_cell_price_ps`:这些参数特别代表在TON主链上计算资源的价格,同样为65536秒 + +:::info + +`utime_since` 接受 `uint32` 数据类型的值。 + +其余接受 `uint64` 数据类型的值。 +::: + +## 参数 20 和 21 + +这些参数定义了TON网络中计算的成本。任何计算的复杂性都以gas单位估计。 + +- `flat_gas_limit` 和 `flat_gas_price`:提供了一定数量的起始gas,价格为 `flat_gas_price`(用于抵消启动TON虚拟机的成本)。 + +- `gas_price`:此参数反映了网络中gas的价格,单位是每65536gas单位的nanotons。 + +- `gas_limit`:此参数代表每笔交易可消耗的最大gas量。 + +- `special_gas_limit`:此参数代表特殊(系统)合约每笔交易可消耗的gas量限制。 + +- `gas_credit`:此参数代表提供给交易的gas单位信用额,用于检查外部消息。 + +- `block_gas_limit`:此参数代表单个区块内可消耗的最大gas量。 + +- `freeze_due_limit` 和 `delete_due_limit`:合约被冻结和删除的累积存储费用(nanotons)的限制。 + +:::info +更多关于 `gas_credit` 和外部消息的其他参数的信息在 [这里](/develop/smart-contracts/guidelines/accept#external-messages) 。 +::: + +## 参数 22 和 23 + +这些参数设置了区块的限制,达到这些限制时,区块将被完成,剩余消息的回调(如果有的话)将延续到下一个区块。 + +### 配置参数: + +- `bytes`:此部分设置了区块大小的字节限制。 + +- `underload`:负载不足是指分片意识到没有负载,并倾向于合并(如果相邻的分片愿意的话)。 + +- `soft_limit`:软限制 - 达到此限制时,内部消息停止处理。 + +- `hard_limit`:硬限制 - 这是绝对最大大小。 + +- `gas`:此部分设置了区块可以消耗的gas量限制。在区块链中,gas是计算工作的指标。对于大小(字节),负载不足、软限制和硬限制的限制方式相同。 + +- `lt_delta`:此部分设置了第一个交易和最后一个交易之间逻辑时间差的限制。逻辑时间是TON区块链用于事件排序的概念。对于大小(字节)和gas,负载不足、软限制和硬限制的限制方式相同。 + +:::info +在分片上负载不足,相应地,希望与neighbor合并的情况下,`soft_limit` 定义了一种状态,即超过此状态后内部消息停止处理,但外部消息继续处理。外部消息处理直到达到 `(soft_limit + hard_limit)/2` 的限制。 +::: + +## 参数 24 和 25 + +参数 24 代表了TON区块链主链中发送消息的成本配置。 + +参数 25 代表了所有其他情况下发送消息的成本配置。 + +### 定义转发成本的配置参数: + +- `lump_price`:此参数表示转发消息的基础价格,无论其大小或复杂性如何。 + +- `bit_price`:此参数代表每位消息转发的成本。 + +- `cell_price`:此参数反映了每个cell消息转发的成本。cell是TON区块链上数据存储的基本单位。 + +- `ihr_price_factor`:用于计算即时超立方路由(IHR)成本的因子。 + :::info + IHR是TON区块链网络中的一种消息传递方法,消息直接发送到接收方的分片链。 + ::: + +- `first_frac`:此参数定义了沿消息路线的第一次转换将使用的剩余的remainder的部分。 + +- `next_frac`:此参数定义了沿消息路线的后续转换将使用的剩余的remainder的部分。 + +## 参数 28 + +此参数提供了TON区块链中Catchain协议的配置。Catchain是TON中用于在验证者之间达成一致的最低层层共识协议。 + +### 配置参数: + +- `flags`:一个通用字段,可用于设置各种二进制参数。在这种情况下,它等于0,这意味着没有设置特定的标志。 + +- `shuffle_mc_validators`:一个布尔值,指示是否打乱主链验证者。如果此参数设置为1,则验证者将被打乱;否则,他们不会。 + +- `mc_catchain_lifetime`:主链catchain组的寿命(秒)。 + +- `shard_catchain_lifetime`:分片链catchain组的寿命(秒)。 + +- `shard_validators_lifetime`:分片链验证者组的寿命(秒)。 + +- `shard_validators_num`:每个分片链验证组的验证者数量。 + +## 参数 29 + +此参数提供了TON区块链中catchain([参数 28](#param-28))上层共识协议的配置。共识协议是区块链网络的关键组成部分,确保所有节点在分布式账本的状态上达成一致。 + +### 配置参数: + +- `flags`:一个通用字段,可用于设置各种二进制参数。在这种情况下,它等于0,这意味着没有设置特定的标志。 + +- `new_catchain_ids`:一个布尔值,指示是否生成新的Catchain标识符。如果此参数设置为1,则将生成新的标识符。在这种情况下,它被赋值为1,这意味着将生成新的标识符。 + +- `round_candidates`:共识协议每轮考虑的候选人数量。这里设置为3。 + +- `next_candidate_delay_ms`:在生成区块候选权转移到下一个验证者之前的延迟(毫秒)。这里设置为2000毫秒(2秒)。 + +- `consensus_timeout_ms`:区块共识的超时时间(毫秒)。这里设置为16000毫秒(16秒)。 + +- `fast_attempts`:达成共识的“快速”尝试次数。这里设置为3。 + +- `attempt_duration`:每次达成一致的尝试持续时间。这里设置为8。 + +- `catchain_max_deps`:Catchain区块的最大依赖数量。这里设置为4。 + +- `max_block_bytes`:区块的最大大小(字节)。这里设置为2097152字节(2MB)。 + +- `max_collated_bytes`:序列化的区块正确性证明的最大大小(字节)。这里设置为2097152字节(2MB)。 + +- `proto_version`:协议版本。这里设置为2。 + +- `catchain_max_blocks_coeff`:Catchain中区块生成速率的限制系数,[描述](https://github.com/ton-blockchain/ton/blob/master/doc/catchain-dos.md)。这里设置为10000。 + +## 参数 31 + +此参数代表来自以下智能合约地址的配置,这些地址不收取gas或存储费用,可以创建tick-tok交易。这个列表通常包括治理合约。该参数以二叉树结构呈现——一个树(HashMap 256),其中键是地址的256位表示。此列表中只能出现主链中的地址。 + +## 参数 32、34 和 36 + +来自上一轮(32)、当前轮(34)和下一轮(36)的验证者列表。参数 36 负责从选举结束到轮次开始时设置。 + +### 配置参数: + +- `cur_validators`:这是当前的验证者列表。验证者通常负责在区块链网络中验证交易。 + +- `utime_since` 和 `utime_until`:这些参数提供了这些验证者活跃的时间段。 + +- `total` 和 `main`:这些参数提供了网络中验证者的总数和验证主链的验证者数量。 + +- `total_weight`:这将验证者的权重加起来。 + +- `list`:一个验证者的树状列表 `id->validator-data`:`validator_addr`、`public_key`、`weight`、`adnl_addr`:这些参数提供了每个验证者的详细信息 - 他们在主链中的256地址、公钥、权重、ADNL地址(TON网络层使用的地址)。 + +## 参数 40 + +此参数定义了对不当行为(非验证)的惩罚结构的配置。在没有此参数的情况下,默认罚款大小为101 TON。 + +## 配置参数: + +**`MisbehaviourPunishmentConfig`**:此数据结构定义了如何惩罚系统中的不当行为。 + +它包含几个字段: + +- `default_flat_fine`:这部分罚款与质押大小无关。 + +- `default_proportional_fine`:这部分罚款与验证者的质押大小成比例。 + +- `severity_flat_mult`:这是应用于验证者重大违规行为的 `default_flat_fine` 值的乘数。 + +- `severity_proportional_mult`:这是应用于验证者重大违规行为的 `default_proportional_fine` 值的乘数。 + +- `unpunishable_interval`:此参数代表违规者不受惩罚的期间,以消除临时网络问题或其他异常。 + +- `long_interval`、`long_flat_mult`、`long_proportional_mult`:这些参数定义了一个“长”时间段及其对不当行为的持平和比例罚款的乘数。 + +- `medium_interval`、`medium_flat_mult`、`medium_proportional_mult`:同样,它们定义了一个“中等”时间段及其对不当行为的持平和比例罚款的乘数。 + +## 参数 43 + +此参数涉及帐户和消息的各种大小限制和其他特性。 + +### 配置参数: + +- `max_msg_bits`:消息的最大大小(位)。 + +- `max_msg_cells`:消息可以占用的最大cell数(存储单位的一种形式)。 + +- `max_library_cells`:用于库cell的最大cell数。 + +- `max_vm_data_depth`:消息和账户状态中的最大cell深度。 + +- `max_ext_msg_size`:外部消息的最大大小(位)。 + +- `max_ext_msg_depth`:外部消息的最大深度。这可能是指消息内部数据结构的深度。 + +- `max_acc_state_cells`:帐户状态可以占用的最大cell数。 + +- `max_acc_state_bits`:帐户状态的最大大小(位)。 + +如果缺失,默认参数为: + +- `max_size` = 65535 +- `max_depth` = 512 +- `max_msg_bits` = 1 \<\< 21 +- `max_msg_cells` = 1 \<\< 13 +- `max_library_cells` = 1000 +- `max_vm_data_depth` = 512 +- `max_acc_state_cells` = 1 \<\< 16 +- `max_acc_state_bits` = (1 \<\< 16) \* 1023 + +:::info +您可以在源代码[这里](https://github.com/ton-blockchain/ton/blob/fc9542f5e223140fcca833c189f77b1a5ae2e184/crypto/block/mc-config.h#L379)查看有关标准参数的更多详情。 +::: + +## 参数 44 + +此参数定义了被暂停的地址列表,这些地址在`suspended_until`之前不能被初始化。它仅适用于尚未启动的账户。这是稳定代币经济学的一种措施(限制早期矿工)。如果未设置 - 则没有限制。每个地址都表示为此树的一个终端节点,树状结构允许有效地检查地址在列表中的存在与否。 + +:::info +代币经济学的稳定进一步在“The Open Network” Telegram 频道的[官方报告](https://t.me/tonblockchain/178)中描述。 +::: + +## 参数 71 - 73 + +此参数涉及在其他网络中包装TON的桥: + +``` +precompiled_smc#b0 gas_usage:uint64 = PrecompiledSmc; +precompiled_contracts_config#c0 list:(HashmapE 256 PrecompiledSmc) = PrecompiledContractsConfig; +_ PrecompiledContractsConfig = ConfigParam 45; +``` + +预编译合约的更多详情请查看[本页面](/develop/smart-contracts/core-contracts/precompil)。 + +## 参数 71 - 73 + +此参数涉及在其他网络中封装TON的桥: + +- ETH-TON \*\* (71)\*\* +- BSC-TON \*\* (72) \*\* +- Polygon-TON \*\* (73) \*\* + +### 配置参数: + +- `bridge_address`:这是接受TON以在其他网络中发行包装的TON的桥合约地址。 + +- `oracle_multisig_address`: 这是 bridge 管理钱包地址。 多重钱包是一种数字钱包类型,需要多方签名授权交易。 它常常被用来加强安全,Oracle充当这些方面的角色。 + +- `oracles`:以树形结构 `id->address` 的形式列出的预言机 + +- `external_chain_address`:对应外部区块链中的桥合约地址。 + +## 参数 79, 81 和 82 + +此参数涉及从其他网络中包装代币到TON网络上的代币的桥: + +- ETH-TON \*\* (79) \*\* +- BSC-TON \*\* (81) \*\* +- Polygon-TON \*\* (82) \*\* + +### 配置参数: + +- `bridge_address` 和 `oracles_address`:这些是桥和桥管理合约(预言机多签)的区块链地址。 + +- `oracles`:以树形结构 `id->address` 的形式列出的预言机 + +- `state_flags`:状态标志。该参数负责启用/禁用不同的 bridge 功能。 + +- `prices`:此参数包含用于桥的不同操作或费用的价格列表或字典,例如 `bridge_burn_fee`、`bridge_mint_fee`、`wallet_min_tons_for_storage`、`wallet_gas_consumption`、`minter_min_tons_for_storage`、`discover_gas_consumption`。 + +- `external_chain_address`:另一区块链中的桥合约地址。 + +## 负参数 + +:::info +负参数与正参数的区别在于需要验证者的验证;它们通常没有特定分配的角色。 +::: + +## 下一步 + +在深入研究本文后,强烈建议您花时间详细研究以下文档: + +- [whitepaper.pdf](https://ton.org/whitepaper.pdf) 和 [tblkch.pdf](/tblkch.pdf) 中的原始但有限的描述。 + +- [mc-config.h](https://github.com/ton-blockchain/ton/blob/fc9542f5e223140fcca833c189f77b1a5ae2e184/crypto/block/mc-config.h),[block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) 和 [BlockMasterConfig 类型](https://docs.evercloud.dev/reference/graphql-api/field_descriptions#blockmasterconfig-type)。 + +## 📖 参阅 + +在此页面上,您可以找到TON区块链的活动网络配置: + +- 主网:https://ton.org/global-config.json +- 测试网:https://ton.org/testnet-global.config.json +- [俄语版本](https://github.com/delovoyhomie/description-config-for-TON-Blockchain/blob/main/Russian-version.md)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/config-params.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/config-params.md new file mode 100644 index 0000000000..de4e6fec1a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/config-params.md @@ -0,0 +1,320 @@ +# 更改参数 + +本文档旨在简要解释TON区块链的配置参数,并提供通过大多数验证者共识更改这些参数的逐步指南。 + +我们假设读者已经熟悉[Fift](/develop/fift/overview)和[轻客户端](/participate/nodes/lite-client),如[FullNode-HOWTO (低级)](/participate/nodes/full-node)和[Validator-HOWTO (低级)](/participate/nodes/validator)中所述,其中描述了验证者为配置提案投票的部分。 + +## 1. 配置参数 +**配置参数**是影响验证者和/或TON区块链基本智能合约行为的某些值。所有配置参数的当前值存储为主链状态的特殊部分,并在需要时从当前主链状态中提取。因此,讲到配置参数的值时要考虑到某个特定的主链区块。每个分片链区块都包含对最新已知主链区块的引用;假定相应主链状态中的值对此分片链区块是有效的,并在其生成和验证过程中使用。对于主链区块,使用上一个主链区块的状态来提取有效的配置参数。因此,即使有人试图在主链区块中更改某些配置参数,这些更改也只会在下一个主链区块中生效。 + +每个配置参数由一个有符号的32位整数索引标识,称为**配置参数索引**或简称**索引**。配置参数的值始终是一个cell。某些配置参数可能会缺失;那时有时假定此参数的值为`Null`。还有一个**强制性**配置参数列表必须始终存在;此列表存储在配置参数`#10`中。 + +所有配置参数组合成一个**配置字典** - 一个带有有符号32位键(配置参数索引)和值(由一个cell引用组成)的哈希映射。换句话说,配置字典是TL-B类型的值(`HashmapE 32 ^Cell`)。实际上,所有配置参数的集合作为TL-B类型`ConfigParams`的值存储在主链状态中: + +``` +_ config_addr:bits256 config:^(Hashmap 32 ^Cell) = ConfigParams; +``` + +我们看到,除了配置字典外,`ConfigParams`还包含`config_addr` - 主链上配置智能合约的256位地址。稍后将提供有关配置智能合约的更多细节。 + +通过特殊的TVM寄存器`c7`,所有智能合约在其交易代码执行时都可以访问包含所有配置参数有效值的配置字典。更准确地说,当执行智能合约时,`c7`被初始化为一个元组,其唯一元素是一个包含几个执行智能合约时有用的“context”值的元组,例如当前Unix时间(如块头中所注册)。此元组的第十个条目(即,以零为基索引的索引9)包含代表配置字典的cell。因此,可以通过TVM指令`PUSH c7; FIRST; INDEX 9`或等效指令`CONFIGROOT`来访问它。事实上,特殊的TVM指令`CONFIGPARAM`和`CONFIGOPTPARAM`将前述操作与字典查找结合起来,通过其索引返回任何配置参数。我们推荐参考TVM文档以获取更多关于这些指令的详细信息。这里相关的是所有配置参数都可以从所有智能合约(主链或分片链)中轻松访问,并且智能合约可以检查并使用它们来执行特定检查。例如,智能合约可能会从配置参数中提取工作链数据存储价格,以计算存储用户提供数据的价格。 + +配置参数的值不是任意的。实际上,如果配置参数索引`i`为非负,则此参数的值必须是TL-B类型(`ConfigParam i`)的有效值。验证者强制执行此限制,不会接受对非负索引的配置参数的更改,除非它们是相应TL-B类型的有效值。 + +因此,此类参数的结构在源文件`crypto/block/block.tlb`中定义,其中为不同的`i`值定义了(`ConfigParam i`)。例如, + +``` +_ config_addr:bits256 = ConfigParam 0; +_ elector_addr:bits256 = ConfigParam 1; +_ dns_root_addr:bits256 = ConfigParam 4; // root TON DNS resolver + +capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion; +_ GlobalVersion = ConfigParam 8; // all zero if absent +``` + +我们看到配置参数`#8`包含一个没有引用且恰好有104个数据位的cell。前四位必须是`11000100`,然后存储32位当前启用的“全局版本”,随后是对应当前启用能力的64位整数标志。所有配置参数的更详细描述将在TON区块链文档的附录中提供;目前,可以检查`crypto/block/block.tlb`中的TL-B方案并检查验证者源代码中不同参数的使用方式。 + +与具有非负索引的配置参数相反,具有负索引的配置参数可以包含任意值。至少,验证者不会对其值强加任何限制。因此,它们可用于存储重要信息(例如,某些智能合约必须开始操作的Unix时间),该信息对于块生成不是关键,但被一些基本智能合约使用。 + +## 2. 更改配置参数 + +我们已经解释了当前配置参数的值是如何存储在主链状态的特殊部分中的。它们是如何更改的? + +事实上,主链中有一个特殊的智能合约称为**配置智能合约**。其地址由`ConfigParams`中的`config_addr`字段确定,我们之前已经描述过了。其数据中的第一个cell引用必须包含所有配置参数的最新副本。当生成新的主链区块时,会通过其地址`config_addr`查找配置智能合约,并从其数据的第一个cell引用中提取新的配置字典。在进行一些有效性检查后(例如,验证具有非负32位索引`i`的任何值确实是TL-B类型(`ConfigParam i`)的有效值),验证者将此新配置字典复制到包含ConfigParams的主链部分。在创建所有交易之后执行此操作,因此只检查配置智能合约中的新配置字典的最终版本。如果有效性检查失败,则“真实”的配置字典保持不变。通过这种方式,配置智能合约无法安装无效的配置参数值。如果新配置字典与当前配置字典一致,则不执行检查也不做更改。 + +通过这种方式,所有配置参数的更改都由配置智能合约执行,其代码决定更改配置参数的规则。当前,配置智能合约支持两种更改配置参数的模式: + +1) 通过由特定私钥签名的外部消息,该私钥对应于存储在配置智能合约数据中的公钥。这是公共测试网和可能由一个实体控制的较小私有测试网络所采用的方法,因为它使运营商能够轻松更改任何配置参数的值。请注意,这个公钥可以通过一个由旧密钥签名的特殊外部消息更改,如果它被更改为零,则此机制被禁用。因此,可以在启动后立即进行微调,然后永久禁用它。 +2) 通过创建“配置提案(configuration proposals)”,然后由验证者对其投票或反对。通常,配置提案必须在一个轮次中收集超过3/4的所有验证者(按权重)的投票,并且不仅在一个轮次中,而且在几个轮次中(即,连续几组验证者必须确认提议的参数更改)。这是TON区块链主网将采用的分布式治理机制。 + +我们希望更详细地描述第二种更改配置参数的方式。 + +## 3. 创建配置提案 + +新的**配置提案**包含以下数据: +- 要更改的配置参数的索引 +- 配置参数的新值(或Null,如果要删除) +- 提案的过期Unix时间 +- 标志位提案是**关键**还是非关键 +- 可选的**旧值哈希**,带有当前值的cell哈希(仅当当前值具有指定哈希时,提案才能被激活) + +任何在主链上拥有钱包的人都可以创建新的配置提案,前提是他支付足够的费用。但是,只有验证者可以对现有的配置提案投票或反对。 + +请注意,有**关键**和**普通**配置提案。关键配置提案可以更改任何配置参数,包括所谓的关键配置参数之一(关键配置参数列表存储在配置参数`#10`中,它本身是关键的)。然而,创建关键配置提案的成本更高,通常需要在更多轮次中收集更多验证者的投票(普通和关键配置提案的确切投票要求存储在关键配置参数`#11`中)。另一方面,普通配置提案更便宜,但它们不能更改关键配置参数。 + +为了创建新的配置提案,首先必须生成一个包含提议的新值的BoC(cell包)文件。这样做的确切方式取决于要更改的配置参数。例如,如果我们想创建包含UTF-8字符串"TEST"(即`0x54455354`)的参数`-239`,我们可以如下创建`config-param-239.boc`:调用Fift,然后输入 + +``` + 2 boc+>B "config-param-239.boc" B>file +bye +``` + + +结果,将创建一个21字节的文件`config-param-239.boc`,包含所需值的序列化。 + +对于更复杂的情况,尤其是对于具有非负索引的配置参数,这种简单的方法不容易适用。我们建议使用`create-state`(在构建目录中作为`crypto/create-state`可用)而不是`fift`,并复制和编辑源文件`crypto/smartcont/gen-zerostate.fif`和`crypto/smartcont/CreateState.fif`的适当部分,通常用于创建TON区块链的零状态(对应于其他区块链架构的“创世块”)。 + +例如,考虑配置参数`#8`,其中包含当前启用的全局区块链版本和能力: + +``` +capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion; +_ GlobalVersion = ConfigParam 8; +``` + +我们可以通过运行轻客户端并输入`getconfig 8`来检查其当前值: + +``` +> getconfig 8 +... +ConfigParam(8) = ( + (capabilities version:1 capabilities:6)) + +x{C4000000010000000000000006} +``` + +现在假设我们想要启用位`#3`(`+8`)表示的能力,即`capReportVersion`(启用时,此能力会迫使所有 collator 在其生成的块头中报告其支持的版本和能力)。因此,我们想要`version=1`和`capabilities=14`。在这个例子中,我们仍然可以猜测正确的序列化并直接通过Fift创建BoC文件。 + +``` +x{C400000001000000000000000E} s>c 2 boc+>B "config-param8.boc" B>file +``` + +(结果创建了一个包含所需值的30字节文件`config-param8.boc`。) + +然而,在更复杂的情况下,这可能不是一个选项,所以让我们以不同的方式做这个例子。也就是说,我们可以检查源文件`crypto/smartcont/gen-zerostate.fif`和`crypto/smartcont/CreateState.fif`中的相关部分。 + +``` +// version capabilities -- +{ 8 config! } : config.version! +1 constant capIhr +2 constant capCreateStats +4 constant capBounceMsgBody +8 constant capReportVersion +16 constant capSplitMergeTransactions +``` + +和 + +``` +// version capabilities +1 capCreateStats capBounceMsgBody or capReportVersion or config.version! +``` + +我们看到,`config.version!`没有最后的`8 config!`实际上就是我们需要的,所以我们可以创建一个临时Fift脚本,例如,`create-param8.fif`: +``` +#!/usr/bin/fift -s +"TonUtil.fif" include + +1 constant capIhr +2 constant capCreateStats +4 constant capBounceMsgBody +8 constant capReportVersion +16 constant capSplitMergeTransactions +{ } : prepare-param8 + +// 为配置参数#8创建新值 +1 capCreateStats capBounceMsgBody or capReportVersion or prepare-param8 +// 检查此值的有效性 +dup 8 is-valid-config? not abort"not a valid value for chosen configuration parameter" +// 打印 +dup ."Serialized value = " B $1 tuck B>file +."(Saved into file " type .")" cr +``` + +现在,如果我们运行`fift -s create-param8.fif config-param8.boc`或者更好地从构建目录运行`crypto/create-state -s create-param8.fif config-param8.boc`,我们看到以下输出: + +``` +Serialized value = x{C400000001000000000000000E} +(Saved into file config-param8.boc) +``` + +我们获得与之前相同内容的30字节文件`config-param8.boc`。 + +一旦我们有了一个包含配置参数所需值的文件,我们就调用目录`crypto/smartcont`中找到的脚本`create-config-proposal.fif`,带有适当的参数。同样,我们建议使用`create-state`(在构建目录中作为`crypto/create-state`可用)而不是`fift`,因为它是Fift的一个特殊扩展版本,能够进行更多与区块链相关的有效性检查: + +``` +$ crypto/create-state -s create-config-proposal.fif 8 config-param8.boc -x 1100000 + + +Loading new value of configuration parameter 8 from file config-param8.boc +x{C400000001000000000000000E} + +Non-critical configuration proposal will expire at 1586779536 (in 1100000 seconds) +Query id is 6810441749056454664 +resulting internal message body: x{6E5650525E838CB0000000085E9455904_} + x{F300000008A_} + x{C400000001000000000000000E} + +B5EE9C7241010301002C0001216E5650525E838CB0000000085E9455904001010BF300000008A002001AC400000001000000000000000ECD441C3C +(a total of 104 data bits, 0 cell references -> 59 BoC data bytes) +(Saved to file config-msg-body.boc) +``` + +我们获得了一个要从主链上任何(钱包)智能合约以适量的Toncoin发送到配置智能合约的内部消息的正文。配置智能合约的地址可以通过在轻客户端中输入`getconfig 0`获得: +``` +> getconfig 0 +ConfigParam(0) = ( config_addr:x5555555555555555555555555555555555555555555555555555555555555555) +x{5555555555555555555555555555555555555555555555555555555555555555} +``` +我们看到配置智能合约的地址是`-1:5555...5555`。通过运行此智能合约的适当get方法,我们可以找出创建此配置提案所需的付款金额: + +``` +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 proposal_storage_price 0 1100000 104 0 + +arguments: [ 0 1100000 104 0 75077 ] +result: [ 2340800000 ] +remote result (not to be trusted): [ 2340800000 ] +``` + +get方法`proposal_storage_price`的参数是关键标志位(本例中为0),此提案将处于活动状态的时间间隔(1.1百万秒),数据中的位总数(104)和cell引用(0)。后两个数量可以在`create-config-proposal.fif`的输出中看到。 + +我们看到,创建此提案需要支付2.3408 Toncoin。最好添加至少1.5 Tonoin到消息中以支付处理费,所以我们打算发送4 Toncoin连同请求(所有多余的Toncoin将退回)。现在我们使用`wallet.fif`(或我们正在使用的钱包对应的Fift脚本)从我们的钱包向配置智能合约创建一个携带4 Toncoin和`config-msg-body.boc`中的正文的转账。这通常看起来像: + +``` +$ fift -s wallet.fif my-wallet -1:5555555555555555555555555555555555555555555555555555555555555555 31 4. -B config-msg-body.boc + +Transferring GR$4. to account kf9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQft = -1:5555555555555555555555555555555555555555555555555555555555555555 seqno=0x1c bounce=-1 +Body of transfer message is x{6E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +signing message: x{0000001C03} + x{627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +resulting external message: x{89FE000000000000000000000000000000000000000000000000000000000000000007F0BAA08B4161640FF1F5AA5A748E480AFD16871E0A089F0F017826CDC368C118653B6B0CEBF7D3FA610A798D66522AD0F756DAEECE37394617E876EFB64E9800000000E01C_} + x{627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944_} + x{F300000008A_} + x{C400000001000000000000000E} + +B5EE9C724101040100CB0001CF89FE000000000000000000000000000000000000000000000000000000000000000007F0BAA08B4161640FF1F5AA5A748E480AFD16871E0A089F0F017826CDC368C118653B6B0CEBF7D3FA610A798D66522AD0F756DAEECE37394617E876EFB64E9800000000E01C010189627FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA773594000000000000000000000000000006E5650525E835154000000085E9293944002010BF300000008A003001AC400000001000000000000000EE1F80CD3 +(Saved to file wallet-query.boc) +``` + +现在我们通过轻客户端发送外部消息`wallet-query.boc`。 + +``` +> sendfile wallet-query.boc +.... +external message status is 1 +``` + +等待一段时间后,我们可以检查我们钱包的传入消息以检查来自配置智能合约的响应消息, + +或者如果我们感到幸运,简单地通过配置智能合约的方法`list_proposals`检查所有活跃配置提案的列表。 + +``` +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 list_proposals +... +arguments: [ 107394 ] +result: [ ([64654898543692093106630260209820256598623953458404398631153796624848083036321 [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0]]) ] +remote result (not to be trusted): [ ([64654898543692093106630260209820256598623953458404398631153796624848083036321 [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0]]) ] +... caching cell FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC +``` + +我们看到所有活跃配置提案的列表只由一个条目组成,由一对表示。 +``` +[6465...6321 [1586779536 0 [8 C{FDCD...} -1] 1124...2998 () 8646...209 3 0 0]] +``` +第一个数字`6465..6321`是配置提案的唯一标识符,等于其256位哈希。这对的第二个组成部分是一个元组,描述了此配置提案的状态。此元组的第一个组成部分是配置提案的过期Unix时间(`1586779546`)。第二个组成部分(`0`)是关键性标志。接下来是配置提案本身,由三元组`[8 C{FDCD...} -1]`描述,其中`8`是要修改的配置参数索引,`C{FDCD...}`是带有新值的cell(由此cell的哈希表示),`-1`是此参数旧值的可选哈希(`-1`表示未指定此哈希)。接下来我们看到一个大数字`1124...2998`,表示当前验证者集的标识符,然后是一个空列表`()`,表示到目前为止已经投票支持此提案的所有当前活跃验证者的集合,然后是`weight_remaining`等于`8646...209` - 一个正数,如果提案在本轮中还没有收集到足够的验证者投票,则为负数。然后我们看到三个数字:`3 0 0`。这些数字分别是`rounds_remaining`(此提案最多在三轮中存活,即,当前验证者集更换次数),`wins`(提案在一轮中收集到超过3/4所有验证者的投票次数)和`losses`(提案未能在一轮中收集到3/4所有验证者的投票次数)。 + +我们可以通过让轻客户端展开cell`C{FDCD...}`来检查配置参数 `#8` 的建议值,使用它的哈希值`FDCD... `或这个哈希值的足够长的前缀来唯一标识所讨论的cell: +``` +> dumpcell FDC +C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} = + x{C400000001000000000000000E} +``` +我们看到值为`x{C400000001000000000000000E}`,这确实是我们嵌入到配置提案中的值。我们甚至可以要求轻客户端将此cell显示为TL-B类型(`ConfigParam 8`)的值。 +``` +> dumpcellas ConfigParam8 FDC +dumping cells as values of TLB type (ConfigParam 8) +C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} = + x{C400000001000000000000000E} +( + (capabilities version:1 capabilities:14)) +``` +当我们考虑由其他人创建的配置提案时,这特别有用。 + +请注意,从现在起,配置提案由其256位哈希标识 - 巨大的十进制数字`6465...6321`。我们可以通过运行配置智能合约的get方法`get_proposal`来检查特定配置提案的当前状态,参数只需等于配置提案的标识符: +``` +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 get_proposal 64654898543692093106630260209820256598623953458404398631153796624848083036321 +... +arguments: [ 64654898543692093106630260209820256598623953458404398631153796624848083036321 94347 ] +result: [ [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 () 864691128455135209 3 0 0] ] +``` +我们获得与之前相同的结果,但仅针对一个配置提案,开头没有配置提案的标识符。 + +## 4. 为配置提案投票 + +一旦创建了配置提案,它就应该在当前轮次中收集到超过3/4的当前验证者(按权重,即按股权)的投票,可能还要在几个后续轮次(选举的验证者集)中。通过这种方式,更改配置参数的决定必须得到不仅是当前验证者集,而且是几个后续验证者集的显著多数的批准。 + +为配置提案投票仅适用于当前验证者,其永久公钥列在配置参数`#34`中。过程大致如下: + +- 验证者的运营商查找他的验证者在配置参数`#34`中存储的当前验证者集中的(0-based)索引`val-idx`。 +- 运营商调用目录`crypto/smartcont`中找到的特殊Fift脚本`config-proposal-vote-req.fif`,指出`val-idx`和`config-proposal-id`作为其参数: +``` + $ fift -s config-proposal-vote-req.fif -i 0 64654898543692093106630260209820256598623953458404398631153796624848083036321 + Creating a request to vote for configuration proposal 0x8ef1603180dad5b599fa854806991a7aa9f280dbdb81d67ce1bedff9d66128a1 on behalf of validator with index 0 + 566F744500008EF1603180DAD5B599FA854806991A7AA9F280DBDB81D67CE1BEDFF9D66128A1 + Vm90RQAAjvFgMYDa1bWZ-oVIBpkaeqnygNvbgdZ84b7f-dZhKKE= + Saved to file validator-to-sign.req +``` +- 在此之后,必须使用`sign 566F744...28A1` 命令通过连接到验证者的 `validator-engine-console` ,用当前验证者的私钥对投票请求进行签名。这个过程类似于[验证者操作指南](/participate/nodes/validator)中描述的参与验证者选举的过程,但这次必须使用当前活跃的密钥。 +- 接下来,必须调用另一个脚本 `config-proposal-signed.fif`。它的参数与 `config-proposal-req.fif` 类似,但它需要两个额外的参数:用于签名投票请求的公钥的 base64 表示,以及签名本身的 base64 表示。这与[验证者操作指南](/participate/nodes/validator)中描述的过程非常类似。 +- 通过这种方式,创建了包含签名投票的配置提案的内部消息体的文件 `vote-msg-body.boc`。 +- 在此之后,`vote-msg-body.boc` 必须通过任何驻留在主链中的智能合约(通常使用验证者的控制智能合约)携带一小笔 Toncoin 进行处理(通常,1.5 Toncoin 应该就足够)被发送出去。这再次与验证者选举期间采用的程序完全相似。这通常通过运行以下命令来实现: +``` +$ fift -s wallet.fif my_wallet_id -1:5555555555555555555555555555555555555555555555555555555555555555 1 1.5 -B vote-msg-body.boc +``` +(如果使用简单钱包来控制验证者),然后从轻客户端发送生成的文件 `wallet-query.boc`: + +``` +> sendfile wallet-query.boc +``` + +你可以监控配置智能合约到控制智能合约的答复消息,以了解你的投票查询的状态。或者,你可以通过配置智能合约的 get-method `show_proposal` 来检查配置提案的状态: +``` +> runmethod -1:5555555555555555555555555555555555555555555555555555555555555555 get_proposal 64654898543692093106630260209820256598623953458404398631153796624848083036321 +... +arguments: [ 64654898543692093106630260209820256598623953458404398631153796624848083036321 94347 ] +result: [ [1586779536 0 [8 C{FDCD887EAF7ACB51DA592348E322BBC0BD3F40F9A801CB6792EFF655A7F43BBC} -1] 112474791597373109254579258586921297140142226044620228506108869216416853782998 (0) 864691128455135209 3 0 0] ] +``` +这一次,已经为此配置提案投票的验证者的索引列表应该是非空的,并且应该包含你的验证者的索引。在这个例子中,这个列表是 (`0`),意味着在配置参数 `#34` 中索引为 `0` 的验证者已经投票。如果列表变得足够大,提案状态中的倒数第二个整数(`3 0 0` 中的第一个零)会增加一,表明这个提案新获得了胜利。如果胜利次数变得大于或等于在配置参数 `#11` 中指示的值,那么配置提案被自动接受,并且所提议的更改立即生效。另一方面,当验证者集合发生变化时,已经投票的验证者的列表会变为空,`rounds_remaining` 的值(在 `3 0 0` 中为三)会减少一个,如果它变成负数,则配置提案被销毁。如果提案没有被销毁,并且这一轮没有获胜,那么损失次数(`3 0 0` 中的第二个零)会增加。如果它变得大于配置参数 `#11` 中指定的值,那么配置提案会被丢弃。 因此,所有没有在一轮中投票的验证者隐式地投了反对票。 + +## 5. 自动投票配置提案的方法 + +类似于命令`createelectionBid`的`validator-engine-console`为参与验证者选举提供的自动化,`validator-engine` 和 `validator-engine-console` 提供了一个自动完成上一节中解释的大部分步骤的方法,生成一个准备用于控制钱包的 `vote-msg-body.boc`。为了使用这个方法,你必须将 Fift 脚本 `config-proposal-vote-req.fif` 和 `config-proposal-vote-signed.fif` 安装到与查找 `validator-elect-req.fif` 和 `validator-elect-signed.fif` 同一个目录中,如[验证者操作指南](/participate/nodes/validator)的第5节中所述。然后,你只需运行 +``` + createproposalvote 64654898543692093106630260209820256598623953458404398631153796624848083036321 vote-msg-body.boc +``` +在 validator-engine-console 中来创建带有要发送给配置智能合约的内部消息体的 `vote-msg-body.boc`。 + +## 6. 升级配置智能合约和选举智能合约的代码 + +可能会发生配置智能合约本身或选举智能合约的代码需要升级的情况。为此,使用上述相同的机制。新代码需存储在值cell的唯一引用中,并且这个值cell必须被提议作为配置参数 `-1000`(用于升级配置智能合约)或 `-1001`(用于升级选举智能合约)的新值。这些参数被视为关键,因此需要很多验证者的票来更改配置智能合约(这类似于采纳新宪法)。我们期望这样的更改首先在测试网中进行测试,并在每个验证者操作员决定投票赞成或反对所提议的更改之前,在公共论坛中讨论所提议的更改。 + +或者,关键配置参数 `0`(配置智能合约的地址)或 `1`(选举智能合约的地址)可以更改为其他值,这些值必须对应于已经存在且正确初始化的智能合约。特别是,新的配置智能合约必须在其持久数据的第一个引用中包含一个有效的配置字典。由于正确转移更改数据(例如活跃配置提案的列表,或验证者选举的前后参与者列表)在不同的智能合约之间并不容易,所以在多数情况下,升级现有智能合约的代码而不是更改配置智能合约地址更为合适。 + +有两个辅助脚本用于创建升级配置或选举智能合约代码的配置提案。即 `create-config-upgrade-proposal.fif` 加载一个 Fift 汇编源文件(默认为 `auto/config-code.fif`,对应于 FunC 编译器从 `crypto/smartcont/config-code.fc` 自动生成的代码)并创建相应的配置提案(用于配置参数 `-1000`)。类似地,`create-elector-upgrade-proposal.fif` 加载一个 Fift 汇编源文件(默认为 `auto/elector-code.fif`)并使用它来创建配置参数 `-1001` 的配置提案。通过这种方式,创建升级这两个智能合约之一的配置提案应该非常简单。但是,人们也应该发布智能合约修改后的 FunC 源代码,用于编译它的 FunC 编译器的确切版本,以便所有验证者(或者他们的操作员)能够复现配置提案中的代码(并比较哈希值)并在决定投票赞成或反对所提议的更改之前研究和讨论源代码及其更改。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/network-configs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/network-configs.md new file mode 100644 index 0000000000..33af0b506c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/configs/network-configs.md @@ -0,0 +1,11 @@ +# 网络配置 + +在本页面上,您可以找到TON区块链的活跃网络配置: + +- 主网:https://ton.org/global-config.json +- 测试网:https://ton.org/testnet-global.config.json + +## 参阅 + +- [节点类型](https://docs.ton.org/participate/nodes/node-types) +- [区块链参数配置](/develop/howto/blockchain-configs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-tcp.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-tcp.md new file mode 100644 index 0000000000..cbbb19ca17 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-tcp.md @@ -0,0 +1,571 @@ +# ADNL TCP - 轻服务器 + +这是构建TON网络中所有交互的底层协议,它可以在任何协议之上运行,但最常用于TCP和UDP之上。UDP用于节点间通信,而TCP用于与轻服务器的通信。 + +现在我们将分析基于TCP的ADNL,并学习如何直接与轻服务器进行交互。 + +在ADNL的TCP版本中,网络节点使用ed25519公钥作为地址,并使用通过椭圆曲线Diffie-Hellman过程 - ECDH获得的共享密钥建立连接。 + +## 数据包结构 + +除握手外,每个ADNL TCP数据包具有以下结构: + +- 小端模式下的4字节标签大小 (N) +- 32字节随机数 [[?]](## "随机字节用于防止校验和攻击") +- (N - 64) 字节的有效载荷 +- 32字节SHA256校验和,来自随机数和有效载荷 + +整个数据包,包括大小,均为**AES-CTR**加密。解密后,需要检查校验和是否与数据匹配,要检查,只需自己计算校验和并将结果与我们在数据包中拥有的进行比较。 + +握手数据包是一个例外,它以部分未加密的形式传输,并在下一章中描述。 + +## 建立连接 + +要建立连接,我们需要知道服务器的ip、端口和公钥,并生成自己的ed25519私钥和公钥。 + +服务器的公共数据如ip、端口和密钥可以从[全局配置](https://ton-blockchain.github.io/global.config.json)中获得。配置中的IP以数字形式出现,可以使用例如[此工具](https://www.browserling.com/tools/dec-to-ip)转换为常规形式。配置中的公钥为base64格式。 + +客户端生成160个随机字节,其中一些将被双方用作AES加密的基础。 + +由此,创建了2个永久的AES-CTR密码,握手后将被双方用来加密/解密消息。 + +- 密码A - 密钥为0 - 31字节,iv为64 - 79字节 +- 密码B - 密钥为32 - 63字节,iv为80 - 95字节 + +密码应用的顺序如下: + +- 服务器使用密码A加密它发送的消息。 +- 客户端使用密码A解密收到的消息。 +- 客户端使用密码B加密它发送的消息。 +- 服务器使用密码B解密收到的消息。 + +要建立连接,客户端必须发送一个包含以下内容的握手数据包: + +- [32字节] **服务器密钥ID** [[详情]](#获取密钥ID) +- [32字节] **我们的ed25519公钥** +- [32字节] **我们160字节的SHA256哈希** +- [160字节] **我们加密的160字节** [[详情]](#handshake-packet-data-encryption) + +收到握手数据包后,服务器将执行相同的操作,接收ECDH密钥,解密160字节并创建2个永久密钥。如果一切顺利,服务器将用一个没有有效载荷的空ADNL数据包作为回应,为了解密该数据包(以及后续的数据包),我们需要使用其中一个永久密码。 + +从这一点开始,连接可以被视为已建立。 + +在建立了连接后,我们可以开始接收信息;TL语言用于序列化数据。 + +[更多关于TL的信息](/develop/data-formats/tl) + +## Ping&Pong + +最佳做法是每5秒发送一次ping数据包。这是在没有数据传输时保持连接的必要条件,否则服务器可能终止连接。 + +ping数据包与其他所有数据包一样,根据[上文](#packet-structure)描述的标准模式构建,并作为有效载荷携带请求ID和ping ID。 + +让我们找到ping请求的所需模式[此处](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L35),并计算模式id为 +`crc32_IEEE("tcp.ping random_id:long = tcp.Pong")`。转换为小端模式字节后,我们得到**9a2b084d**。 + +因此,我们的ADNL ping数据包将如下所示: + +- 小端模式下的4字节标签大小 -> 64 + (4+8) = **76** +- 32字节随机数 -> 随机的32字节 +- 4字节的ID TL模式 -> **9a2b084d** +- 8字节的请求id -> 随机的uint64数字 +- 32字节的SHA256校验和,来自随机数和有效载荷 + +我们发送我们的数据包并等待[tcp.pong](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L23),`random_id`将与我们在ping数据包中发送的相同。 + +## 从轻服务器接收信息 + +旨在从区块链获取信息的所有请求都包裹在[Liteserver Query](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L83)模式中,该模式又被包裹在[ADNL Query](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L22)模式中。 + +LiteQuery: +`liteServer.query data:bytes = Object`, id **df068c79** + +ADNLQuery: +`adnl.message.query query_id:int256 query:bytes = adnl.Message`, id **7af98bb4** + +LiteQuery作为`query:bytes`传递给ADNLQuery内部,最终查询作为`data:bytes`传递给LiteQuery内部。 + +[解析TL中的编码字节](/develop/data-formats/tl) + +### getMasterchainInfo + +现在,由于我们已经知道如何为Lite API生成TL数据包,我们可以请求有关当前TON masterchain块的信息。 +masterchain区块在许多后续请求中用作输入参数,以指示我们需要信息的状态(时刻)。 + +我们正在寻找[我们需要的TL模式](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L60),计算其ID并构建数据包: + +- 小端模式下的4字节标签大小 -> 64 + (4+32+(1+4+(1+4+3)+3)) = **116** +- 32字节随机数 -> 随机的32字节 +- 4字节的ID ADNLQuery模式 -> **7af98bb4** +- 32字节`query_id:int256` -> 随机的32字节 +- - 1字节数组大小 -> **12** +- - 4字节的ID LiteQuery模式 -> **df068c79** +- - - 1字节数组大小 -> **4** +- - - 4字节的ID getMasterchainInfo模式 -> **2ee6b589** +- - - 3字节填充(对齐至8) +- - 3字节填充(对齐至16) +- 32字节的校验和SHA256,来自随机数和有效载荷 + +数据包示例(十六进制): + +``` +74000000 -> packet size (116) +5fb13e11977cb5cff0fbf7f23f674d734cb7c4bf01322c5e6b928c5d8ea09cfd -> nonce + 7af98bb4 -> ADNLQuery + 77c1545b96fa136b8e01cc08338bec47e8a43215492dda6d4d7e286382bb00c4 -> query_id + 0c -> array size + df068c79 -> LiteQuery + 04 -> array size + 2ee6b589 -> getMasterchainInfo + 000000 -> 3 bytes of padding + 000000 -> 3 bytes of padding +ac2253594c86bd308ed631d57a63db4ab21279e9382e416128b58ee95897e164 -> sha256 +``` + +我们预期收到的响应为[liteServer.masterchainInfo](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L30),包括last:[ton.blockIdExt](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/tonlib_api.tl#L51) state_root_hash:int256 和 init:[tonNode.zeroStateIdExt](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L359)。 + +收到的数据包与发送的数据包同样方式进行反序列化 - 具有相同算法,但方向相反,除了响应仅被[ADNLAnswer](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L23)包裹。 + +解码响应后,我们得到如下形式的数据包: + +``` +20010000 -> packet size (288) +5558b3227092e39782bd4ff9ef74bee875ab2b0661cf17efdfcd4da4e53e78e6 -> nonce + 1684ac0f -> ADNLAnswer + 77c1545b96fa136b8e01cc08338bec47e8a43215492dda6d4d7e286382bb00c4 -> query_id (identical to request) + b8 -> array size + 81288385 -> liteServer.masterchainInfo + last:tonNode.blockIdExt + ffffffff -> workchain:int + 0000000000000080 -> shard:long + 27405801 -> seqno:int + e585a47bd5978f6a4fb2b56aa2082ec9deac33aaae19e78241b97522e1fb43d4 -> root_hash:int256 + 876851b60521311853f59c002d46b0bd80054af4bce340787a00bd04e0123517 -> file_hash:int256 + 8b4d3b38b06bb484015faf9821c3ba1c609a25b74f30e1e585b8c8e820ef0976 -> state_root_hash:int256 + init:tonNode.zeroStateIdExt + ffffffff -> workchain:int + 17a3a92992aabea785a7a090985a265cd31f323d849da51239737e321fb05569 -> root_hash:int256 + 5e994fcf4d425c0a6ce6a792594b7173205f740a39cd56f537defd28b48a0f6e -> file_hash:int256 + 000000 -> 3 bytes of padding +520c46d1ea4daccdf27ae21750ff4982d59a30672b3ce8674195e8a23e270d21 -> sha256 +``` + +### runSmcMethod + +我们已经知道如何获取masterchain区块,所以现在我们可以调用任何轻服务器方法。 +让我们分析**runSmcMethod** - 这是一个调用智能合约中的函数并返回结果的方法。在这里,我们需要了解一些新的数据类型,如[TL-B](/develop/data-formats/tl-b)、[Cell](/develop/data-formats/cell-boc#cell)和[BoC](/develop/data-formats/cell-boc#bag-of-cells)。 + +要执行智能合约方法,我们需要构建并发送使用TL模式的请求: + +```tlb +liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult +``` + +并等待带有模式的响应: + +```tlb +liteServer.runMethodResult mode:# id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:mode.0?bytes proof:mode.0?bytes state_proof:mode.1?bytes init_c7:mode.3?bytes lib_extras:mode.4?bytes exit_code:int result:mode.2?bytes = liteServer.RunMethodResult; +``` + +在请求中,我们看到以下字段: + +1. mode:# - uint32位掩码,指示我们希望在响应中看到的内容,例如,`result:mode.2?bytes`只有在索引为2的位设置为一时才会出现在响应中。 +2. id:tonNode.blockIdExt - 我们在前一章中获得的主区块状态。 +3. account:[liteServer.accountId](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L27) - 工作链和智能合约地址数据。 +4. method_id:long - 8字节,其中写入了调用方法名称的crc16与XMODEM表+设置了第17位 [[计算]](https://github.com/xssnick/tonutils-go/blob/88f83bc3554ca78453dd1a42e9e9ea82554e3dd2/ton/runmethod.go#L16) +5. params:bytes - [Stack](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/crypto/block/block.tlb#L783)以[BoC](/develop/data-formats/cell-boc#bag-of-cells)序列化,其中包含调用方法的参数。[[实现示例]](https://github.com/xssnick/tonutils-go/blob/88f83bc3554ca78453dd1a42e9e9ea82554e3dd2/tlb/stack.go) + +例如,我们只需要`result:mode.2?bytes`,那么我们的 mode 将等于0b100,即4。在响应中,我们将获得: + +1. mode:# -> 发送的内容 - 4。 +2. id:tonNode.blockIdExt -> 我们的主区块,针对该区块执行了方法 +3. shardblk:tonNode.blockIdExt -> 托管合约账户的分片区块 +4. exit_code:int -> 4字节,是执行方法时的退出代码。如果一切顺利,则为0,如果不是,则等于异常代码。 +5. result:mode.2?bytes -> [Stack](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/crypto/block/block.tlb#L783)以[BoC](/develop/data-formats/cell-boc#bag-of-cells)序列化,其中包含方法返回的值。 + +让我们分析调用合约`EQBL2_3lMiyywU17g-or8N7v9hDmPCpttzBPE2isF2GTzpK4`的`a2`方法并获取结果: + +FunC中的方法代码: + +```func +(cell, cell) a2() method_id { + cell a = begin_cell().store_uint(0xAABBCC8, 32).end_cell(); + cell b = begin_cell().store_uint(0xCCFFCC1, 32).end_cell(); + return (a, b); +} +``` + +填写我们的请求: + +- `mode` = 4,我们只需要结果 -> `04000000` +- `id` = 执行getMasterchainInfo的结果 +- `account` = 工作链 0 (4字节 `00000000`),和int256 [从我们的合约地址获得](/develop/data-formats/tl-b#addresses),即32字节 `4bdbfde5322cb2c14d7b83ea2bf0deeff610e63c2a6db7304f1368ac176193ce` +- `method_id` = 从`a2`[计算](https://github.com/xssnick/tonutils-go/blob/88f83bc3554ca78453dd1a42e9e9ea82554e3dd2/ton/runmethod.go#L16)得出的id -> `0a2e010000000000` +- `params:bytes` = 我们的方法不接受输入参数,因此我们需要传递一个空栈(`000000`,cell3字节 - 栈深度0)以[BoC](/develop/data-formats/cell-boc#bag-of-cells)序列化 -> `b5ee9c72010101010005000006000000` -> 序列化为字节并得到 `10b5ee9c72410101010005000006000000000000` 0x10 - 大小,在末尾的 3 字节是填充。 + +我们得到的响应是: + +- `mode:#` -> 不感兴趣 +- `id:tonNode.blockIdExt` -> 不感兴趣 +- `shardblk:tonNode.blockIdExt` -> 不感兴趣 +- `exit_code:int` -> 如果执行成功则为0 +- `result:mode.2?bytes` -> [Stack](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/crypto/block/block.tlb#L783)包含方法返回的数据,以[BoC](/develop/data-formats/cell-boc#bag-of-cells)格式提供,我们将对其进行解包。 + +在`result`中我们收到`b5ee9c7201010501001b000208000002030102020203030400080ccffcc1000000080aabbcc8`,这是包含数据的[BoC](/develop/data-formats/cell-boc#bag-of-cells)。当我们反序列化它时,我们得到一个cell: + +```json +32[00000203] -> { + 8[03] -> { + 0[], + 32[0AABBCC8] + }, + 32[0CCFFCC1] +} +``` + +如果我们解析它,我们将得到2个cell类型的值,这是我们的FunC方法返回的。根cell的前3字节`000002` - 是栈的深度,即2。这意味着该方法返回了2个值。 + +我们继续解析,接下来的8位(1字节)是当前堆栈级别的值类型。对于某些类型,它可能需要2个字节。可能的选项可以在[schema](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/crypto/block/block.tlb#L766)中看到。 +在我们的案例中,我们有`03`,这意味着: + +```tlb +vm_stk_cell#03 cell:^Cell = VmStackValue; +``` + +所以我们的值类型是 - cell,并且根据模式,它将值本身作为引用存储。但是,如果我们看看栈元素存储模式: + +```tlb +vm_stk_cons#_ {n:#} rest:^(VmStackList n) tos:VmStackValue = VmStackList (n + 1); +``` + +我们将看到第一个链接`rest:^(VmStackList n)` - 是栈中下一个值的cell,而我们的值`tos:VmStackValue`排在第二位,所以要获得我们需要的值,我们需要读取第二个链接,即`32[0CCFFCC1]` - 这是合约返回的第一个cell。 + +现在我们可以深入并获取栈中的第二个元素,我们通过第一个链接,现在我们有: + +```json +8[03] -> { + 0[], + 32[0AABBCC8] + } +``` + +我们重复相同的过程。第一个8位 = `03` - 即又是一个cell。第二个引用是值`32[0AABBCC8]`,由于我们的栈深度为2,我们完成了遍历。总体上,我们有2个值由合约返回 - `32[0CCFFCC1]`和`32[0AABBCC8]`。 + +请注意,它们的顺序是相反的。调用函数时也需要以相反的顺序传递参数,与我们在FunC代码中看到的顺序相反。 + +[实现示例](https://github.com/xssnick/tonutils-go/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/ton/runmethod.go#L24) + +### getAccountState + +要获取账户状态数据,如余额、代码和合约数据,我们可以使用[getAccountState](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L68)。请求需要一个[最新的主链块](#getmasterchaininfo)和账户地址。响应中,我们将接收到TL结构[AccountState](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/lite_api.tl#L38)。 + +让我们分析AccountState TL模式: + +```tlb +liteServer.accountState id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes proof:bytes state:bytes = liteServer.AccountState; +``` + +1. `id` - 我们的主链区块,我们从中获取了数据。 +2. `shardblk` - 我们账户所在的工作链分片区块,我们从中接收数据。 +3. `shard_proof` - 分片区块的Merkle证明。 +4. `proof` - 账户状态的Merkle证明。 +5. `state` - [BoC](/develop/data-formats/cell-boc#bag-of-cells) TL-B [账户状态模式](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/crypto/block/block.tlb#L232)。 + +我们需要的所有数据都在state中,我们将对其进行分析。 + +例如,让我们获取账户`EQAhE3sLxHZpsyZ_HecMuwzvXHKLjYx4kEUehhOy2JmCcHCT`的状态,响应中的`state`将是(撰写本文时): + +```hex +b5ee9c720102350100051e000277c0021137b0bc47669b3267f1de70cbb0cef5c728b8d8c7890451e8613b2d899827026a886043179d3f6000006e233be8722201d7d239dba7d818134001020114ff00f4a413f4bcf2c80b0d021d0000000105036248628d00000000e003040201cb05060013a03128bb16000000002002012007080043d218d748bc4d4f4ff93481fd41c39945d5587b8e2aa2d8a35eaf99eee92d9ba96004020120090a0201200b0c00432c915453c736b7692b5b4c76f3a90e6aeec7a02de9876c8a5eee589c104723a18020004307776cd691fbe13e891ed6dbd15461c098b1b95c822af605be8dc331e7d45571002000433817dc8de305734b0c8a3ad05264e9765a04a39dbe03dd9973aa612a61f766d7c02000431f8c67147ceba1700d3503e54c0820f965f4f82e5210e9a3224a776c8f3fad1840200201200e0f020148101104daf220c7008e8330db3ce08308d71820f90101d307db3c22c00013a1537178f40e6fa1f29fdb3c541abaf910f2a006f40420f90101d31f5118baf2aad33f705301f00a01c20801830abcb1f26853158040f40e6fa120980ea420c20af2670edff823aa1f5340b9f2615423a3534e2a2d2b2c0202cc12130201201819020120141502016616170003d1840223f2980bc7a0737d0986d9e52ed9e013c7a21c2b2f002d00a908b5d244a824c8b5d2a5c0b5007404fc02ba1b04a0004f085ba44c78081ba44c3800740835d2b0c026b500bc02f21633c5b332781c75c8f20073c5bd0032600201201a1b02012020210115bbed96d5034705520db3c8340201481c1d0201201e1f0173b11d7420c235c6083e404074c1e08075313b50f614c81e3d039be87ca7f5c2ffd78c7e443ca82b807d01085ba4d6dc4cb83e405636cf0069006031003daeda80e800e800fa02017a0211fc8080fc80dd794ff805e47a0000e78b64c00015ae19574100d56676a1ec40020120222302014824250151b7255b678626466a4610081e81cdf431c24d845a4000331a61e62e005ae0261c0b6fee1c0b77746e102d0185b5599b6786abe06fedb1c68a2270081e8f8df4a411c4605a400031c34410021ae424bae064f613990039e2ca840090081e886052261c52261c52265c4036625ccd88302d02012026270203993828290111ac1a6d9e2f81b609402d0015adf94100cc9576a1ec1840010da936cf0557c1602d0015addc2ce0806ab33b50f6200220db3c02f265f8005043714313db3ced542d34000ad3ffd3073004a0db3c2fae5320b0f26212b102a425b3531cb9b0258100e1aa23a028bcb0f269820186a0f8010597021110023e3e308e8d11101fdb3c40d778f44310bd05e254165b5473e7561053dcdb3c54710a547abc2e2f32300020ed44d0d31fd307d307d33ff404f404d10048018e1a30d20001f2a3d307d3075003d70120f90105f90115baf2a45003e06c2170542013000c01c8cbffcb0704d6db3ced54f80f70256e5389beb198106e102d50c75f078f1b30542403504ddb3c5055a046501049103a4b0953b9db3c5054167fe2f800078325a18e2c268040f4966fa52094305303b9de208e1638393908d2000197d3073016f007059130e27f080705926c31e2b3e63006343132330060708e2903d08308d718d307f40430531678f40e6fa1f2a5d70bff544544f910f2a6ae5220b15203bd14a1236ee66c2232007e5230be8e205f03f8009322d74a9802d307d402fb0002e83270c8ca0040148040f44302f0078e1771c8cb0014cb0712cb0758cf0158cf1640138040f44301e201208e8a104510344300db3ced54925f06e234001cc8cb1fcb07cb07cb3ff400f400c9 +``` + +[解析此BoC](/develop/data-formats/cell-boc#bag-of-cells)并获取 + +
+ large cell + +```json +473[C0021137B0BC47669B3267F1DE70CBB0CEF5C728B8D8C7890451E8613B2D899827026A886043179D3F6000006E233BE8722201D7D239DBA7D818130_] -> { + 80[FF00F4A413F4BCF2C80B] -> { + 2[0_] -> { + 4[4_] -> { + 8[CC] -> { + 2[0_] -> { + 13[D180], + 141[F2980BC7A0737D0986D9E52ED9E013C7A218] -> { + 40[D3FFD30730], + 48[01C8CBFFCB07] + } + }, + 6[64] -> { + 178[00A908B5D244A824C8B5D2A5C0B5007404FC02BA1B048_], + 314[085BA44C78081BA44C3800740835D2B0C026B500BC02F21633C5B332781C75C8F20073C5BD00324_] + } + }, + 2[0_] -> { + 2[0_] -> { + 84[BBED96D5034705520DB3C_] -> { + 112[C8CB1FCB07CB07CB3FF400F400C9] + }, + 4[4_] -> { + 2[0_] -> { + 241[AEDA80E800E800FA02017A0211FC8080FC80DD794FF805E47A0000E78B648_], + 81[AE19574100D56676A1EC0_] + }, + 458[B11D7420C235C6083E404074C1E08075313B50F614C81E3D039BE87CA7F5C2FFD78C7E443CA82B807D01085BA4D6DC4CB83E405636CF0069004_] -> { + 384[708E2903D08308D718D307F40430531678F40E6FA1F2A5D70BFF544544F910F2A6AE5220B15203BD14A1236EE66C2232] + } + } + }, + 2[0_] -> { + 2[0_] -> { + 323[B7255B678626466A4610081E81CDF431C24D845A4000331A61E62E005AE0261C0B6FEE1C0B77746E0_] -> { + 128[ED44D0D31FD307D307D33FF404F404D1] + }, + 531[B5599B6786ABE06FEDB1C68A2270081E8F8DF4A411C4605A400031C34410021AE424BAE064F613990039E2CA840090081E886052261C52261C52265C4036625CCD882_] -> { + 128[ED44D0D31FD307D307D33FF404F404D1] + } + }, + 4[4_] -> { + 2[0_] -> { + 65[AC1A6D9E2F81B6090_] -> { + 128[ED44D0D31FD307D307D33FF404F404D1] + }, + 81[ADF94100CC9576A1EC180_] + }, + 12[993_] -> { + 50[A936CF0557C14_] -> { + 128[ED44D0D31FD307D307D33FF404F404D1] + }, + 82[ADDC2CE0806AB33B50F60_] + } + } + } + } + }, + 872[F220C7008E8330DB3CE08308D71820F90101D307DB3C22C00013A1537178F40E6FA1F29FDB3C541ABAF910F2A006F40420F90101D31F5118BAF2AAD33F705301F00A01C20801830ABCB1F26853158040F40E6FA120980EA420C20AF2670EDFF823AA1F5340B9F2615423A3534E] -> { + 128[DB3C02F265F8005043714313DB3CED54] -> { + 128[ED44D0D31FD307D307D33FF404F404D1], + 112[C8CB1FCB07CB07CB3FF400F400C9] + }, + 128[ED44D0D31FD307D307D33FF404F404D1], + 40[D3FFD30730], + 640[DB3C2FAE5320B0F26212B102A425B3531CB9B0258100E1AA23A028BCB0F269820186A0F8010597021110023E3E308E8D11101FDB3C40D778F44310BD05E254165B5473E7561053DCDB3C54710A547ABC] -> { + 288[018E1A30D20001F2A3D307D3075003D70120F90105F90115BAF2A45003E06C2170542013], + 48[01C8CBFFCB07], + 504[5230BE8E205F03F8009322D74A9802D307D402FB0002E83270C8CA0040148040F44302F0078E1771C8CB0014CB0712CB0758CF0158CF1640138040F44301E2], + 856[DB3CED54F80F70256E5389BEB198106E102D50C75F078F1B30542403504DDB3C5055A046501049103A4B0953B9DB3C5054167FE2F800078325A18E2C268040F4966FA52094305303B9DE208E1638393908D2000197D3073016F007059130E27F080705926C31E2B3E63006] -> { + 112[C8CB1FCB07CB07CB3FF400F400C9], + 384[708E2903D08308D718D307F40430531678F40E6FA1F2A5D70BFF544544F910F2A6AE5220B15203BD14A1236EE66C2232], + 504[5230BE8E205F03F8009322D74A9802D307D402FB0002E83270C8CA0040148040F44302F0078E1771C8CB0014CB0712CB0758CF0158CF1640138040F44301E2], + 128[8E8A104510344300DB3CED54925F06E2] -> { + 112[C8CB1FCB07CB07CB3FF400F400C9] + } + } + } + } + } + }, + 114[0000000105036248628D00000000C_] -> { + 7[CA] -> { + 2[0_] -> { + 2[0_] -> { + 266[2C915453C736B7692B5B4C76F3A90E6AEEC7A02DE9876C8A5EEE589C104723A1800_], + 266[07776CD691FBE13E891ED6DBD15461C098B1B95C822AF605BE8DC331E7D45571000_] + }, + 2[0_] -> { + 266[3817DC8DE305734B0C8A3AD05264E9765A04A39DBE03DD9973AA612A61F766D7C00_], + 266[1F8C67147CEBA1700D3503E54C0820F965F4F82E5210E9A3224A776C8F3FAD18400_] + } + }, + 269[D218D748BC4D4F4FF93481FD41C39945D5587B8E2AA2D8A35EAF99EEE92D9BA96000] + }, + 74[A03128BB16000000000_] + } +} +``` + +
+ +现在我们需要根据TL-B结构解析cell: + +```tlb +account_none$0 = Account; + +account$1 addr:MsgAddressInt storage_stat:StorageInfo + storage:AccountStorage = Account; +``` + +我们的结构引用了其他结构,例如: + +```tlb +anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast; +addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; +addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + +storage_info$_ used:StorageUsed last_paid:uint32 due_payment:(Maybe Grams) = StorageInfo; +storage_used$_ cells:(VarUInteger 7) bits:(VarUInteger 7) public_cells:(VarUInteger 7) = StorageUsed; + +account_storage$_ last_trans_lt:uint64 balance:CurrencyCollection state:AccountState = AccountStorage; + +currencies$_ grams:Grams other:ExtraCurrencyCollection = CurrencyCollection; + +var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; +var_int$_ {n:#} len:(#< n) value:(int (len * 8)) = VarInteger n; +nanograms$_ amount:(VarUInteger 16) = Grams; + +account_uninit$00 = AccountState; +account_active$1 _:StateInit = AccountState; +account_frozen$01 state_hash:bits256 = AccountState; +``` + +我们可以看到,cell包含很多数据,但我们将分析主要情况并获取余额。其余的可以以类似的方式进行分析。 + +让我们开始解析。在根cell数据中,我们有: + +``` +C0021137B0BC47669B3267F1DE70CBB0CEF5C728B8D8C7890451E8613B2D899827026A886043179D3F6000006E233BE8722201D7D239DBA7D818130_ +``` + +转换为二进制形式并获取: + +``` +11000000000000100001000100110111101100001011110001000111011001101001101100110010011001111111000111011110011100001100101110110000110011101111010111000111001010001011100011011000110001111000100100000100010100011110100001100001001110110010110110001001100110000010011100000010011010101000100001100000010000110001011110011101001111110110000000000000000000000110111000100011001110111110100001110010001000100000000111010111110100100011100111011011101001111101100000011000000100110 +``` + +让我们看看我们的主要TL-B结构,我们看到我们有两个可能的选项 - `account_none$0`或`account$1`。我们可以通过读取符号$后声明的前缀来理解我们拥有哪个选项,在我们的例子中,它是1位。如果是0,则我们拥有`account_none`,如果是1,则`account`。 + +我们上面的数据中的第一个bit=1,所以我们正在处理`account$1`,将使用模式: + +```tlb +account$1 addr:MsgAddressInt storage_stat:StorageInfo + storage:AccountStorage = Account; +``` + +接下来我们有`addr:MsgAddressInt`,我们看到MsgAddressInt也有几个选项: + +```tlb +addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; +addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) workchain_id:int32 address:(bits addr_len) = MsgAddressInt; +``` + +要理解应该使用哪一个,我们像上次一样,读取前缀位,这次我们读取2个位。我们去掉已读的位,“1000000...”剩下,我们读取前2个位得到“10”,这意味着我们正在处理`addr_std$10`。 + +接下来我们需要解析`anycast:(Maybe Anycast)`,Maybe意味着我们应该读取1位,如果是1,则读取Anycast,否则跳过。我们剩余的位是“00000...”,读取1位,它是0,所以我们跳过Anycast。 + +接下来,我们有`workchain_id:int8`,这里很简单,我们读取8个位,这将是工作链ID。我们读取接下来的8个位,全部为零,所以工作链为0。 + +接下来,我们读取`address:bits256`,这是地址的256个位,与`workchain_id`一样。在读取时,我们得到`21137B0BC47669B3267F1DE70CBB0CEF5C728B8D8C7890451E8613B2D8998270`的十六进制表示。 + +我们读取了地址`addr:MsgAddressInt`,然后我们有`storage_stat:StorageInfo`来自主结构,它的模式是: + +```tlb +storage_info$_ used:StorageUsed last_paid:uint32 due_payment:(Maybe Grams) = StorageInfo; +``` + +首先是`used:StorageUsed`,它的模式是: + +```tlb +storage_used$_ cells:(VarUInteger 7) bits:(VarUInteger 7) public_cells:(VarUInteger 7) = StorageUsed; +``` + +这是用于存储账户数据的cell和位的数量。每个字段都定义为`VarUInteger 7`,这意味着动态大小的uint,但最多为7位。你可以根据模式了解它是如何排列的: + +```tlb +var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; +``` + +在我们的案例中,n将等于7。在len中,我们将有`(#< 7)`,这意味着可以容纳最多7的数字的位数。你可以通过将7-1=6转换为二进制形式 - `110`,我们得到3个位,所以长度len = 3个位。而value是`(uint (len * 8))`。要确定它,我们需要读取3个位的长度,得到一个数字并乘以8,这将是`value`的大小,也就是需要读取的位数以获取VarUInteger的值。 + +读取`cells:(VarUInteger 7)`,取我们根cell的下一个位,看接下来的16个位以理解,这是`0010011010101000`。我们读取前3个位,这是`001`,即1,我们得到大小(uint (1 \* 8)),我们得到uint 8,我们读取8个位,它将是`cells`,`00110101`,即十进制中的53。对于 `bits` 和 `public_cells`,我们做同样的操作。 + +我们成功读取了`used:StorageUsed`,接下来我们有`last_paid:uint32`,我们读取32个位。`due_payment:(Maybe Grams)`在这里也很简单,Maybe将是0,所以我们跳过Grams。但是,如果Maybe是1,我们可以看看Grams的`amount:(VarUInteger 16) = Grams`模式并立即理解我们已经知道如何处理这个。像上次一样,只是我们有16而不是7。 + +接下来我们有`storage:AccountStorage`,它的模式是: + +```tlb +account_storage$_ last_trans_lt:uint64 balance:CurrencyCollection state:AccountState = AccountStorage; +``` + +我们读取`last_trans_lt:uint64`,这是64个位,存储最后一次账户交易的lt。最后是余额,由模式表示: + +```tlb +currencies$_ grams:Grams other:ExtraCurrencyCollection = CurrencyCollection; +``` + +从这里我们将读取`grams:Grams`,这将是以 nanotones 计的账户余额。 +`grams:Grams`是`VarUInteger 16`,要存储16(二进制形式`10000`,减去1得到`1111`),那么我们读取前4个位,并将得到的值乘以8,然后读取接收到的位数,它是我们的余额。 + +让我们根据我们的数据分析剩余的位: + +``` +100000000111010111110100100011100111011011101001111101100000011000000100110 +``` + +读取前4个位 - `1000`,这是8。8\*8=64,读取接下来的64个位 = `0000011101011111010010001110011101101110100111110110000001100000`,去掉额外的零位,我们得到`11101011111010010001110011101101110100111110110000001100000`,即等于`531223439883591776`,将 nano 转换为TON,我们得到`531223439.883591776`。 + +我们将在这里停止,因为我们已经分析了所有主要情况,其余的可以以与我们已分析的类似的方式获得。此外,关于解析TL-B的更多信息可以在[官方文档](/develop/data-formats/tl-b-language)中找到。 + +### 其他方法 + +现在,学习了所有信息,您也可以调用并处理其他轻服务器方法的响应。同样的原理 :) + +## 握手的其他技术细节 + +### 获取密钥ID + +密钥ID是序列化TL模式的SHA256哈希。 + +最常用的TL模式密钥是: + +```tlb +pub.ed25519 key:int256 = PublicKey -- ID c6b41348 +pub.aes key:int256 = PublicKey -- ID d4adbc2d +pub.overlay name:bytes = PublicKey -- ID cb45ba34 +pub.unenc data:bytes = PublicKey -- ID 0a451fb6 +pk.aes key:int256 = PrivateKey -- ID 3751e8a5 +``` + +例如,对于握手中使用的ED25519类型密钥,密钥ID将是 +**[0xC6, 0xB4, 0x13, 0x48]** 和 **公钥**的SHA256哈希(36字节数组,前缀+密钥) + +[代码示例](https://github.com/xssnick/tonutils-go/blob/2b5e5a0e6ceaf3f28309b0833cb45de81c580acc/liteclient/crypto.go#L16) + +### 握手数据包数据加密 + +握手数据包以半开放形式发送,只有160字节被加密,包含有关永久密码的信息。 + +要加密它们,我们需要一个AES-CTR密码,我们需要160字节的SHA256哈希和[ECDH共享密钥](#使用ECDH获取共享密钥) + +密码构建如下: + +- key = (公钥的0 - 15字节)+(哈希的16 - 31字节) +- iv = (哈希的0 - 3字节)+(公钥的20 - 31字节) + +密码组装后,我们用它加密我们的160字节。 + +[代码示例](https://github.com/xssnick/tonutils-go/blob/2b5e5a0e6ceaf3f28309b0833cb45de81c580acc/liteclient/connection.go#L361) + +### 使用ECDH获取共享密钥 + +要计算共享密钥,我们需要我们的私钥和服务器的公钥。 + +DH的本质是获取共享的密钥,而不暴露私人信息。我将给出一个这是如何发生的示例,以最简化的形式。假设我们需要生成我们和服务器之间的共享密钥,过程将如下: + +1. 我们生成secret和公共数字,如**6**和**7** +2. 服务器生成secret和公共数字,如**5**和**15** +3. 我们与服务器交换公共数字,发送**7**给服务器,它发送给我们**15**。 +4. 我们计算:**7^6 mod 15 = 4** +5. 服务器计算:**7^5 mod 15 = 7** +6. 我们交换收到的数字,我们给服务器**4**,它给我们**7** +7. 我们计算**7^6 mod 15 = 4** +8. 服务器计算:**4^5 mod 15 = 4** +9. 共享密钥 = **4** + +为了简洁起见,将省略ECDH本身的细节。它是通过在曲线上找到一个共同点,使用两个密钥,私钥和公钥来计算的。如果感兴趣,最好单独阅读。 + +[代码示例](https://github.com/xssnick/tonutils-go/blob/2b5e5a0e6ceaf3f28309b0833cb45de81c580acc/liteclient/crypto.go#L32) + +## 参考资料 + +*这里是[Oleg Baranov](https://github.com/xssnick)撰写的原始文章的[链接](https://github.com/xssnick/ton-deep-doc/blob/master/ADNL-TCP-Liteserver.md)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-udp.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-udp.md new file mode 100644 index 0000000000..13b7d1f67c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/adnl-udp.md @@ -0,0 +1,352 @@ +# ADNL UDP - 节点间通信 + +ADNL通过UDP用于节点和TON组件之间的通信。它是一个低层级协议,其他更高级的TON协议(如DHT和RLDP)都是在其基础上运行的。在这篇文章中,我们将了解ADNL通过UDP在节点之间进行基本通信的工作方式。 + +与ADNL通过TCP不同,在UDP实现中,握手以不同的形式发生,并且使用了通道的额外层,但其他原则相似: +基于我们的私钥和预先从配置或从其他网络节点收到的对方的公钥生成加密密钥。 + +在ADNL的UDP版本中,连接是在从对方接收初始数据的同时建立的,如果发起方发送了'创建通道'消息,通道的密钥将被计算,并将确认通道的创建。建立通道后,进一步的通信将继续在通道内进行。 + +## 数据包结构和通信 + +### 首个数据包 + +让我们分析与DHT节点建立连接并获取其签名地址列表的初始化,以便了解协议的工作方式。 + +在[全局配置](https://ton-blockchain.github.io/global.config.json)中找到您喜欢的节点,在`dht.nodes`部分。例如: + +```json +{ + "@type": "dht.node", + "id": { + "@type": "pub.ed25519", + "key": "fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk=" + }, + "addr_list": { + "@type": "adnl.addressList", + "addrs": [ + { + "@type": "adnl.address.udp", + "ip": 1091897261, + "port": 15813 + } + ], + "version": 0, + "reinit_date": 0, + "priority": 0, + "expire_at": 0 + }, + "version": -1, + "signature": "cmaMrV/9wuaHOOyXYjoxBnckJktJqrQZ2i+YaY3ehIyiL3LkW81OQ91vm8zzsx1kwwadGZNzgq4hI4PCB/U5Dw==" +} +``` + +1. 我们取其ED25519密钥`fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk`,并从base64解码。 +2. 取其IP地址`1091897261`,并使用[服务](https://www.browserling.com/tools/dec-to-ip)或转换为小端字节,得到`65.21.7.173`。 +3. 与端口结合,得到`65.21.7.173:15813`并建立UDP连接。 + +我们想要打开一个通道与节点通信并获取一些信息,主要任务是接收其签名地址列表。为此,我们将生成2个消息,第一个是[创建通道](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L129): + +```tlb +adnl.message.createChannel key:int256 date:int = adnl.Message +``` + +这里有两个参数 - key和date。作为date,我们将指定当前的unix时间戳。对于key,我们需要为通道专门生成一个新的ED25519私钥+公钥对,它们将用于初始化[公共加密密钥](/develop/network/adnl-tcp#getting-a-shared-key-using-ecdh)。我们将在消息的`key`参数中使用生成的公钥,并暂时保存私钥。 + +序列化填充的TL结构,得到: + +``` +bbc373e6 -- TL ID adnl.message.createChannel +d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key +555c8763 -- date +``` + +接下来,我们转到我们的主要查询 - [获取地址列表](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L198)。为此,我们首先需要序列化其TL结构: + +```tlb +dht.getSignedAddressList = dht.Node +``` + +它没有参数,因此我们只需序列化它。它将只是它的id - `ed4879a9`。 + +接下来,由于这是DHT协议更高级别的请求,我们需要首先将它包裹在`adnl.message.query` TL结构中: + +```tlb +adnl.message.query query_id:int256 query:bytes = adnl.Message +``` + +作为`query_id`,我们生成随机的32字节,作为`query`,我们使用我们的主要请求,[包裹在字节数组中](/develop/data-formats/tl#encoding-bytes-array)。 +我们将得到: + +``` +7af98bb4 -- TL ID adnl.message.query +d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id +04 ed4879a9 000000 -- query +``` + +### 构建数据包 + +所有通信都是通过数据包进行的,其内容是[TL结构](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L81): + +```tlb +adnl.packetContents + rand1:bytes -- random 7 or 15 bytes + flags:# -- bit flags, used to determine the presence of fields further + from:flags.0?PublicKey -- sender's public key + from_short:flags.1?adnl.id.short -- sender's ID + message:flags.2?adnl.Message -- message (used if there is only one message) + messages:flags.3?(vector adnl.Message) -- messages (if there are > 1) + address:flags.4?adnl.addressList -- list of our addresses + priority_address:flags.5?adnl.addressList -- priority list of our addresses + seqno:flags.6?long -- packet sequence number + confirm_seqno:flags.7?long -- sequence number of the last packet received + recv_addr_list_version:flags.8?int -- address version + recv_priority_addr_list_version:flags.9?int -- priority address version + reinit_date:flags.10?int -- connection reinitialization date (counter reset) + dst_reinit_date:flags.10?int -- connection reinitialization date from the last received packet + signature:flags.11?bytes -- signature + rand2:bytes -- random 7 or 15 bytes + = adnl.PacketContents + +``` + +一旦我们序列化了所有想要发送的消息,我们就可以开始构建数据包。发送到通道的数据包与初始化通道之前发送的数据包在内容上有所不同。首先,让我们分析用于初始化的主数据包。 + +在通道外的初始数据交换期间,数据包的序列化内容结构前缀为对方的公钥 - 32字节。我们的公钥为32字节,数据包内容结构的序列化TL的sha256哈希 - 32字节。数据包内容使用从我们的私钥和对方的公钥(不是通道的密钥)获得的[共享密钥](/develop/network/adnl-tcp#getting-a-shared-key-using-ecdh)进行加密。 + +序列化我们的数据包内容结构,然后逐字节解析: + +``` +89cd42d1 -- TL ID adnl.packetContents +0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) random bytes +d9050000 -- flags (0x05d9) -> 0b0000010111011001 + -- from (present because flag's zero bit = 1) +c6b41348 -- TL ID pub.ed25519 + afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256 + -- messages (present because flag's third bit = 1) +02000000 -- vector adnl.Message, size = 2 messages + bbc373e6 -- TL ID adnl.message.createChannel + d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key + 555c8763 -- date (date of creation) + + 7af98bb4 -- TL ID [adnl.message.query](/) + d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id + 04 ed4879a9 000000 -- query (bytes size 4, padding 3) + -- address (present because flag's fourth bit = 1), without TL ID since it is specified explicitly +00000000 -- addrs (empty vector, because we are in client mode and do not have an address on wiretap) +555c8763 -- version (usually initialization date) +555c8763 -- reinit_date (usually initialization date) +00000000 -- priority +00000000 -- expire_at + +0100000000000000 -- seqno (present because flag's sixth bit = 1) +0000000000000000 -- confirm_seqno (present because flag's seventh bit = 1) +555c8763 -- recv_addr_list_version (present because flag's eighth bit = 1, usually initialization date) +555c8763 -- reinit_date (present because flag's tenth bit = 1, usually initialization date) +00000000 -- dst_reinit_date (present because flag's tenth bit = 1) +0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) random bytes +``` + +序列化后 - 我们需要使用我们之前生成并保存的私有客户端(不是通道的)ED25519密钥对结果字节数组进行签名。 +生成签名(大小为64字节)后,我们需要将其添加到数据包中,再次序列化,但现在在标志位中添加第11位,表示存在签名: + +``` +89cd42d1 -- TL ID adnl.packetContents +0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) random bytes +d90d0000 -- flags (0x0dd9) -> 0b0000110111011001 + -- from (present because flag's zero bit = 1) +c6b41348 -- TL ID pub.ed25519 + afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256 + -- messages (present because flag's third bit = 1) +02000000 -- vector adnl.Message, size = 2 message + bbc373e6 -- TL ID adnl.message.createChannel + d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key + 555c8763 -- date (date of creation) + + 7af98bb4 -- TL ID adnl.message.query + d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id + 04 ed4879a9 000000 -- query (bytes size 4, padding 3) + -- address (present because flag's fourth bit = 1), without TL ID since it is specified explicitly +00000000 -- addrs (empty vector, because we are in client mode and do not have an address on wiretap) +555c8763 -- version (usually initialization date) +555c8763 -- reinit_date (usually initialization date) +00000000 -- priority +00000000 -- expire_at + +0100000000000000 -- seqno (present because flag's sixth bit = 1) +0000000000000000 -- confirm_seqno (present because flag's seventh bit = 1) +555c8763 -- recv_addr_list_version (present because flag's eighth bit = 1, usually initialization date) +555c8763 -- reinit_date (present because flag's tenth bit = 1, usually initialization date) +00000000 -- dst_reinit_date (present because flag's tenth bit = 1) +40 b453fbcbd8e884586b464290fe07475ee0da9df0b8d191e41e44f8f42a63a710 -- signature (present because flag's eleventh bit = 1), (bytes size 64, padding 3) + 341eefe8ffdc56de73db50a25989816dda17a4ac6c2f72f49804a97ff41df502 -- + 000000 -- +0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) random bytes +``` + +现在我们有一个组装好的、签名的和序列化的数据包,它是一个字节数组。 +为了让接收方随后验证其完整性,我们需要计算数据包的sha256哈希。例如,让这个哈希是`408a2a4ed623b25a2e2ba8bbe92d01a3b5dbd22c97525092ac3203ce4044dcd2`。 + +现在让我们使用AES-CTR密码对数据包内容进行加密,使用从我们的私钥和对方的公钥(不是通道的密钥)获得的[共享密钥](/develop/network/adnl-tcp#getting-a-shared-key-using-ecdh)。 + +我们几乎准备好发送了,只剩下计算ED25519对等密钥的[ID](/develop/network/adnl-tcp#getting-key-id),并将所有内容连接在一起: + +``` +daa76538d99c79ea097a67086ec05acca12d1fefdbc9c96a76ab5a12e66c7ebb -- server Key ID +afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- our public key +408a2a4ed623b25a2e2ba8bbe92d01a3b5dbd22c97525092ac3203ce4044dcd2 -- sha256 content hash (before encryption) +... -- encrypted content of the packet +``` + +现在我们可以通过UDP发送我们构建的数据包,并等待响应。 + +作为响应,我们将收到具有类似结构的数据包,但消息不同。它将包括: + +``` +68426d4906bafbd5fe25baf9e0608cf24fffa7eca0aece70765d64f61f82f005 -- ID of our key +2d11e4a08031ad3778c5e060569645466e52bd1bd2c7b78ddd56def1cf3760c9 -- server public key, for shared key +f32fa6286d8ae61c0588b5a03873a220a3163cad2293a5dace5f03f06681e88a -- sha256 content hash (before encryption) +... -- the encrypted content of the packet +``` + +从服务器的数据包的反序列化如下: + +1. 我们检查数据包中的密钥ID,以了解该数据包是为我们准备的。 +2. 使用数据包中的服务器公钥和我们的私钥,我们计算共享密钥并解密数据包内容 +3. 比较我们收到的sha256哈希和从解密数据获得的哈希,它们必须匹配 +4. 使用`adnl.packetContents` TL模式开始反序列化数据包内容 + +数据包内容将如下所示: + +``` +89cd42d1 -- TL ID adnl.packetContents +0f 985558683d58c9847b4013ec93ea28 -- rand1, 15 (0f) random bytes +ca0d0000 -- flags (0x0dca) -> 0b0000110111001010 +daa76538d99c79ea097a67086ec05acca12d1fefdbc9c96a76ab5a12e66c7ebb -- from_short (because flag's first bit = 1) +02000000 -- messages (present because flag's third bit = 1) + 691ddd60 -- TL ID adnl.message.confirmChannel + db19d5f297b2b0d76ef79be91ad3ae01d8d9f80fab8981d8ed0c9d67b92be4e3 -- key (server channel public key) + d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- peer_key (our public channel key) + 94848863 -- date + + 1684ac0f -- TL ID adnl.message.answer + d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id + 90 48325384c6b413487d99e4a08031ad3778c5e060569645466e52bd5bd2c7b -- answer (the answer to our request, we will analyze its content in an article about DHT) + 78ddd56def1cf3760c901000000e7a60d67ad071541c53d0000ee354563ee -- + 35456300000000000000009484886340d46cc50450661a205ad47bacd318c -- + 65c8fd8e8f797a87884c1bad09a11c36669babb88f75eb83781c6957bc976 -- + 6a234f65b9f6e7cc9b53500fbe2c44f3b3790f000000 -- + 000000 -- +0100000000000000 -- seqno (present because flag's sixth bit = 1) +0100000000000000 -- confirm_seqno (present because flag's seventh bit = 1) +94848863 -- recv_addr_list_version (present because flag's eighth bit = 1, usually initialization date) +ee354563 -- reinit_date (present because flag's tenth bit = 1, usually initialization date) +94848863 -- dst_reinit_date (present because flag's tenth bit = 1) +40 5c26a2a05e584e9d20d11fb17538692137d1f7c0a1a3c97e609ee853ea9360ab6 -- signature (present because flag's eleventh bit = 1), (bytes size 64, padding 3) + d84263630fe02dfd41efb5cd965ce6496ac57f0e51281ab0fdce06e809c7901 -- + 000000 -- +0f c3354d35749ffd088411599101deb2 -- rand2, 15 (0f) random bytes +``` + +服务器用两条消息回应我们:`adnl.message.confirmChannel`和`adnl.message.answer`。 +对于`adnl.message.answer`,一切都很简单,这是我们请求`dht.getSignedAddressList`的回答,我们将在关于DHT的文章中分析。 + +让我们关注`adnl.message.confirmChannel`,这意味着对方已确认创建通道并发送给我们其公共通道密钥。现在,有了我们的私有通道密钥和对方的公共通道密钥,我们可以计算[共享密钥](/develop/network/adnl-tcp#getting-a-shared-key-using-ecdh)。 + +现在我们计算出共享通道密钥后,我们需要从中生成2个密钥 - 一个用于加密发出的消息,另一个用于解密传入的消息。 +从中生成2个密钥相当简单,第二个密钥等于共享密钥的倒序写法。例如: + +``` +Shared key : AABB2233 + +First key: AABB2233 +Second key: 3322BBAA +``` + +剩下的就是确定哪个密钥用于什么,我们可以通过将我们的公共通道密钥的ID与服务器通道的公钥的ID比较,将它们转换为数值形式 - uint256。这种方法用于确保服务器和客户端都确定哪个密钥用于什么。如果服务器使用第一个密钥进行加密,那么使用这种方法,客户端将始终将其用于解密。 + +使用条款是: + +``` +The server id is smaller than our id: +Encryption: First Key +Decryption: Second Key + +The server id is larger than our id: +Encryption: Second Key +Decryption: First Key + +If the ids are equal (nearly impossible): +Encryption: First Key +Decryption: First Key +``` + +[[实现示例]](https://github.com/xssnick/tonutils-go/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/adnl/adnl.go#L502) + +### 通道内通信 + +所有后续的数据包交换将在通道内发生,通道密钥将用于加密。 +让我们在新创建的通道内发送相同的`dht.getSignedAddressList`请求,看看区别。 + +让我们使用相同的`adnl.packetContents`结构为通道构建数据包: + +``` +89cd42d1 -- TL ID adnl.packetContents +0f c1fbe8c4ab8f8e733de83abac17915 -- rand1, 15 (0f) random bytes +c4000000 -- flags (0x00c4) -> 0b0000000011000100 + -- message (because second bit = 1) +7af98bb4 -- TL ID adnl.message.query +fe3c0f39a89917b7f393533d1d06b605b673ffae8bbfab210150fe9d29083c35 -- query_id +04 ed4879a9 000000 -- query (our dht.getSignedAddressList packed in bytes with padding 3) +0200000000000000 -- seqno (because flag's sixth bit = 1), 2 because it is our second message +0100000000000000 -- confirm_seqno (flag's seventh bit = 1), 1 because it is the last seqno received from the server +07 e4092842a8ae18 -- rand2, 7 (07) random bytes +``` + +通道内的数据包非常简单,实质上由序列(seqno)和消息本身组成。 + +序列化后,像上次一样,我们计算数据包的sha256哈希。然后我们使用用于通道传出数据包的密钥加密数据包。[计算](/develop/network/adnl-tcp#getting-key-id)我们传出消息的加密密钥的`pub.aes` ID,并构建我们的数据包: + +``` +bcd1cf47b9e657200ba21d94b822052cf553a548f51f539423c8139a83162180 -- ID of encryption key of our outgoing messages +6185385aeee5faae7992eb350f26ba253e8c7c5fa1e3e1879d9a0666b9bd6080 -- sha256 content hash (before encryption) +... -- the encrypted content of the packet +``` + +我们通过UDP发送数据包,并等待响应。作为回应,我们将收到与我们发送的同类型的数据包(相同字段),但带有我们请求`dht.getSignedAddressList`的回复。 + +## 其他消息类型 + +对于基本通信,使用像`adnl.message.query`和`adnl.message.answer`这样的消息,我们在上面讨论了,但对于某些情况,也使用其他类型的消息,我们将在本节中讨论。 + +### adnl.message.part + +此消息类型是其他可能消息类型的一部分,例如`adnl.message.answer`。当消息太大而无法通过单个UDP数据报传输时,使用此传输数据的方法。 + +```tlb +adnl.message.part +hash:int256 -- sha256 hash of the original message +total_size:int -- original message size +offset:int -- offset according to the beginning of the original message +data:bytes -- piece of data of the original message + = adnl.Message; +``` + +因此,为了组装原始消息,我们需要获取几个部分,并根据偏移量将它们连接成一个字节数组。 +然后将其作为消息处理(根据这个字节数组中的ID前缀)。 + +### adnl.message.custom + +```tlb +adnl.message.custom data:bytes = adnl.Message; +``` + +当更高级别的逻辑与请求-响应格式不符时,使用此类消息,这种消息类型允许将处理完全转移到更高级别,因为消息只携带一个字节数组,没有query_id和其他字段。例如,RLDP使用此类消息,因为对许多请求只有一个响应,这种逻辑由RLDP本身控制。 + +### 结论 + +此后的通信基于本文描述的逻辑进行,但数据包的内容取决于更高级别的协议,如DHT和RLDP。 + +## 参考 + +*这里是[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/ADNL-UDP-Internal.md),作者是[Oleg Baranov](https://github.com/xssnick)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/low-level-adnl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/low-level-adnl.md new file mode 100644 index 0000000000..7fa195afd2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/low-level-adnl.md @@ -0,0 +1,110 @@ +# 低层级 ADNL + +抽象数据报网络层(ADNL)是 TON 的核心协议,它帮助网络节点相互通信。 + +## 节点身份 + +每个节点至少应该有一个身份,可以但不必要使用多个。每个身份是一对密钥,用于执行节点间的 Diffie-Hellman。抽象网络地址是从公钥中派生出的,方法如下:`address = SHA-256(type_id || public_key)`。注意,type_id 必须以小端格式的 uint32 序列化。 + +## 公钥加密系统列表 + +| type_id | 加密系统 | +| ---------------------------- | ------------------- | +| 0x4813b4c6 | ed255191 | + +*1. 要执行 x25519,必须以 x25519 格式生成密钥对。然而,公钥通过网络是以 ed25519 格式传输的,因此你必须将公钥从 x25519 转换为 ed25519,可在[此处](https://github.com/tonstack/adnl-rs/blob/master/src/integrations/dalek.rs#L10)找到 Rust 的转换示例,以及在[此处](https://github.com/andreypfau/curve25519-kotlin/blob/f008dbc2c0ebc3ed6ca5d3251ffb7cf48edc91e2/src/commonMain/kotlin/curve25519/MontgomeryPoint.kt#L39)找到 Kotlin 的转换示例。* + +## 客户端-服务器协议(ADNL over TCP) + +客户端通过 TCP 连接到服务器,并发送一个 ADNL 握手包,其中包含服务器抽象地址、客户端公钥和加密的 AES-CTR 会话参数,这些参数由客户端确定。 + +### 握手 + +首先,客户端必须使用其私钥和服务器公钥执行密钥协议(例如,x25519),同时要考虑服务器密钥的 `type_id`。然后,客户端将获得 `secret`,用于在后续步骤中加密会话密钥。 + +再然后,客户端必须生成 AES-CTR 会话参数,一个 16 字节的 nonce 和 32 字节的密钥,分别用于 TX(客户端->服务器)和 RX(服务器->客户端)方向,并将其序列化为一个 160 字节的缓冲区,如下所示: + +| 参数 | 大小 | +| ----------------------------- | ----- | +| rx_key | 32 字节 | +| tx_key | 32 字节 | +| rx_nonce | 16 字节 | +| tx_nonce | 16 字节 | +| padding | 64 字节 | + +填充(padding)的目的未知,服务器实现并不使用。建议用随机字节填充整个 160 字节缓冲区,否则攻击者可能利用被篡改的 AES-CTR 会话参数进行主动中间人攻击。 + +接下来的步骤是使用上述密钥协议中的 `secret` 对会话参数进行加密。为此,必须使用 AES-256 在 CTR 模式下初始化,使用 128 位大端计数器和(key, nonce)对,计算方法如下(`aes_params` 是之前构建的 160 字节缓冲区): + +```cpp +hash = SHA-256(aes_params) +key = secret[0..16] || hash[16..32] +nonce = hash[0..4] || secret[20..32] +``` + +加密 `aes_params` 后,记为 `E(aes_params)`,因为不再需要 AES,所以应该移除 AES。 + +现在我们准备将所有信息序列化到 256 字节的握手包中,并发送给服务器: + +| 参数 | 大小 | 说明 | +| ----------------------------------------------------------- | ------ | --------------- | +| receiver_address | 32 字节 | 服务器节点身份,如相应部分所述 | +| sender_public | 32 字节 | 客户端公钥 | +| SHA-256(aes_params) | 32 字节 | 会话参数的完整性证明 | +| E(aes_params) | 160 字节 | 加密的会话参数 | + +服务器必须使用从密钥协议中以与客户端相同的方式派生的secret解密会话参数。然后,服务器必须执行以下检查以确认协议的安全性: + +1. 服务器必须拥有 `receiver_address` 对应的私钥,否则无法执行密钥协议。 +2. `SHA-256(aes_params) == SHA-256(D(E(aes_params)))`,否则密钥协议失败,双方的 `secret` 不相等。 + +如果这些检查中的任何一个失败,服务器将立即断开连接,不对客户端作出响应。如果所有检查通过,服务器必须向客户端发送一个空数据报(见数据报部分),以证明其拥有指定 `receiver_address` 的私钥。 + +### 数据报 + +客户端和服务器都必须分别为 TX 和 RX 方向初始化两个 AES-CTR 实例。必须使用 AES-256 在 CTR 模式下使用 128 位大端计数器。每个 AES 实例使用属于它的(key, nonce)对进行初始化,可以从握手中的 `aes_params` 中获取。 + +发送数据报时,节点(客户端或服务器)必须构建以下结构,加密它并发送给另一方: + +| 参数 | 大小 | 说明 | +| ------ | ---------------- | --------------------------------------- | +| length | 4 字节(LE) | 整个数据报的长度,不包括 `length` 字段 | +| nonce | 32 字节 | 随机值 | +| buffer | `length - 64` 字节 | 实际要发送给另一方的数据 | +| hash | 32 字节 | `SHA-256(nonce \\|\\| buffer)` 以确保完整性 | + +整个结构必须使用相应的 AES 实例加密(客户端 -> 服务器的 TX,服务器 -> 客户端的 RX)。 + +接收方必须获取前 4 字节,将其解密为 `length` 字段,并准确读取 `length` 字节以获得完整的数据报。接收方可以提前开始解密和处理 `buffer`,但必须考虑到它可能被故意或偶然损坏。必须检查数据报的 `hash` 以确保 `buffer` 的完整性。如果失败,则不能发出新的数据报,连接必须断开。 + +会话中的第一个数据报始终由服务器在成功接受握手包后发送给客户端,其实际缓冲区为空。客户端应该对其进行解密,如果失败,则应与服务器断开连接,因为这意味着服务器没有正确遵循协议,服务器和客户端的实际会话的密钥不同。 + +### 通信细节 + +如果你想深入了解通信细节,可以阅读文章 [ADNL TCP - Liteserver](/develop/network/adnl-tcp) 查看一些示例。 + +### 安全考虑 + +#### 握手填充 + +TON 初始团队为什么决定将此字段包含在握手中尚不清楚。`aes_params` 的完整性受 SHA-256 哈希保护,其保密性受从 `secret` 参数派生的密钥保护。可能,他们打算在某个时点迁移到 AES-CTR。为此,规范可能会扩展以包含 `aes_params` 中的特殊值,来表示节点准备使用的更新的原语。对这样的握手的响应可能会用新旧方案解密两次,以确定另一方实际使用的方案。 + +#### 会话参数加密密钥派生过程 + +如果仅从 `secret` 参数派生加密密钥,它将是静态的,因为secret是静态的。为了为每个会话派生新的加密密钥,开发人员还使用了 `SHA-256(aes_params)`,如果 `aes_params` 是随机的,则它也是随机的。然而,使用不同子数组拼接的实际密钥派生算法是被认为有问题的。 + +#### 数据报 nonce + +数据报中 `nonce` 字段的存在原因不明确,因为即使没有它,任何两个密文也会由于 AES 的会话绑定密钥和在 CTR 模式下的加密而不同。然而,如果 nonce 不存在或可预测,则可以执行以下攻击。CTR 加密模式将区块密码(如 AES)转换为流密码,以便执行位翻转攻击(bit-flipping attack)。如果攻击者知道属于加密数据报的明文,他们可以获得纯密钥流,将其与自己的明文进行XOR,并有效地替换节点发送的消息。缓冲区的完整性受 SHA-256 哈希保护,但攻击者也可以替换它,因为完整的明文信息意味着拥有其哈希的信息。nonce 字段存在是为了防止这种攻击,所以没有 nonce 的信息,攻击者无法替换 SHA-256。 + +## P2P 协议(ADNL over UDP) + +详细描述可以在文章 [ADNL UDP - Internode](/develop/network/adnl-udp) 中找到。 + +## 参考 + +- [开放网络,第 80 页](https://ton.org/ton.pdf) +- [TON 中的 ADNL 实现](https://github.com/ton-blockchain/ton/tree/master/adnl) + +*感谢 [hacker-volodya](https://github.com/hacker-volodya) 为社区做出的贡献!*\ +*此处是 GitHub 上的[原文链接](https://github.com/tonstack/ton-docs/tree/main/ADNL)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/overview.md new file mode 100644 index 0000000000..6777fe7416 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/adnl/overview.md @@ -0,0 +1,38 @@ +# ADNL 协议 + +实现: + +- https://github.com/ton-blockchain/ton/tree/master/adnl + +## 概览 + +TON的基石是抽象数据报网络层(ADNL)。 + +这是一个基于**UDP**在**IPv4**(将来是IPv6)之上运行的覆盖层、点对点、不可靠(小尺寸)数据报协议,如果UDP不可用,可以选择**TCP备选**。 + +## ADNL 地址 + +每个参与者都有一个256位的ADNL地址。 + +ADNL协议允许您仅使用ADNL地址发送(不可靠)和接收数据报。IP地址和端口由ADNL协议隐藏。 + +ADNL地址本质上等同于一个256位的ECC公钥。这个公钥可以任意生成,从而为节点创建尽可能多的不同网络身份。然而,为了接收(并解密)发给接收地址的消息,必须知道相应的私钥。 + +实际上,ADNL地址不是公钥本身;相反,它是一个序列化TL对象的256位SHA256哈希,该对象可以根据其构造器来描述几种类型的公钥和地址。 + +## 加密与安全 + +通常,每个发送的数据报都由发送方签名,并加密,以便只有接收方可以解密消息并通过签名来验证其完整性。 + +## 邻居表 + +通常,一个TON ADNL节点会有一些“邻居节点(neighbors)”,其中包含了其他已知节点的信息,如他们的抽象地址、公钥、IP地址和UDP端口。随着时间的推移,它将逐渐使用从这些已知节点收集的信息扩展此表。这些新信息可以是对特殊查询的回答,或有时是过时记录的移除。 + +ADNL允许您建立点对点的通道和隧道(一系列代理)。 + +还可以在ADNL之上构建类TCP的流协议。 + +## 参阅 + +- 在[低层级ADNL文章](/learn/networking/low-level-adnl)中阅读更多关于ADNL的信息 +- [TON白皮书](https://docs.ton.org/ton.pdf)的第3.1章。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/dht-deep-dive.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/dht-deep-dive.md new file mode 100644 index 0000000000..29a68b5949 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/dht-deep-dive.md @@ -0,0 +1,181 @@ +# DHT + +DHT代表分布式哈希表,本质上是一个分布式的键值数据库,其中网络的每个成员都可以存储某些内容,例如,关于自己的信息。 + +TON中的DHT实现与IPFS中使用的[Kademlia](https://codethechange.stanford.edu/guides/guide_kademlia.html)的实现本质上类似。任何网络成员都可以运行一个DHT节点,生成密钥并存储数据。为此,他需要生成一个随机ID并通知其他节点自己的存在。 + +为了确定在哪个节点上存储数据,使用了一种确定节点与密钥之间“距离”的算法。算法很简单:我们取节点的ID和密钥的ID,执行XOR操作。值越小,节点越近。任务是尽可能接近密钥的节点上存储密钥,以便其他网络参与者可以使用同样的算法,找到可以给出此密钥数据的节点。 + +## 通过密钥查找值 + +让我们看一个查找密钥的示例,[连接到任何DHT节点并通过ADNL UDP建立连接](/develop/network/adnl-udp#packet-structure-and-communication)。 + +例如,我们想找到托管foundation.ton站点的节点的地址和公钥。假设我们已经通过执行DNS合约的Get方法获得了该站点的ADNL地址。ADNL地址的十六进制表示为`516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174`。现在我们的目标是找到拥有此地址的节点的ip、端口和公钥。 + +为此,我们需要获取DHT密钥的ID,首先我们将填充DHT密钥模式: + +```tlb +dht.key id:int256 name:bytes idx:int = dht.Key +``` + +`name`是密钥类型,对于ADNL地址使用“address”,例如,要搜索分片链节点 - 使用“nodes”。但密钥类型可以是任何字节数组,取决于您正在查找的值。 + +填写此模式,我们得到: + +``` +8fde67f6 -- TL ID dht.key +516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174 -- our searched ADNL address +07 61646472657373 -- key type, the word "address" as an TL array of bytes +00000000 -- index 0 because there is only 1 key +``` + +接下来 - 从上面序列化的字节获取DHT密钥ID的sha256哈希。它将是`b30af0538916421b46df4ce580bf3a29316831e0c3323a7f156df0236c5b2f75` + +现在我们可以开始搜索。为此,我们需要执行一个具有[模式](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L197)的查询: + +```tlb +dht.findValue key:int256 k:int = dht.ValueResult +``` + +`key`是我们DHT密钥的id,`k`是搜索的“宽度”,它越小,搜索越精确,但可查询的潜在节点越少。TON节点的最大k为10,通常使用6。 + +我们填充这个结构,序列化并发送请求,使用`adnl.message.query`模式。[您可以在另一篇文章中阅读更多关于此的内容](/develop/network/adnl-udp#packet-structure-and-communication)。 + +作为回应,我们可以得到: + +- `dht.valueNotFound` - 如果未找到值。 +- `dht.valueFound` - 如果此节点上找到了值。 + +##### dht.valueNotFound + +如果我们得到`dht.valueNotFound`,响应将包含我们请求的节点所知并且尽可能接近我们请求的密钥的节点列表。在这种情况下,我们需要连接并将收到的节点添加到我们已知的列表中。 +之后,从我们已知的所有节点列表中选择最接近、可访问且尚未请求的节点,并对其进行相同的请求。如此反复,直到我们尝试了我们选择的范围内的所有节点或直到我们不再收到新节点为止。 + +让我们更详细地分析响应字段,使用的模式: + +```tlb +adnl.address.udp ip:int port:int = adnl.Address; +adnl.addressList addrs:(vector adnl.Address) version:int reinit_date:int priority:int expire_at:int = adnl.AddressList; + +dht.node id:PublicKey addr_list:adnl.addressList version:int signature:bytes = dht.Node; +dht.nodes nodes:(vector dht.node) = dht.Nodes; + +dht.valueNotFound nodes:dht.nodes = dht.ValueResult; +``` + +`dht.nodes -> nodes` - DHT节点列表(数组)。 + +每个节点都有一个`id`,即其公钥,通常是[pub.ed25519](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L47),用作通过ADNL连接到节点的服务器密钥。此外,每个节点都有一个地址列表`addr_list:adnl.addressList`,版本和签名。 + +我们需要检查每个节点的签名,为此我们读取`signature`的值并将该字段置零(我们使其成为空字节数组)。之后 - 我们序列化TL结构`dht.node`并检查空签名。之后 - 我们使用`id`字段中的公钥检查清空之前的`signature`字段。[[实现示例]](https://github.com/xssnick/tonutils-go/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/adnl/dht/client.go#L91) + +从列表`addrs:(vector adnl.Address)`中,我们取地址并尝试建立ADNL UDP连接,作为服务器密钥我们使用`id`,即公钥。 + +为了找出与该节点的“距离” - 我们需要从`id`字段的密钥中取出[key id](/develop/network/adnl-tcp#getting-key-id)并通过节点密钥id和所需密钥的XOR操作检查距离。如果距离足够小,我们可以对此节点发出相同的请求。依此类推,直到我们找到值或没有更多新节点。 + +##### dht.valueFound + +响应将包含值本身,完整的密钥信息,以及可选的签名(取决于值类型)。 + +让我们更详细地分析响应字段,使用的模式: + +```tlb +adnl.address.udp ip:int port:int = adnl.Address; +adnl.addressList addrs:(vector adnl.Address) version:int reinit_date:int priority:int expire_at:int = adnl.AddressList; + +dht.key id:int256 name:bytes idx:int = dht.Key; + +dht.updateRule.signature = dht.UpdateRule; +dht.updateRule.anybody = dht.UpdateRule; +dht.updateRule.overlayNodes = dht.UpdateRule; + +dht.keyDescription key:dht.key id:PublicKey update_rule:dht.UpdateRule signature:bytes = dht.KeyDescription; + +dht.value key:dht.keyDescription value:bytes ttl:int signature:bytes = dht.Value; + +dht.valueFound value:dht.Value = dht.ValueResult; +``` + +首先,让我们分析`key:dht.keyDescription`,它是密钥的完整描述,密钥本身以及谁以及如何可以更新值的信息。 + +- `key:dht.key` - 密钥必须与我们用于搜索的密钥ID的密钥相匹配。 +- `id:PublicKey` - 记录所有者 +- `update_rule:dht.UpdateRule` - 记录更新规则。 +- - `dht.updateRule.signature` - 只有私钥所有者才能更新记录,密钥和值的`signature`都必须有效 +- - `dht.updateRule.anybody` - 任何人都可以更新记录,`signature`为空且不被检查 +- - `dht.updateRule.overlayNodes` - 同一overlay的节点可以更新密钥,用于找到同一overlay的节点并添加自己 + +###### dht.updateRule.signature + +阅读密钥描述后,我们会根据`updateRule`行为。ADNL地址查找案例的类型总是`dht.updateRule.signature`。 +我们以与上次相同的方式检查密钥签名,使签名成为空字节数组、序列化并检查。 之后-我们重复相同的值,即对于整个`dht.value`对象 (同时将密钥签名还原到它的位置)。 + +[[实现示例]](https://github.com/xssnick/tonutils-go/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/adnl/dht/client.go#L331) + +###### dht.updateRule.overlayNodes + +用于包含有关网络中工作链和其分片的其他节点信息的键,值始终具有`overlay.nodes`的TL结构。 +该值字段必须为空。 + +```tlb +overlay.node id:PublicKey overlay:int256 version:int signature:bytes = overlay.Node; +overlay.nodes nodes:(vector overlay.node) = overlay.Nodes; +``` + +要检查有效性,我们必须检查所有 `nodes`,并通过序列化 TL 结构,针对每个节点的 `id` 检查 `signature`: + +```tlb +overlay.node.toSign id:adnl.id.short overlay:int256 version:int = overlay.node.ToSign; +``` + +我们可以看到,id 应替换为 adnl.id.short,即原始结构中 `id` 字段的键 id(哈希值)。序列化后,我们使用数据检查签名。 + +因此,我们得到了能够给我们提供所需工作链分片信息的节点的有效列表。 + +###### dht.updateRule.anybody + +没有签名,任何人都可以更新。 + +#### 使用值 + +当一切验证完毕且 `ttl:int` 值没有过期时,我们就可以开始处理值本身,即 `value:bytes` 。对于 ADNL 地址,内部必须有一个 `adnl.addressList` 结构。 +它将包含与请求的 ADNL 地址相对应的服务器的 IP 地址和端口。在我们的例子中,很可能有 1 个 `foundation.ton` 服务的 RLDP-HTTP 地址。 +我们将使用 DHT 密钥信息中的 `id:PublicKey`作为服务器密钥。 + +建立连接后,我们就可以使用 RLDP 协议请求访问网站页面。此时 DHT 方面的任务已经完成。 + +### 搜索存储区块链状态的节点 + +DHT也用于查找存储工作链及其分片数据的节点的信息。该过程与搜索任何密钥相同,唯一的区别在于密钥本身的序列化和响应的验证,我们将在本节中分析这些点。 + +为了获取例如主链及其分片的数据,我们需要填充TL结构: + +``` +tonNode.shardPublicOverlayId workchain:int shard:long zero_state_file_hash:int256 = tonNode.ShardPublicOverlayId; +``` + +其中`workchain`在主链的情况下将等于-1,它的分片将等于-922337203685477580(0xFFFFFFFFFFFFFFFF),而`zero_state_file_hash`是链的零状态的哈希(file_hash),像其他数据一样,可以从全局网络配置中获取,在`"validator"`字段中 + +```json +"zero_state": { + "workchain": -1, + "shard": -9223372036854775808, + "seqno": 0, + "root_hash": "F6OpKZKqvqeFp6CQmFomXNMfMj2EnaUSOXN+Mh+wVWk=", + "file_hash": "XplPz01CXAps5qeSWUtxcyBfdAo5zVb1N979KLSKD24=" +} +``` + +在我们填充了`tonNode.shardPublicOverlayId`后,我们序列化它并通过哈希获取密钥ID(像往常一样)。 + +我们需要使用结果密钥ID作为`name`来填充`pub.overlay name:bytes = PublicKey`结构,将其包裹在TL字节数组中。接下来,我们序列化它,并从中获取现在的密钥ID。 + +生成的 id 将是 `dht.findValue` 的密钥,而 `name` 字段的值将是 `nodes` 字样。我们重复上一节的过程,一切与上次相同,但 `updateRule` 将是 [dht.updateRule.overlayNodes](#dhtupdateruleoverlaynodes)。 + +经过验证 - 我们将获得公钥(`id`)的节点,这些节点拥有我们工作链和分片的信息。为了获取节点的ADNL地址,我们需要对每个密钥(使用哈希方法)制作ID并重复上述过程,就像`foundation.ton`域的ADNL地址一样。 + +结果是,我们将得到节点的地址,如果需要,我们可以使用[overlay.getRandomPeers](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L237)方法找到此链的其他节点的地址。我们还可以从这些节点接收有关区块的所有信息。 + +## 参考资料 + +*这里是[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/DHT.md),作者是[Oleg Baranov](https://github.com/xssnick)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/ton-dht.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/ton-dht.md new file mode 100644 index 0000000000..da57855790 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/dht/ton-dht.md @@ -0,0 +1,52 @@ +# TON 分布式哈希表(DHT)服务 + +实现: + +- https://github.com/ton-blockchain/ton/tree/master/dht +- https://github.com/ton-blockchain/ton/tree/master/dht-server + +## 概览 + +类 Kademlia 的分布式哈希表(DHT)在 TON 的网络部分扮演着至关重要的角色,用于定位网络中的其他节点。 + +TON DHT 的键是简单的 256 位整数。在大多数情况下,它们是作为 TL-序列化对象的 SHA256 计算得出。 + +这些 256 位键所分配的值本质上是长度有限的任意字节串。这样的字节串的解释由相应键的原像决定; +通常情况下,查找键的节点和存储键的节点都知道这一点。 + +在最简单的情况下,键代表某个节点的 ADNL 地址,值可以是其 IP 地址和端口。 + +TON DHT 的键值映射保存在 DHT 节点上。 + +## DHT 节点 + +每个 DHT 节点都有一个 256 位的 DHT 地址。与 ADNL 地址不同,DHT 地址不应该太频繁地更改,否则其他节点将无法定位它们正在寻找的键。 + +预计键 `K` 的值将存储在与 `K` 最近的 `S` 个 Kademlia 节点上。 + +Kademlia 距离 = 256 位键 `XOR` 256 位 DHT 节点地址(与地理位置无关)。 + +`S` 是一个小参数,例如 `S = 7`,用于提高 DHT 的可靠性(如果我们只在一个节点上保存键,即最接近 `K` 的那个,如果那个单个节点离线,那个键的值将会丢失)。 + +## Kademlia 路由表 + +任何参与 DHT 的节点通常都会维护一个 Kademlia 路由表。 + +它包含编号从 0 到 255 的 256 个桶。第 `i` 个桶将包含一些已知节点的信息(固定数量的“最佳”节点和可能的一些额外候选节点),这些节点与节点的地址 `a` 的 Kademlia 距离在 `2^i` 到 `2^(i+1) − 1` 之间。 + +这些信息包括它们的 DHT 地址、IP 地址和 UDP 端口,以及一些可用性信息,例如最后一次 ping 的时间和延迟。 + +当 Kademlia 节点因某些查询而了解到任何其他 Kademlia 节点时,它会将其放入其路由表的适当的桶中,首先将其作为候选者。然后,如果该桶中的一些“最佳”节点故障(例如,长时间不响应 ping 查询),它们可以被这些候选者中的一些替换。通过这种方式,Kademlia 路由表保持着填充状态。 + +## 键值对 + +可以在 TON DHT 中添加和更新键值对。 + +“更新规则”可能有所不同。在某些情况下,它们简单地允许用新值替换旧值,前提是新值是由所有者/创建者签名(签名必须作为值的一部分保留,以便稍后由其他节点在获取此键的值后进行检查)。 +在其他情况下,旧值以某种方式影响新值。例如,它可以包含一个序列号,只有当新序列号更大时(为了防止重放攻击)才覆盖旧值。 + +TON DHT 不仅用于存储 ADNL 节点的 IP 地址,还用于其他目的 - 它可以存储特定 TON 存储种子的节点地址列表、包含在覆盖子网络中的节点地址列表、TON 服务的 ADNL 地址或 TON 区块链账户的 ADNL 地址等。 + +:::info +更多关于 TON DHT 的信息,请参阅 [DHT](/develop/network/dht) 这篇文章,或阅读 [TON 白皮书](https://docs.ton.org/ton.pdf) 的第 3.2 章。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/overlay.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/overlay.md new file mode 100644 index 0000000000..09d4ede639 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/overlay.md @@ -0,0 +1,75 @@ +# 覆盖子网络 + +TON的架构构建方式使得许多链可以同时且独立地存在于其中 - 它们可以是私有的或公共的。 +节点能够选择它们存储和处理的分片和链。 +同时,由于其通用性,通信协议保持不变。像DHT、RLDP和Overlays这样的协议使这成为可能。 +我们已经熟悉前两者,在本节中我们将了解Overlay是什么。 + +Overlay负责将单个网络划分为额外的子网络。Overlay既可以是公共的,任何人都可以连接,也可以是私有的,需要额外的凭证才能进入,这些凭证只为少数人所知。 + +TON中的所有链,包括主链,都使用它们自己的overlay进行通信。要加入它,你需要找到已经在其中的节点,并开始与它们交换数据。对于公共overlay,你可以使用DHT找到节点。 + +## 与overlay节点的互动 + +我们已经在关于DHT的文章中分析了一个查找overlay节点的例子,在[搜索存储区块链状态的节点](/develop/network/dht#search-for-nodes-that-store-the-state-of-the-blockchain)一节中。在这一节中,我们将专注于与它们的互动。 + +当查询DHT时,我们将获得overlay节点的地址,从中我们可以使用[overlay.getRandomPeers](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L237)查询找出这个overlay的其他节点的地址。一旦我们连接了足够数量的节点,我们就可以从它们那里接收所有区块信息和其他链事件,以及向它们发送我们的交易以供处理。 + +### 寻找更多邻居节点(neighbors) + +让我们看一个在overlay中获取节点的例子。 + +为此,向任何已知的overlay节点发送`overlay.getRandomPeers`请求,序列化TL模式: + +```tlb +overlay.node id:PublicKey overlay:int256 version:int signature:bytes = overlay.Node; +overlay.nodes nodes:(vector overlay.node) = overlay.Nodes; + +overlay.getRandomPeers peers:overlay.nodes = overlay.Nodes; +``` + +`peers` - 应包含我们已知的节点,这样我们就不会再次得到它们,但由于我们还不知道任何节点,`peers.nodes`将是一个空数组。 + +如果我们不只是想获取一些信息,而是想参与overlay并获取广播,我们还应该在`peers`中添加我们节点的信息,从中我们发出请求。当对方获取到我们的信息 - 他们将开始使用ADNL或RLDP向我们发送广播。 + +overlay内的每个请求都必须以TL模式为前缀: + +```tlb +overlay.query overlay:int256 = True; +``` + +`overlay`应该是overlay的id - `tonNode.ShardPublicOverlayId`模式键的id - 与我们用于搜索DHT时使用的相同。 + +我们需要通过简单地连接2个序列化的字节数组来连接2个序列化的模式,`overlay.query`将首先出现,其次是`overlay.getRandomPeers`。 + +我们将结果数组包裹在`adnl.message.query`模式中并通过ADNL发送。作为回应,我们等待`overlay.nodes` - 这将是我们可以连接的overlay节点的列表,并且如果需要,重复向新的节点发送相同的请求,直到我们获得足够的连接。 + +### 功能请求 + +一旦建立了连接,我们可以使用[请求](https://github.com/ton-blockchain/ton/blob/ad736c6bc3c06ad54dc6e40d62acbaf5dae41584/tl/generate/scheme/ton_api.tl#L413) `tonNode.*`访问overlay节点。 + +对于此类请求,使用的是 RLDP 协议。重要的是,不要忘记 `overlay.query` 前缀--overlay中的每个查询都必须使用它。 + +请求本身并无异常,与我们[在有关 ADNL TCP 的文章中所做的](/develop/network/adnl-tcp#getmasterchaininfo)非常相似。 + +例如,"downloadBlockFull "请求使用的是我们已经熟悉的区块 ID 模式: + +```tlb +tonNode.downloadBlockFull block:tonNode.blockIdExt = tonNode.DataFull; +``` + +通过传递它,我们将能够下载关于区块的完整信息,作为回应我们将收到: + +```tlb +tonNode.dataFull id:tonNode.blockIdExt proof:bytes block:bytes is_link:Bool = tonNode.DataFull; + or +tonNode.dataFullEmpty = tonNode.DataFull; +``` + +如果存在,`block`字段将包含TL-B格式的数据。 + +因此,我们可以直接从节点获得信息。 + +## 参考资料 + +*这里是[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/Overlay-Network.md),作者是[Oleg Baranov](https://github.com/xssnick)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/rldp.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/rldp.md new file mode 100644 index 0000000000..5f26c25e5f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/network/protocols/rldp.md @@ -0,0 +1,183 @@ +# RLDP + +RLDP(可靠的大数据报协议)是基于ADNL UDP之上的协议,用于传输大数据块,并包括正向错误校正(FEC)算法来替代另一端的确认包。这使得在网络组件之间更高效地传输数据成为可能,但会消耗更多的流量。 + +RLDP在TON基础设施中广泛使用,例如,从其他节点下载区块并向它们传输数据,访问TON网站和TON存储。 + +## 协议 + +RLDP使用以下TL结构进行通信: + +```tlb +fec.raptorQ data_size:int symbol_size:int symbols_count:int = fec.Type; +fec.roundRobin data_size:int symbol_size:int symbols_count:int = fec.Type; +fec.online data_size:int symbol_size:int symbols_count:int = fec.Type; + +rldp.messagePart transfer_id:int256 fec_type:fec.Type part:int total_size:long seqno:int data:bytes = rldp.MessagePart; +rldp.confirm transfer_id:int256 part:int seqno:int = rldp.MessagePart; +rldp.complete transfer_id:int256 part:int = rldp.MessagePart; + +rldp.message id:int256 data:bytes = rldp.Message; +rldp.query query_id:int256 max_answer_size:long timeout:int data:bytes = rldp.Message; +rldp.answer query_id:int256 data:bytes = rldp.Message; +``` + +序列化结构被包裹在`adnl.message.custom` TL模式中,并通过ADNL UDP发送。RLDP传输用于传输大数据,随机生成`transfer_id`,数据本身由FEC算法处理。生成的片段被包裹在`rldp.messagePart`结构中并发送给对方,直到对方向我们发送`rldp.complete`或超时为止。 + +当接收方收集到组装完整消息所需的`rldp.messagePart`片段时,它会将它们全部连接起来,使用FEC解码并将结果字节数组反序列化为`rldp.query`或`rldp.answer`结构之一,取决于类型(tl前缀id)。 + +### FEC + +有效的正向错误校正算法用于RLDP包括RoundRobin、Online和RaptorQ。 +目前用于数据编码的是[RaptorQ](https://www.qualcomm.com/media/documents/files/raptorq-technical-overview.pdf)。 + +#### RaptorQ + +RaptorQ的本质是将数据分割成所谓的符号 - 同一预定大小的块。 + +从块创建矩阵,并对其应用离散数学运算。这使我们能够从相同的数据创建几乎无限数量的符号。 +所有符号都混合在一起,可以在不向服务器请求额外数据的情况下恢复丢失的数据包,同时使用的数据包比我们循环发送相同片段时少。 + +生成的符号被发送给对方,直到他报告所有数据已收到并通过应用相同的离散运算恢复(解码)。 + +[[RaptorQ在Golang中的实现示例]](https://github.com/xssnick/tonutils-go/tree/46dbf5f820af066ab10c5639a508b4295e5aa0fb/adnl/rldp/raptorq) + +## RLDP-HTTP + +为了与TON Sites互动,使用了封装在RLDP中的HTTP。托管者在任何HTTP网络服务器上运行他的站点,并在旁边启动rldp-http-proxy。TON网络中的所有请求通过RLDP协议发送到代理,代理将请求重新组装为简单的HTTP,并在本地调用原始网络服务器。 + +用户在他的一侧启动代理,例如,[Tonutils Proxy](https://github.com/xssnick/TonUtils-Proxy),并使用`.ton` sites,所有流量都以相反的顺序包裹,请求发送到本地HTTP代理,它通过RLDP将它们发送到远程TON站点。 + +RLDP中的HTTP使用TL结构实现: + +```tlb +http.header name:string value:string = http.Header; +http.payloadPart data:bytes trailer:(vector http.header) last:Bool = http.PayloadPart; +http.response http_version:string status_code:int reason:string headers:(vector http.header) no_payload:Bool = http.Response; + +http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response; +http.getNextPayloadPart id:int256 seqno:int max_chunk_size:int = http.PayloadPart; +``` + +这不是纯文本形式的HTTP,一切都包裹在二进制TL中,并由代理自己解包以发送给网络服务器或浏览器。 + +工作方案如下: + +- 客户端发送`http.request` +- 服务器在接收请求时检查`Content-Length`头 +- - 如果不为0,向客户端发送`http.getNextPayloadPart`请求 +- - 接收到请求时,客户端发送`http.payloadPart` - 请求的正文片段,取决于`seqno`和`max_chunk_size`。 +- - 服务器重复请求,递增`seqno`,直到从客户端接收到所有块,即直到接收到的最后一个块的`last:Bool`字段为真。 +- 处理请求后,服务器发送`http.response`,客户端检查`Content-Length`头 +- - 如果不为0,则向服务器发送`http.getNextPayloadPart`请求,并重复这些操作,就像客户端一样,反之亦然 + +## 请求TON站点 + +为了了解RLDP的工作原理,让我们看一个从TON站点`foundation.ton`获取数据的示例。 +假设我们已经通过调用NFT-DNS合约的Get方法获得了其ADNL地址,[使用DHT确定了RLDP服务的地址和端口](https://github.com/xssnick/ton-deep-doc/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/DHT.md),并[通过ADNL UDP连接到它](https://github.com/xssnick/ton-deep-doc/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/ADNL-UDP-Internal.md)。 + +### 向`foundation.ton`发送GET请求 + +为此,填写结构: + +```tlb +http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response; +``` + +通过填写字段序列化`http.request`: + +``` +e191b161 -- TL ID http.request +116505dac8a9a3cdb464f9b5dd9af78594f23f1c295099a9b50c8245de471194 -- id = {random} +03 474554 -- method = string `GET` +16 687474703a2f2f666f756e646174696f6e2e746f6e2f 00 -- url = string `http://foundation.ton/` +08 485454502f312e31 000000 -- http_version = string `HTTP/1.1` +01000000 -- headers (1) + 04 486f7374 000000 -- name = Host + 0e 666f756e646174696f6e2e746f6e 00 -- value = foundation.ton +``` + +现在让我们将序列化的`http.request`包装进`rldp.query`并且也序列化它: + +``` +694d798a -- TL ID rldp.query +184c01cb1a1e4dc9322e5cabe8aa2d2a0a4dd82011edaf59eb66f3d4d15b1c5c -- query_id = {random} +0004040000000000 -- max_answer_size = 257 KB, can be any sufficient size that we accept as headers +258f9063 -- timeout (unix) = 1670418213 +34 e191b161116505dac8a9a3cdb464f9b5dd9af78594f23f1c295099a9b50c8245 -- data (http.request) + de4711940347455416687474703a2f2f666f756e646174696f6e2e746f6e2f00 + 08485454502f312e310000000100000004486f73740000000e666f756e646174 + 696f6e2e746f6e00 000000 +``` + +### 编码和发送数据包 + +现在我们需要将FEC RaptorQ算法应用到这些数据上。 + +我们创建一个编码器,为此我们需要将结果字节数组转换为固定大小的符号。在TON中,符号大小为768字节。为此,我们将数组分割为768字节的片段。在最后一个片段中,如果它小于768字节,需要用零字节填充到所需大小。 + +我们的数组大小为156字节,这意味着只有1个片段,我们需要用612个零字节填充到768的大小。 + +此外,编码器也会根据数据和符号的大小选择常量,您可以在RaptorQ的文档中了解更多,但为了不陷入数学丛林,我建议使用已实现此类编码的现成库。[[创建编码器的示例]](https://github.com/xssnick/tonutils-go/blob/46dbf5f820af066ab10c5639a508b4295e5aa0fb/adnl/rldp/raptorq/encoder.go#L15) 和 [[符号编码示例]](https://github.com/xssnick/tonutils-go/blob/be3411cf412f23e6889bf0b648904306a15936e7/adnl/rldp/raptorq/solver.go#L26)。 + +符号按循环方式编码和发送:我们最初定义`seqno`为0,并为每个后续编码的数据包增加1。例如,如果我们有2个符号,那么我们编码并发送第一个,增加seqno 1,然后第二个并增加seqno 1,然后再次第一个并增加seqno,此时已经等于2,再增加1。如此直到我们收到对方已接受数据的消息。 + +现在,当我们创建了编码器,我们准备发送数据,为此我们将填写TL模式: + +```tlb +fec.raptorQ data_size:int symbol_size:int symbols_count:int = fec.Type; + +rldp.messagePart transfer_id:int256 fec_type:fec.Type part:int total_size:long seqno:int data:bytes = rldp.MessagePart; +``` + +- `transfer_id` - 随机int256,对于同一数据传输中的所有messageParts相同。 +- `fec_type`是`fec.raptorQ`。 +- - `data_size` = 156 +- - `symbol_size` = 768 +- - `symbols_count` = 1 +- `part`在我们的案例中始终为0,可用于达到大小限制的传输。 +- `total_size` = 156。我们传输数据的大小。 +- `seqno` - 对于第一个数据包将等于0,对于每个后续数据包将递增1,将用作解码和编码符号的参数。 +- `data` - 我们编码的符号,大小为768字节。 + +序列化`rldp.messagePart`后,将其包裹在`adnl.message.custom`中并通过ADNL UDP发送。 + +我们循环发送数据包,不断增加seqno,直到等待来自对方的`rldp.complete`消息,或我们在超时时停止。在我们发送了与我们的符号数量相等的数据包之后,我们可以放慢速度,例如,每10毫秒或更少发送一个附加数据包。额外的数据包用于在数据丢失的情况下恢复,因为UDP是快速但不可靠的协议。 + +[[实现示例]](https://github.com/xssnick/tonutils-go/blob/be3411cf412f23e6889bf0b648904306a15936e7/adnl/rldp/rldp.go#L249) + +### 处理来自`foundation.ton`的响应 + +在发送过程中,我们已经可以期待来自服务器的响应,在我们的例子中我们等待带有`http.response`的`rldp.answer`。它将以与请求发送时相同的方式以RLDP传输的形式发送给我们,但`transfer_id`将被反转(每个字节XOR 0xFF)。我们将收到包含`rldp.messagePart`的`adnl.message.custom`消息。 + +首先,我们需要从传输的第一个接收消息中获取FEC信息,特别是从`fec.raptorQ`消息部分结构中获取`data_size`,`symbol_size`和`symbols_count`参数。我们需要它们来初始化RaptorQ解码器。[[示例]](https://github.com/xssnick/tonutils-go/blob/be3411cf412f23e6889bf0b648904306a15936e7/adnl/rldp/rldp.go#L137) + +初始化后,我们将收到的符号及其`seqno`添加到解码器中,一旦我们积累了等于`symbols_count`的最小所需数量,我们就可以尝试解码完整消息。成功后,我们将发送`rldp.complete`。[[示例]](https://github.com/xssnick/tonutils-go/blob/be3411cf412f23e6889bf0b648904306a15936e7/adnl/rldp/rldp.go#L168) + +结果将是带有与我们发送的`rldp.query`中相同query_id的`rldp.answer`消息。数据必须包含`http.response`。 + +```tlb +http.response http_version:string status_code:int reason:string headers:(vector http.header) no_payload:Bool = http.Response; +``` + +对于主要字段,我认为一切都很清楚,实质与HTTP相同。这里有趣的标志位是`no_payload`,如果它为真,则响应中没有正文,(`Content-Length` = 0)。可以认为服务器的响应已经接收。 + +如果`no_payload` = false,那么响应中有内容,我们需要获取它。为此,我们需要发送一个TL模式`http.getNextPayloadPart`包裹在`rldp.query`中的请求。 + +```tlb +http.getNextPayloadPart id:int256 seqno:int max_chunk_size:int = http.PayloadPart; +``` + +`id`应与我们在`http.request`中发送的相同,`seqno` - 0,对于每个下一个部分+1。`max_chunk_size`是我们准备接受的最大块大小,通常使用128 KB(131072字节)。 + +作为回应,我们将收到: + +```tlb +http.payloadPart data:bytes trailer:(vector http.header) last:Bool = http.PayloadPart; +``` + +如果`last` = true,那么我们已经到达尾部,我们可以将所有部分放在一起,获得完整的响应正文,例如html。 + +## 参考资料 + +*这里是[原文链接](https://github.com/xssnick/ton-deep-doc/blob/master/RLDP.md),作者是[Oleg Baranov](https://github.com/xssnick)。* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/README.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/README.mdx new file mode 100644 index 0000000000..892fb64778 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/README.mdx @@ -0,0 +1,160 @@ +import Button from '@site/src/components/button' + +# 简介 + +在TON区块链上创建、开发和部署智能合约,需要使用[FunC编程语言](/develop/smart-contracts/#func-language)和[TON虚拟机(TVM)](/develop/smart-contracts/#ton-virtual-machine)。 + +## 快速开始:您的第一个智能合约 + +使用_Blueprint_框架编写并部署您的第一个智能合约。 + +Blueprint是一个用于编写、测试和部署智能合约的开发环境。 +要创建一个新的演示项目,请使用以下命令: + +```bash +npm create ton@latest +``` + + + + + + + +## 开始 + +### 有趣且简单的教程 + +使用我们适合初学者的指南开始您的旅程: + +- [TON Hello World:逐步指导编写您的第一个智能合约](https://ton-community.github.io/tutorials/02-contract/) +- [TON速度竞赛教程](https://tonspeedrun.com/) + - [🚩 挑战1:简单NFT部署](https://github.com/romanovichim/TONQuest1) + - [🚩 挑战2:聊天机器人合约](https://github.com/romanovichim/TONQuest2) + - [🚩 挑战3:Jetton自动售卖机](https://github.com/romanovichim/TONQuest3) + - [🚩 挑战4:彩票/抽奖](https://github.com/romanovichim/TONQuest4) + - [🚩 挑战5:在5分钟内创建与合约互动的UI](https://github.com/romanovichim/TONQuest5) + - [🚩 挑战6:分析Getgems市场上的NFT销售](https://github.com/romanovichim/TONQuest6) + +### TON 课程 + +我们自豪地呈现**TON区块链课程**,这是TON区块链的全面指南。该课程适用于想要学习如何在TON区块链上创建智能合约和去中心化应用的开发者。 + +它包括**9个模块**,涵盖了TON区块链的基础知识、智能合约开发生命周期、FunC编程和TON虚拟机(TVM)。 + + + +### 综合指南 + +对于喜欢详请和细节的人,请访问: + +- [如何使用钱包智能合约工作](/develop/smart-contracts/tutorials/wallet) + +## 智能合约示例 + +探索由TON社区提供的现成智能合约示例和工具。 + +:::info 小提示 +专注于使用_FunC_编写的智能合约。通常更好的做法是关注使用FunC(_.fc)而不是低层级Fift(_.fif)语言编写的智能合约。 +::: + +TON上标准的智能合约示例包括钱包、选举(管理TON上的验证)和多签钱包,这些可以在学习时参考。 + + + +## 智能合约最佳实践 + +TON提供了无限可能性。来了解如何在遵循推荐指南的同时进行充分利用。 + +- [智能合约指南](/develop/smart-contracts/guidelines) + +## TON 虚拟机(TVM) + +探索运行您智能合约的引擎。 + +- [TVM概览](/learn/tvm-instructions/tvm-overview) + +## 编程语言 + +### 📘 FunC + +为TON智能合约量身定制的语言。 + + + +### 📒 Tact + +类似于TypeScript和Rust的TON智能合约高级语言。 + +:::caution +由社区开发。谨慎使用。 +::: + + + + + +### 📕 Fift(高级) + +:::caution 高级水平 +只适用于勇敢者! +::: + + + +## 社区工具 + +- [disintar/toncli](/develop/smart-contracts/sdk/toncli) — toncli是用于构建、部署和测试FunC合约的命令行界面。 +- [MyLocalTON](/participate/run-nodes/local-ton) — MyLocalTON用于在您的本地环境中运行私有TON区块链。 +- [tonwhales.com/tools/boc](https://tonwhales.com/tools/boc) — BOC解析器 +- [tonwhales.com/tools/introspection-id](https://tonwhales.com/tools/introspection-id) — crc32生成器 +- [@orbs-network/ton-access](https://www.orbs.com/ton-access/) — 去中心化API网关 + +## 进一步阅读 + +通过这些社区驱动的教育资源提高您的技能。 + +- [TON FunC学习路径](https://blog.ton.org/func-journey) ([俄文版](https://github.com/romanovichim/TonFunClessons_ru)) +- [YouTube教程](https://www.youtube.com/@TONDevStudy) [\[俄文版\]](https://www.youtube.com/@WikiMar) + +## 额外资源 + +- [什么是区块链?什么是智能合约?什么是gas?](https://blog.ton.org/what-is-blockchain) +- [理解交易费用](/develop/smart-contracts/fees#how-to-calculate-fees) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/addresses.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/addresses.md new file mode 100644 index 0000000000..5085605aef --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/addresses.md @@ -0,0 +1,175 @@ +# 智能合约地址 + +本节将描述TON区块链上智能合约地址的特点,并解释在TON上,actor与智能合约是如何等同的。 + +## 一切皆为智能合约 + +在TON上,智能合约是使用[Actor模型](/learn/overviews/ton-blockchain#single-actor)构建的。实际上,在TON中的actor在技术上是以智能合约的形式表示的。这意味着,即使您的钱包也是一个简单的actor(以及一个智能合约)。 + +通常,actor处理传入消息,改变其内部状态,并生成传出消息。这就是为什么TON区块链上的每一个actor(即智能合约)都必须有一个地址,以便能够从其他actor接收消息。 + +:::info 以太坊虚拟机(EVM) +在以太坊虚拟机(EVM)上,地址与智能合约完全分离。欢迎阅读Tal Kol的文章["TON 区块链的六个独特之处,会让 Solidity 开发者感到惊讶"](https://blog.ton.org/six-unique-aspects-of-ton-blockchain-that-will-surprise-solidity-developers) 了解更多差异。 +::: + +## 智能合约的地址 + +在TON上运行的智能合约地址通常包含两个主要组成部分: + +- **(workchain_id)**:代表工作链ID(一个有符号的32位整数) + +- **(account_id)** 代表账户的地址(根据工作链不同,为64-512位,) + +在本文档的原始地址概述部分,我们将讨论\*\*(workchain_id, account_id)\*\* 是如何呈现。 + +### 工作链ID和账户ID + +#### 工作链ID + +[正如我们之前所见](/learn/overviews/ton-blockchain#workchain-blockchain-with-your-own-rules),在TON区块链上可以创建多达`2^32`个工作链。我们还注意到,32位前缀的智能合约地址用于识别并链接到不同工作链中的智能合约地址。这允许智能合约在TON区块链的不同工作链之间发送和接收消息。 + +如今,TON区块链中仅运行主链(workchain_id=-1)和不定期地运行基本工作链(workchain_id=0)。 + +它们都有256位地址,因此,我们假设workchain_id是0或-1,工作链中的地址正好是256位。 + +#### 账户ID + +TON的所有账户ID都在主链和基本链(或基本工作链)上使用256位地址。 + +实际上,账户ID **(account_id)** 被定义为智能合约对象的哈希函数(专指SHA-256)。每个在TON区块链上运行的智能合约都存储两个主要组件。这些包括: + +1. *编译后的代码*。智能合约的逻辑以字节码形式编译。 +2. *初始状态*。合约在链上部署时的值。 + +最后,为了准确地推导出合约的地址,需要计算与\*\*(初始代码,初始状态)\*\* 对象相对应的哈希。目前,我们不会深入研究[TVM](/learn/tvm-instructions/tvm-overview)的工作方式,但重要的是要理解TON上的账户ID是使用这个公式确定的: +: +**account_id = hash(初始代码,初始状态)** + +随着本文档的深入,我们将进一步深入技术规格和TVM及TL-B方案的概述。现在我们熟悉了**account_id**的生成以及它们与TON上智能合约地址的交互,接下来让我们解释什么是原始地址和用户友好地址。 + +## 原始地址和用户友好地址 + +在简要概述了TON上的智能合约地址是如何利用工作链和账户ID(特别是对于主链和基本链)之后,那重要的是要理解这些地址以下面两种主要格式表示: + +- **原始地址**:智能合约地址的原始完整表示。 +- **用户友好地址**:用户友好地址是原始地址的增强格式,有更好的安全性和易用性。 + +以下,我们将更详细地解释这两种地址类型的区别,并深入探讨TON上使用用户友好地址的原因。 + +### 原始地址 + +原始智能合约地址由工作链ID和账户ID组成\*(workchain_id, account_id)\*,并以以下格式显示: + +- [十进制workchain_id\]:[64个十六进制数字的account_id\] + +以下是一个使用工作链ID和账户ID的原始智能合约地址示例(表示为**workchain_id**和**account_id**): + +`-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260` + +注意地址字符串开始的`-1`,表示属于主链的_workchain_id_。 + +:::note +地址字符串中可以使用大写字母(如 'A'、'B'、'C'、'D'等)替代其小写的对应字母(如 'a'、'b'、'c' 'd'等)。 +::: + +#### 原始地址的问题 + +使用原始地址形式存在两个主要问题: + +1. 在使用原始地址格式时,无法在发送交易前验证地址以消除错误。 + 这意味着,如果您在发送交易前不小心在地址字符串中添加或删除字符,您的交易将被发送到错误的目的地,导致资金损失。 +2. 在使用原始地址格式时,无法添加像使用用户友好地址时发送交易所用的特殊标志位。 + 为了帮助您更好地理解这个概念,我们将在下面解释可以使用哪些标志位。 + +### 用户友好地址 + +用户友好地址是为了保护和简化在互联网上(例如,在公共消息平台上或通过电子邮件服务提供商)以及现实世界中分享地址的TON用户的体验而开发的。 + +#### 用户友好地址结构 + +用户友好地址总共由36个字节组成,按顺序生成以下组件: + +1. *[标志位 - 1字节]* — 附加到地址的标志位会改变智能合约对收到的消息的反应。 + 使用用户友好地址格式的标志位类型包括: + + - isBounceable。表示可弹回或不可弹回的地址类型。(*0x11* 表示“可弹回”,*0x51* 表示“不可弹回”) + - isTestnetOnly。表示仅用于测试网的地址类型。以_0x80_ 开头的地址不应被生产网络上运行的软件接受 + - isUrlSafe。表示已定义为地址的URL安全的已弃用标志位。所有地址都被认为是URL安全的。 +2. *\[workchain_id - 1字节]* — 工作链ID (*workchain_id*) 由一个有符号的8位整数 *workchain_id* 定义。\ + (*0x00* 表示基本链,*0xff* 表示主链) +3. *\[account_id - 32字节]* — 账户ID由工作链中的256位([大端序](https://www.freecodecamp.org/news/what-is-endianness-大端-vs-little-endian/))地址组成。 +4. *\[地址验证 - 2字节]* — 在用户友好地址中,地址验证由前34个字节的CRC16-CCITT签名组成。([示例](https://github.com/andreypfau/ton-kotlin/blob/ce9595ec9e2ad0eb311351c8a270ef1bd2f4363e/ton-kotlin-crypto/common/src/crc32.kt)) + 实际上,用户友好地址的验证思想与所有信用卡上使用的[Luhn算法](https://en.wikipedia.org/wiki/Luhn_algorithm)类似,以防止用户错误输入不存在的卡号。 + +这4个主要组件的总计`1 + 1 + 32 + 2 = 36`字节(每个用户友好地址)。 + +要生成用户友好地址,开发者必须使用以下方式对所有36个字节进行编码: + +- *base64*(即数字、大小写拉丁字母、'/' 和 '+') +- *base64url*(用 '_' 和 '-' 代替 '/' 和 '+') + +完成这个过程后,会生成一个长度为48个非空格字符的用户友好地址。 + +:::info DNS地址标志位 +在TON上,有时使用诸如mywallet.ton之类的DNS地址,而不是原始和用户友好地址。实际上,DNS地址由用户友好地址组成,并包括所有必需的标志位,允许开发者从TON域中的DNS记录访问所有标志位。 +::: + +#### 用户友好地址编码示例 + +例如,“测试赠予者”智能合约(一个特殊的智能合约,位于测试网主链中,向任何请求者发送2个测试代币)使用以下原始地址: + +`-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260` + +上述“测试赠予者”的原始地址必须转换为用户友好地址形式。这可以通过使用之前介绍的base64或base64url形式来获得,如下所示: + +- `kf/8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15+KsQHFLbKSMiYIny` (base64) +- `kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny` (base64url) + +:::info +注意,*base64* 和 *base64url* 两种形式都是有效的,都会被接受! +::: + +#### 可弹回与不可弹回地址 + +可弹回地址标志位背后的核心思想是发件人资金的安全。 + +例如,如果目标智能合约不存在,或者在交易过程中发生某些问题,消息将被“弹回”给发件人,并包括交易原始价值的剩余部分(减去所有转账和燃料费)。这确保了发件人不会因意外发送到无法接受交易的地址而损失资金。 + +关于可弹回地址特别说明: + +1. **bounceable=false** 标志位通常意味着接收者是一个钱包。 +2. **bounceable=true** 标志位通常表示具有自己应用逻辑的自定义智能合约(例如,DEX)。在这个例子中,因为安全原因不应发送非弹回消息。 + +请阅读我们的文档以更好地了解[不可弹回消息](/develop/smart-contracts/guidelines/non-bouncable-messages)。 + +#### base64加固型表示 + +TON区块链相关的附加二进制数据采用类似的"加固型" base64 用户友好地址表示。它们根据字节标签的前4个字符来进行区分。例如,256位Ed25519公钥通过以下顺序创建的36字节序列来表示: + +- 使用_0x3E_格式的单字节标签表示公钥 +- 使用_0xE6_格式的单字节标签表示Ed25519公钥 +- 32字节标签含标准二进制表示的Ed25519公钥 +- 2字节标签含大端序表示的前34字节的CRC16-CCITT + +获得的36字节序列会转换为标准方式的48字符base64或base64url字符串。例如,Ed25519公钥`E39ECDA0A7B0C60A7107EC43967829DBE8BC356A49B9DFC6186B3EAC74B5477D`(通常由32字节序列表示,例如:`0xE3, 0x9E, ..., 0x7D`)通过“加固型”表示呈现如下: + +`Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2` + +### 用户友好地址和原始地址的转换 + +转换用户友好和原始地址的最简单方式是使用几个TON API和其他工具,包括: + +- [ton.org/address](https://ton.org/address) +- [toncenter主网的API方法](https://toncenter.com/api/v2/#/accounts/pack_address_packAddress_get) +- [toncenter测试网的API方法](https://testnet.toncenter.com/api/v2/#/accounts/pack_address_packAddress_get) + +此外,使用JavaScript为钱包转换用户友好和原始地址有两种方式: + +- [使用ton.js转换地址的形式,从/到用户友好或原始形式](https://github.com/ton-org/ton-core/blob/main/src/address/Address.spec.ts) +- [使用tonweb转换地址的形式,从/到用户友好或原始形式](https://github.com/toncenter/tonweb/tree/master/src/utils#address-class) + +还可以使用[SDKs](/develop/dapps/apis/sdk)进行类似的转换。 + +### 地址示例 + +在 [TON Cookbook](/develop/dapps/cookbook#working-with-contracts-addresses) 中了解有关 TON 地址的更多示例。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/examples.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/examples.md new file mode 100644 index 0000000000..a6399fc181 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/examples.md @@ -0,0 +1,164 @@ +# 智能合约示例 + +在这个页面上,您可以找到为各种程序软件实现的TON智能合约的参考。 + +:::info +确保在生产环境中使用它们之前彻底测试合约。这是确保您的软件正常运行和安全的关键步骤。 +::: + +## FunC智能合约 + +### 生产环境中使用的合约 + +| 合约 | 描述 | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| [wallet-contract](https://github.com/ton-blockchain/wallet-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/wallet-contract\&name=wallet-contract) | Wallet v4是提出用于替换v3或更早的钱包的钱包版本 | +| [liquid-staking-contract](https://github.com/ton-blockchain/liquid-staking-contract/)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/liquid-staking-contract/\&name=liquid-staking-contract) | Liquid Staking (LSt)是一个协议,连接所有水平的TON持有者与硬件节点运营商,通过资产池参与TON区块链验证。 | +| [modern_jetton](https://github.com/EmelyanenkoK/modern_jetton)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/EmelyanenkoK/modern_jetton\&name=modern_jetton) | 实现标准jetton,附加withdraw_tons和withdraw_jettons功能。 | +| [governance-contract](https://github.com/ton-blockchain/governance-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/governance-contract\&name=governance-contract) | TON区块链核心合约`elector-code.fc`和`config-code.fc`。 | +| [bridge-func](https://github.com/ton-blockchain/bridge-func)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/bridge-func\&name=bridge-func) | TON-EVM Toncoin桥。 | +| [token-bridge-func](https://github.com/ton-blockchain/token-bridge-func)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/token-bridge-func\&name=token-bridge-func) | TON-EVM代币桥 - FunC智能合约。 | +| [lockup-wallet-contract/universal](https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/universal)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/universal\&name=lockup-wallet-contract/universal) | Universal lockup wallet是可以存储锁定的和受限的代币的合约。 | +| [lockup-wallet-contract/vesting](https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/vesting)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/vesting\&name=lockup-wallet-contract/vesting) | Vesting钱包智能合约 | +| [multisig-contract](https://github.com/ton-blockchain/multisig-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/multisig-contract\&name=multisig-contract) | `(n, k)`-多签名钱包是一个拥有`n`个私钥持有者的钱包,如果请求收集到至少`k`个持有者的签名,则接受发送消息的请求。 | +| [token-contract](https://github.com/ton-blockchain/token-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/token-contract\&name=token-contract) | 可替代、不可替代、半可替代代币智能合约 | +| [dns-contract](https://github.com/ton-blockchain/dns-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/dns-contract\&name=dns-contract) | `.ton`区域的智能合约。 | +| [nominator-pool](https://github.com/ton-blockchain/nominator-pool)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/nominator-pool\&name=nominator-pool) | Nominator池智能合约 | +| [single-nominator-pool](https://github.com/orbs-network/single-nominator)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/nominator-pool\&name=nominator-pool) | 单一Nominator池智能合约 | +| [vesting-contract](https://github.com/ton-blockchain/vesting-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/nominator-pool\&name=nominator-pool) | Nominator池智能合约 | +| [storage](https://github.com/ton-blockchain/ton/tree/master/storage/storage-daemon/smartcont)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/ton/tree/master/storage/storage-daemon/smartcont\&name=storage) | TON存储提供商和制造合约 | +| [vesting-contract](https://github.com/ton-blockchain/vesting-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/nominator-pool\&name=nominator-pool) | Nominator池智能合约 | +| [ton-random](https://github.com/ton-blockchain/ton/tree/master/storage/storage-daemon/smartcont)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-blockchain/ton/tree/master/storage/storage-daemon/smartcont\&name=storage) | TON存储提供商和制造合约 | + +### 生态系统合约 + +| 合约 | 描述 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| [telemint](https://github.com/TelegramMessenger/telemint)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/TelegramMessenger/telemint\&name=telemint) | Telegram用户名(`nft-item.fc`)和Telegram号码(`nft-item-no-dns.fc`)合约。 | +| [WTON](https://github.com/TonoxDeFi/WTON)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/TonoxDeFi/WTON\&name=WTON) | 此智能合约提供了称为WTON的wrapped toncoin的实现 | +| [capped-fungible-token](https://github.com/TonoxDeFi/capped-fungible-token)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/TonoxDeFi/capped-fungible-token\&name=capped-fungible-token) | Jetton钱包和Jetton铸币的基本智能合约实现 | +| [getgems-io/nft-contracts](https://github.com/getgems-io/nft-contracts/tree/main/packages/contracts/sources)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/getgems-io/nft-contracts/tree/main/packages/contracts/sources\&name=getgems-io/nft-contracts) | Getgems NFT合约 | +| [lockup-wallet-deployment](https://github.com/ton-defi-org/lockup-wallet-deployment)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-defi-org/lockup-wallet-deployment\&name=lockup-wallet-deployment) | 部署和运行锁定合约的端到端实现 | +| [wton-contract](https://github.com/ton-community/wton-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-community/wton-contract\&name=wton-contract) | wTON合约 | +| [contract-verifier-contracts](https://github.com/ton-community/contract-verifier-contracts)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-community/contract-verifier-contracts\&name=contract-verifier-contracts) | 存储每个代码cell哈希的链上证明的源注册合约。 | +| [vanity-contract](https://github.com/ton-community/vanity-contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-community/vanity-contract\&name=vanity-contract) | 允许为任何合约“挖掘”任何合适地址的智能合约。 | +| [ton-config-smc](https://github.com/ton-foundation/ton-config-smc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-foundation/ton-config-smc\&name=ton-config-smc) | 简单的用于在TON区块链中存储版本化数据的合约。 | +| [ratelance](https://github.com/ProgramCrafter/ratelance/tree/main/contracts/func)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ProgramCrafter/ratelance/tree/main/contracts/func\&name=ratelance) | Ratelance是一个自由职业平台,旨在消除潜在雇主和工作者之间的障碍。 | +| [ton-forwarder.fc](https://github.com/TrueCarry/ton-contract-forwarder/blob/main/func/ton-forwarder.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/TrueCarry/ton-contract-forwarder/blob/main/func/ton-forwarder.fc\&name=ton-forwarder.fc) | 接受确切金额并将其转发到指定地址的合约。错误金额或后续退款时退还资金。 | +| [logger.fc](https://github.com/tonwhales/ton-contracts/blob/master/contracts/logger.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/tonwhales/ton-contracts/blob/master/contracts/logger.fc\&name=logger.fc) | 将数据保存在本地存储中的合约。 | +| [ton-nominators](https://github.com/tonwhales/ton-nominators)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/tonwhales/ton-nominators\&name=ton-nominators) | Ton Whales Nominator池源代码。 | +| [ton-link-contract-v3](https://github.com/ton-link/ton-link-contract-v3)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-link/ton-link-contract-v3\&name=ton-link-contract-v3) | Ton-link允许智能合约访问区块链外的数据,同时保持数据安全。 | +| [delab-team/fungible-token](https://github.com/delab-team/contracts/tree/main/fungible-token)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/delab-team/contracts/tree/main/fungible-token\&name=delab-team/fungible-token) | DeLab TON可替代代币实现 | +| [whitelisted-wallet.fc](https://github.com/tonwhales/ton-contracts/blob/master/contracts/whitelisted-wallet.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/tonwhales/ton-contracts/blob/master/contracts/whitelisted-wallet.fc\&name=whitelisted-wallet.fc) | 简单的白名单钱包合约 | +| [delab-team/jetton-pool](https://github.com/delab-team/contracts/tree/main/jetton-pool)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/delab-team/contracts/tree/main/jetton-pool\&name=delab-team/jetton-pool) | Jetton Pool TON智能合约旨在创建farm pools。 | +| [ston-fi/contracts](https://github.com/ston-fi/dex-core/tree/main/contracts)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ston-fi/dex-core/tree/main/contracts\&name=ston-fi/contracts) | Stonfi DEX核心合约 | +| [onda-ton](https://github.com/0xknstntn/onda-ton)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/0xknstntn/onda-ton\&name=onda-ton) | Onda借贷池 - TON上首个借贷协议的核心智能合约 | +| [ton-stable-timer](https://github.com/ProgramCrafter/ton-stable-timer)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ProgramCrafter/ton-stable-timer\&name=ton-stable-timer) | TON稳定计时器合约 | +| [HipoFinance/contract](https://github.com/HipoFinance/contract)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/HipoFinance/contract\&name=HipoFinance) | hTON是TON区块链上的去中心化、无需许可的开源流动性质押协议 | + +### 学习合约 + +| 合约 | 描述 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- | +| [counter.fc](https://github.com/ton-community/blueprint/blob/main/example/contracts/counter.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-community/blueprint/blob/main/example/contracts/counter.fc\&name=counter.fc) | 带有评论的counter(计数器)智能合约。 | +| [simple-distributor](https://github.com/ton-community/simple-distributor)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/ton-community/simple-distributor\&name=simple-distributor) | 简单的TON分发器。 | +| [ping-pong.fc](https://github.com/tonwhales/ton-nft/blob/main/packages/nft/ping-pong/ping-pong.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/tonwhales/ton-nft/blob/main/packages/nft/ping-pong/ping-pong.fc\&name=ping-pong.fc) | 测试以不同模式发送Toncoin的简单合约。 | +| [ton-random](https://github.com/puppycats/ton-random)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/puppycats/ton-random\&name=ton-random) | 将帮助您在链上生成随机数的两个合约。 | +| [Blueprint simple contract](https://github.com/liminalAngel/1-func-project/blob/master/contracts/main.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/liminalAngel/1-func-project/blob/master/contracts/main.fc\&name=simple_contract) | 示例智能合约 | +| [Blueprint jetton_minter.fc](https://github.com/liminalAngel/func-blueprint-tutorial/blob/master/6/contracts/jetton_minter.fc)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/liminalAngel/func-blueprint-tutorial/blob/master/6/contracts/jetton_minter.fc\&name=jetton_minter.fc) | 铸造Jettons的智能合约示例。 | +| [Simple TON DNS Subdomain manager](https://github.com/Gusarich/simple-subdomain)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/Gusarich/simple-subdomain\&name=Simple_TON_DNS_Subdomain_manager) | TON DNS子域名管理器。 | +| [disintar/sale-dapp](https://github.com/disintar/sale-dapp/tree/master/func)
🪄 [在WebIDE中运行](https://ide.nujan.io/?importURL=https://github.com/disintar/sale-dapp/tree/master/func\&name=disintar/sale-dapp) | React + NFT销售DApp与FunC | + +### TON智能挑战 + +#### TON智能挑战1 + +- https://github.com/nns2009/TON-FunC-contest-1/tree/main +- https://github.com/pyAndr3w/func-contest1-solutions +- https://github.com/crazyministr/TonContest-FunC/tree/master/func-contest1 + +#### TON智能挑战2 + +- https://github.com/ton-blockchain/func-contest2-solutions +- https://github.com/nns2009/TON-FunC-contest-2 +- https://github.com/crazyministr/TonContest-FunC/tree/master/func-contest2 + +#### TON智能挑战3 + +- https://github.com/nns2009/TON-FunC-contest-3 +- https://github.com/shuva10v/func-contest3-solutions +- https://github.com/crazyministr/TonContest-FunC/tree/master/func-contest3 + +#### TON智能挑战4 + +- https://github.com/akifoq/tsc4 (最佳优化) +- https://github.com/Gusarich/tsc4 +- https://github.com/Skydev0h/tsc4 +- https://github.com/aSpite/tsc4-contracts (FunC解决方案) +- [https://github.com/ProgramCrafter/tsc4](https://github.com/ProgramCrafter/tsc4/tree/c1616e12d1b449b01fdcb787a3aa8442e671371e/contracts) (FunC解决方案) + +## Fift智能合约 + +- [CreateState.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/CreateState.fif) +- [asm-to-cpp.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/asm-to-cpp.fif) +- [auto-dns.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/auto-dns.fif) +- [complaint-vote-req.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/complaint-vote-req.fif) +- [complaint-vote-signed.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/complaint-vote-signed.fif) +- [config-proposal-vote-req.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/config-proposal-vote-req.fif) +- [config-proposal-vote-signed.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/config-proposal-vote-signed.fif) +- [create-config-proposal.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/create-config-proposal.fif) +- [create-config-upgrade-proposal.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/create-config-upgrade-proposal.fif) +- [create-elector-upgrade-proposal.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/create-elector-upgrade-proposal.fif) +- [envelope-complaint.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/envelope-complaint.fif) +- [gen-zerostate-test.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/gen-zerostate-test.fif) +- [gen-zerostate.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/gen-zerostate.fif) +- [highload-wallet-v2-one.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet-v2-one.fif) +- [highload-wallet-v2.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet-v2.fif) +- [highload-wallet.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet.fif) +- [manual-dns-manage.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/manual-dns-manage.fif) +- [new-auto-dns.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-auto-dns.fif) +- [new-highload-wallet-v2.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-highload-wallet-v2.fif) +- [new-highload-wallet.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-highload-wallet.fif) +- [new-manual-dns.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-manual-dns.fif) +- [new-pinger.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-pinger.fif) +- [new-pow-testgiver.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-pow-testgiver.fif) +- [new-restricted-wallet.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-restricted-wallet.fif) +- [new-restricted-wallet2.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-restricted-wallet2.fif) +- [new-restricted-wallet3.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-restricted-wallet3.fif) +- [new-testgiver.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-testgiver.fif) +- [new-wallet-v2.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-wallet-v2.fif) +- [new-wallet-v3.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-wallet-v3.fif) +- [new-wallet.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/new-wallet.fif) +- [show-addr.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/show-addr.fif) +- [testgiver.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/testgiver.fif) +- [update-config-smc.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/update-config-smc.fif) +- [update-config.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/update-config.fif) +- [update-elector-smc.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/update-elector-smc.fif) +- [validator-elect-req.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/validator-elect-req.fif) +- [validator-elect-signed.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/validator-elect-signed.fif) +- [wallet-v2.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet-v2.fif) +- [wallet-v3.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet-v3.fif) +- [wallet.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet.fif) +- [wallet-v3-code.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet-v3-code.fif) + +## FunC库和帮助工具 + +- https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/crypto/elliptic-curves +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/math +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/messages +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/slices +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/strings +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/tuples +- https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/utils +- https://github.com/disintar/sale-dapp/tree/master/func + +## 添加参考 + +如果您想分享新的智能合约示例,请为这个[页面](https://github.com/ton-community/ton-docs/tree/main/docs/develop/smart-contracts/examples.md)提交您的PR。 + +## 参阅 + +- [开发智能合约简介](/develop/smart-contracts/) +- [如何使用钱包智能合约](/develop/smart-contracts/tutorials/wallet) +- [[YouTube] Ton Dev 研究 FunC & BluePrint课程](https://www.youtube.com/watch?v=7omBDfSqGfA\&list=PLtUBO1QNEKwtO_zSyLj-axPzc9O9rkmYa) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/governance.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/governance.md new file mode 100644 index 0000000000..1d25ae1fb9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/contracts-specs/governance.md @@ -0,0 +1,82 @@ +# 治理合约 + +在TON中,与TVM、catchain、费用和链拓扑(以及这些参数如何存储和更新)有关的节点操作共识参数由一组特殊的智能合约控制(与前几代区块链采用的旧式和不灵活的硬编码这些参数的方式不同)。通过这种方式,TON实现了全面透明的链上治理。这组特殊合约本身受参数控制,目前包括选举人、配置合约和DNS合约,将来将通过额外的货币铸币和其他合约进行扩展。 + +## 选举人(Elector) + +选举人智能合约控制验证轮次的更替方式、谁获得验证区块链的职责以及如何分配验证奖励。如果您想成为验证者并与选举人互动,请查看[验证者说明](https://ton.org/validator)。 + +选举人存储未提取的Toncoin数据在`credits`哈希表中,新的申请在`elect`哈希表中,以及关于以往选举的信息在 *past\*elections* 哈希表中(后者存储在关于验证者不当行为的 *complaints* 和_frozen*-已完成轮次的验证者质押中,这些质押被扣留用于 `stake_held_for`(配置参数15))。选举人合约有三个目的: + +- 处理验证者选举的申请 +- 举行选举 +- 处理验证者不当行为的报告 +- 分配验证奖励 + +### 处理申请 + +要创建申请,未来的验证者需要形成一个包含相应参数(ADNL地址、公钥、`max_factor`等)的特殊消息,将其附加到一定数量的TON(称为质押),并发送给选举人。反过来,选举人检查这些参数,要么注册申请,要么立即将质押退还给发送者。请注意,只接受来自主链地址的申请。 + +### 举行选举 + +选举人是一个特殊的智能合约,可以在每个区块的开始和结束时被强制调用(所谓的Tick和Tock交易)。选举人确实在每个区块上被调用,并检查是否是举行新选举的时候。 + +选举过程的总体概念是考虑所有申请,特别是它们的TON数量和`max_factor`(此申请人同意做的验证工作与最弱验证者做的验证工作相比的最大比例),并按照TON数量为每个验证者设置权重,但要满足所有`max_factor`条件。 + +技术实现如下: + +1. 选举人取出所有质押金额高于当前网络最低`min_stake`(配置参数17)的申请。 +2. 按质押金额降序排列。 +3. 如果参与者多于验证者的最大数量(`max_validators` 配置参数16),则舍弃名单尾部。 +4. 从`1`循环到`N`(剩余参与者数量)。 + +- 取名单中按降序排列的第`i`个元素 +- 假设第_i_个候选人将是最后一个被接受的(因此权重最低),并根据`max_factor`计算有效质押(`true_stake`)。换句话说,第_j_个(`j 0 +vesting_total_duration <= 135 years (2^32 seconds) +unlock_period > 0 +unlock_period <= vesting_total_duration +cliff_duration >= 0 +cliff_duration < vesting_total_duration +vesting_total_duration mod unlock_period == 0 +cliff_duration mod unlock_period == 0 +``` + +尽管智能合约不检查这些条件的符合性,但在合约部署后且在发送Toncoin之前,用户可以通过get方法验证所有参数是否OK。 + +## 锁定 + +在`vesting_start_time`之前,所有`vesting_total_amount`都被锁定。 + +从`vesting_start_time`开始,金额开始按比例解锁。 + +例如,如果`vesting_total_duration`为10个月,`unlock_period`为1个月,且`vesting_total_amount`为500 TON,则每月将解锁500\*(10/100)=50 TON,10个月后将解锁全部500 TON。 + +如果有悬崖期,在此悬崖期内不解锁任何金额,过了悬崖期后,按上述公式解锁。 + +例如,如果`cliff_period`为3个月,其他参数与前例相同,则前3个月不会解锁任何金额,3个月后一次性解锁150 TON(然后每个月解锁50 TON)。 + +Get方法`get_locked_amount(int at_time)`允许您计算在某个时间点将锁定多少金额。 + +您只能将锁定的Toncoin发送到白名单地址或`vesting_sender_address`。 + +您可以随时随地发送已解锁的Toncoin。 + +## 白名单 + +白名单是一系列地址,即使还有Toncoin被锁定,也可以向其发送Toncoin。 + +Get方法`get_whitelist()`以(wc, hash_part)元组列表形式返回所有白名单地址。 + +Get方法`is_whitelisted(slice address)`检查此地址是否在白名单上。 + +`vesting_sender_address`可以随时通过`op::add_whitelist`消息向白名单添加新地址。 + +无法从白名单中移除地址。 + +此外,始终可以将锁定的代币发送到`vesting_sender_address`(无需单独添加到白名单)。 + +## 充值 + +您可以从任何地址向归属合约发送Toncoin。 + +## 钱包智能合约 + +该合约设计类似于[标准钱包V3智能合约](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet3-code.fc)。 + +在其数据中,它保留`seqno`、`subwallet_id`、`public_key`,并接受相同格式的外部消息。 + +Get方法`seqno()`、`get_subwallet_id()`和`get_public_key()`可用。 + +与标准钱包不同,归属合约一次只允许发送一条消息。 + +## 发送 + +公钥的所有者可以通过外部消息发起从归属合约发送Toncoin的请求,就像在标准钱包中一样。 + +也可以通过从`owner_address`发送的`op::send`内部消息发起Toncoin的发送。 + +实际上,公钥和`owner_address`通常由同一用户拥有。 + +## 白名单限制 + +可以发送到`vesting_sender_address`的消息有以下限制: + +- 仅允许`send_mode == 3`; + +在大多数情况下,地址被添加到白名单中,以允许用户使用被锁定的硬币进行验证或将被锁定的硬币质押到池中。 + +为了避免Toncoin被盗,发送到白名单的消息有以下限制: + +- 仅允许`send_mode == 3`; + +- 仅允许可弹回的消息; + +- 不允许附加`state_init`; + +如果目的地是系统选举人地址: + +- 仅允许`op::elector_new_stake`、`op::elector_recover_stake`、`op::vote_for_complaint`、`op::vote_for_proposal`操作; + +如果目的地是系统配置地址: + +- 仅允许`op::vote_for_proposal`操作; + +对于其他目的地: + +- 允许空消息和空文本消息; +- 允许以"d"、"w"、"D"、"W"开头的文本消息; +- 允许`op::single_nominator_pool_withdraw`、`op::single_nominator_pool_change_validator`、`op::ton_stakers_deposit`、`op::jetton_burn`、`op::ton_stakers_vote`、`op::vote_for_proposal`、`op::vote_for_complaint`操作; + +对不在白名单上的地址没有限制。 + +发送未锁定的 Toncoin 时不适用任何限制,即使我们发送到白名单`vesting_sender_address`。 + +## 项目结构 + +- `contracts` - 项目所有智能合约及其依赖的源代码。 +- `wrappers` - 合约的封装类(实现ton-core的`Contract`),包括任何[解]序列化原语和编译函数。 +- `tests` - 合约的测试。 +- `scripts` - 项目使用的脚本,主要是部署脚本。 + +## 如何使用 + +### 构建 + +`npx blueprint build` 或 `yarn blueprint build` + +### 测试 + +`npx blueprint test` 或 `yarn blueprint test` + +### 部署或运行其他脚本 + +`npx blueprint run` 或 `yarn blueprint run` + +### 添加新合约 + +`npx blueprint create ContractName` 或 `yarn blueprint create ContractName` + +## 参阅 + +- [单一提名者](/participate/network-maintenance/single-nominator) +- [归属合约](https://github.com/ton-blockchain/vesting-contract) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-and-tvm-assembly.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-and-tvm-assembly.md new file mode 100644 index 0000000000..d55340e56d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-and-tvm-assembly.md @@ -0,0 +1,129 @@ +# Fift 和 TVM 汇编 + +Fift 是一种基于堆栈的汇编编程语言,它具有 TON 特有的功能,因此可以处理cell。TVM 汇编同样是一种基于堆栈的、特定于 TON 的编程语言,它也可以处理cell。那么它们之间的区别是什么呢? + +## 区别 + +Fift 在**编译时**执行 - FunC 代码被处理后,您的编译器构建智能合约代码 BOC 。Fift 可以有不同的形式: + +``` +// tuple primitives +x{6F0} @Defop(4u) TUPLE +x{6F00} @Defop NIL +x{6F01} @Defop SINGLE +x{6F02} dup @Defop PAIR @Defop CONS +``` + +> Asm.fif 中的 TVM 操作码定义 + +``` +"Asm.fif" include +<{ SETCP0 DUP IFNOTRET // return if recv_internal + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 32 LDU NIP 256 PLDU CONDSEL // cnt or pubk + }> + INC 32 THROWIF // fail unless recv_external + 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs + NOW s1 s3 XCHG LEQ 35 THROWIF // signature in_msg subwallet_id cs msg_seqno + c4 PUSH CTOS 32 LDU 32 LDU 256 LDU ENDS // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key + s3 s2 XCPU EQUAL 33 THROWIFNOT // signature in_msg subwallet_id cs public_key stored_seqno stored_subwallet + s4 s4 XCPU EQUAL 34 THROWIFNOT // signature in_msg stored_subwallet cs public_key stored_seqno + s0 s4 XCHG HASHSU // signature stored_seqno stored_subwallet cs public_key msg_hash + s0 s5 s5 XC2PU // public_key stored_seqno stored_subwallet cs msg_hash signature public_key + CHKSIGNU 35 THROWIFNOT // public_key stored_seqno stored_subwallet cs + ACCEPT + WHILE:<{ + DUP SREFS // public_key stored_seqno stored_subwallet cs _51 + }>DO<{ // public_key stored_seqno stored_subwallet cs + 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno stored_subwallet cs _56 mode + SENDRAWMSG + }> // public_key stored_seqno stored_subwallet cs + ENDS SWAP INC // public_key stored_subwallet seqno' + NEWC 32 STU 32 STU 256 STU ENDC c4 POP +}>c +``` + +> wallet_v3_r2.fif + +最后一段代码看起来像是 TVM 汇编,而且大部分确实是!这是怎么发生的? + +想象一下你正在对一位实习程序员说:“现在在函数末尾添加执行这个、这个和那个的命令”。你的命令最终会出现在实习生的程序中。它们被处理了两次 - 就像这里,大写字母的操作码(SETCP0、DUP 等)同时被 Fift 和 TVM 处理。 + +你可以向实习生解释高级抽象,最终他会理解并能够使用它们。Fift 也是可扩展的 - 你可以定义自己的命令。事实上,Asm[Tests].fif 就是关于定义 TVM 操作码的。 + +另一方面,TVM 操作码在**运行时**执行 - 它们是智能合约的代码。可以把它们看作是你实习生的程序 - TVM 汇编可以做的事情较少(例如,它没有内置的数据签名原语 - 因为 TVM 在区块链中做的一切都是公开的),但它可以真正与其环境互动。 + +## 在智能合约中的使用 + +### [Fift] - 将大型 BOC 放入合约 + +如果你使用的是 `toncli`,这是可能的。如果你使用其他编译器构建合约,可能还有其他方法来包含大型 BOC。 +编辑 `project.yaml`,使得构建智能合约代码时包含 `fift/blob.fif`: + +``` +contract: + fift: + - fift/blob.fif + func: + - func/code.fc +``` + +将 BOC 放入 `fift/blob.boc`,然后将以下代码添加到 `fift/blob.fif`: + +``` +B B>boc ref, b> s PUSHSLICE"; +``` + +原因很明显:Fift 在编译时进行计算,那时还没有 `x` 可供转换。要将非常量整数转换为字符串切片,你需要 TVM 汇编。例如,这是 TON 智能挑战 3位 参赛者之一的代码: + +``` +tuple digitize_number(int value) + asm "NIL WHILE:<{ OVER }>DO<{ SWAP TEN DIVMOD s1 s2 XCHG TPUSH }> NIP"; + +builder store_number(builder msg, tuple t) + asm "WHILE:<{ DUP TLEN }>DO<{ TPOP 48 ADDCONST ROT 8 STU SWAP }> DROP"; + +builder store_signed(builder msg, int v) inline_ref { + if (v < 0) { + return msg.store_uint(45, 8).store_number(digitize_number(- v)); + } elseif (v == 0) { + return msg.store_uint(48, 8); + } else { + return msg.store_number(digitize_number(v)); + } +} +``` + +### [TVM 汇编] - 低成本的模乘 + +``` +int mul_mod(int a, int b, int m) inline_ref { ;; 1232 gas units + (_, int r) = muldivmod(a % m, b % m, m); + return r; +} +int mul_mod_better(int a, int b, int m) inline_ref { ;; 1110 gas units + (_, int r) = muldivmod(a, b, m); + return r; +} +int mul_mod_best(int a, int b, int m) asm "x{A988} s,"; ;; 65 gas units +``` + +`x{A988}` 是根据 [5.2 Division](/learn/tvm-instructions/instructions#52-division) 格式化的操作码:带有预乘法的除法,唯一返回的结果是第三个参数的余数。但操作码需要进入智能合约代码 - 这就是 `s,` 的作用:它将栈顶的切片存储到稍低的构建器中。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-deep-dive.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-deep-dive.md new file mode 100644 index 0000000000..cc794df657 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/fift-deep-dive.md @@ -0,0 +1,136 @@ +# Fift 深入解析 + +Fift 是一种高级的基于栈的语言,用于本地操作cell和其他 TVM 原语,主要用于将 TVM 汇编代码转换为合约代码的cell包。 + +:::caution +本节描述了与 TON 特有功能在**非常**低层级的交互。 +需要对栈语言基础有深入理解。 +::: + +## 简单算术 + +你可以使用 Fift 解释器作为计算器,以[逆波兰表示法(reverse Polish notation)](https://en.wikipedia.org/wiki/Reverse_Polish_notation)编写表达式。 + +``` +6 17 17 * * 289 + . +2023 ok +``` + +## 标准输出 + +``` +27 emit ."[30;1mgrey text" 27 emit ."[37m" +grey text ok +``` + +`emit` 从栈顶取出数字,并将指定代码的 Unicode 字符打印到 stdout。 +`."..."` 打印常量字符串。 + +## 定义函数(Fift words) + +定义word的主要方式是将其效果括在大括号中,然后写 `:` 和word名称。 + +``` +{ minmax drop } : min +{ minmax nip } : max +``` + +> Fift.fif + +不过,还有几个*定义word*的方法,不仅仅是 `:`。它们的不同之处在于,用其中一些定义的word是**active**(在大括号内工作),而有些是**prefix**(不需要在它们之后有空格字符): + +``` +{ bl word 1 2 ' (create) } "::" 1 (create) +{ bl word 0 2 ' (create) } :: : +{ bl word 2 2 ' (create) } :: :_ +{ bl word 3 2 ' (create) } :: ::_ +{ bl word 0 (create) } : create +``` + +> Fift.fif + +## 条件执行 + +代码块(由大括号分隔)可以有条件或无条件地执行。 + +``` +{ { ."true " } { ."false " } cond } : ?. 4 5 = ?. 4 5 < ?. +false true ok +{ ."hello " } execute ."world" +hello world ok +``` + +## 循环 + +``` +// ( l c -- l') deletes first c elements from list l +{ ' safe-cdr swap times } : list-delete-first +``` + +> GetOpt.fif + +循环word `times` 接受两个参数 - 我们称它们为 `cont` 和 `n` - 并执行 `cont` `n` 次。 +这里 `list-delete-first` 继承 `safe-cdr` (从Lisp样式列表中删除head命令),将其放在 `c` 下面,然后 `c` 次从堆栈上的列表中删除head。 + +还有 `while` 和 `until` 循环word。 + +## 注释 + +``` +{ 0 word drop 0 'nop } :: // +{ char " word 1 { swap { abort } if drop } } ::_ abort" +{ { bl word dup "" $= abort"comment extends after end of file" "*/" $= } until 0 'nop } :: /* +``` + +> Fift.fif + +注释在 `Fift.fif` 中定义。单行注释以 `//` 开始,一直到行尾;多行注释以 `/*` 开始,以 `*/` 结束。 + +让我们理解它们为什么有效。 +Fift 程序本质上是一系列word的序列,每个单词都以某种方式转换栈或定义新单词。`Fift.fif` 的第一行代码(上面所示)是新word `//` 的声明。注释必须在定义新word时也能工作,所以它们必须在嵌套环境中工作。这就是为什么它们被定义为**active**单词,通过 `::` 实现。正在创建的单词的动作列在大括号中: + +1. `0`:零被推到栈上 +2. `word`:此命令读取字符,直到达到栈顶的字符,并将读取的数据作为字符串推送。零是特殊情况:这里 `word` 跳过前导空格,然后读取直到当前输入行的末尾。 +3. `drop`:栈顶元素(注释数据)被丢弃。 +4. `0`:再次将零推到栈上 - 结果的数量,因为word是用 `::` 定义的。 +5. `'nop` 推送执行令牌在调用时什么也不做。这几乎等同于 `{ nop }`。 + +## 使用 Fift 定义 TVM 汇编代码 + +``` +x{00} @Defop NOP +{ 1 ' @addop does create } : @Defop +{ tuck sbitrefs @ensurebitrefs swap s, } : @addop +{ @havebitrefs ' @| ifnot } : @ensurebitrefs +{ 2 pick brembitrefs 1- 2x<= } : @havebitrefs +{ rot >= -rot <= and } : 2x<= +... +``` + +> Asm.fif (行顺序颠倒) + +`@Defop` 负责检查是否有足够的空间放置操作码(`@havebitrefs`),如果没有,它将继续写入另一个构建器(`@|`;也称为隐式跳转)。这就是为什么你通常不想写 `x{A988} s,` 作为操作码:可能没有足够的空间放置此操作码,因此编译会失败;你应该写 `x{A988} @addop`。 + +您可以使用 Fift 将大型cell包纳入到合约中: + +``` +B B>boc ref, b> pub - 从私钥生成公钥 + - ed25519_sign[_uint] - 给定数据和私钥生成签名 + - ed25519_chksign - 检查 Ed25519 签名 +- 与 TVM 的交互 + - runvmcode 及类似的 - 使用从堆栈中取得的代码切片调用 TVM +- 将 BOC 写入文件: + `boc>B ".../contract.boc" B>file` + +## 继续学习 + +- [Fift 简介](https://docs.ton.org/fiftbase.pdf) by Nikolai Durov diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/overview.mdx new file mode 100644 index 0000000000..c19915c27c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/fift/overview.mdx @@ -0,0 +1,49 @@ +import Button from '@site/src/components/button' + +# 概览 + +Fift 是一种基于栈的通用编程语言,专为创建、调试和管理 TON 区块链智能合约而进行了优化。 +Fift 专门设计用于与 TON 虚拟机(TON VM 或 TVM)和 TON 区块链进行交互。 + +```fift +{ ."hello " } execute ."world" +hello world ok +``` + +:::info +通常,编程 TON 智能合约时不需要使用 Fift。然而,有时,您可能需要使用 Fift 语言来解决作为任务的一部分的不常见技术挑战。 +::: + + + + + +



+ +## 文档 + +- [Fift 简介](https://ton.org/fiftbase.pdf) +- [TON 虚拟机](/learn/tvm-instructions/tvm-overview) + +## 示例 + +- [Fift 智能合约示例](/develop/smart-contracts/examples#fift-smart-contracts) + +## 教程 + +- [Fift 介绍](https://blog.ton.org/introduction-to-fift) +- [\[YouTube\]His majesty Fift](https://www.youtube.com/watch?v=HVsveTmVowc&list=PLtUBO1QNEKwttRsAs9eacL2oCMOhWaOZs) \[[RU 版本](https://www.youtube.com/playlist?list=PLyDBPwv9EPsCYG-hR4N5FRTKUkfM8POgh)],由 **@MarcoDaTr0p0je** 和 **@Wikimar** 主讲。 + +## 资料 + +- [标准智能合约的 Fift 脚本](https://github.com/ton-blockchain/ton/tree/master/crypto/smartcont) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/changelog.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/changelog.md new file mode 100644 index 0000000000..939dc2aeec --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/changelog.md @@ -0,0 +1,61 @@ +# FunC 的历史 + +# 初始版本 + +初始版本由 Telegram 完成,并在 2020 年 5 月后停止了积极开发。我们将 2020 年 5 月的版本称为“初始版本”。 + +# 版本 0.1.0 + +发布于 [2022 年 5 月更新](https://github.com/ton-blockchain/ton/releases/tag/v2022.05)。 + +在这个版本中增加了: + +- [常量](/develop/func/literals_identifiers#constants) +- [扩展字符串字面量](/develop/func/literals_identifiers#string-literals) +- [Semver 编译指令](/develop/func/compiler_directives#pragma-version) +- [包含](/develop/func/compiler_directives#pragma-version) + +修复: + +- 修复了在 Asm.fif 中偶尔出现的错误。 + +# 版本 0.2.0 + +发布于 [2022 年 8 月更新](https://github.com/ton-blockchain/ton/releases/tag/v2022.08)。 + +在这个版本中增加了: + +- 不平衡的 if/else 分支(当某些分支返回而有些则不返回) + +修复: + +- [FunC 错误处理 while(false) 循环 #377](https://github.com/ton-blockchain/ton/issues/377) +- [FunC 错误生成 ifelse 分支的代码 #374](https://github.com/ton-blockchain/ton/issues/374) +- [FunC 在内联函数中错误返回条件 #370](https://github.com/ton-blockchain/ton/issues/370) +- [Asm.fif: 大型函数体的分割错误地干扰了内联 #375](https://github.com/ton-blockchain/ton/issues/375) + +# 版本 0.3.0 + +发布于 [2022 年 10 月更新](https://github.com/ton-blockchain/ton/releases/tag/v2022.10)。 + +在这个版本中增加了: + +- [多行 asms](/develop/func/functions#multiline-asms) +- 允许对常量和 asms 的重复定义 +- 允许对常量进行位操作 + +# 版本 0.4.0 + +发布于 [2023 年 1 月更新](https://github.com/ton-blockchain/ton/releases/tag/v2023.01)。 + +在这个版本中增加了: + +- [try/catch 语句](/develop/func/statements#try-catch-statements) +- [throw_arg 函数](/develop/func/builtins#throwing-exceptions) +- 允许就地修改和批量赋值全局变量:`a~inc()` 和 `(a, b) = (3, 5)`,其中 `a` 是全局变量 + +修复: + +- 禁止在同一表达式中使用局部变量后对其进行模糊修改:`var x = (ds, ds~load_uint(32), ds~load_unit(64));` 是禁止的,而 `var x = (ds~load_uint(32), ds~load_unit(64), ds);` 是允许的 +- 允许空的内联函数 +- 修复罕见的 `while` 优化错误 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/cookbook.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/cookbook.md new file mode 100644 index 0000000000..d3a225f922 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/cookbook.md @@ -0,0 +1,1495 @@ +# FunC 开发手册 + +创建 FunC 开发手册的核心原因是将所有 FunC 开发者的经验汇集在一个地方,以便未来的开发者们使用! + +与 FunC 文档相比,本文更侧重于 FunC 开发者在智能合约开发过程中每天都要解决的任务。 + +## 基础知识 +### 如何编写 if 语句 + +假设我们想检查某个事件是否相关。为此,我们使用标志变量。记住在 FunC 中 `true` 是 `-1` 而 `false` 是 `0`。 + +```func +int flag = 0; ;; false + +if (flag) { + ;; 做一些事情 +} +else { + ;; 拒绝交易 +} +``` + +> 💡 注意 +> +> 我们不需要使用 `==` 操作符,因为 `0` 的值是 `false`,所以任何其他值都将是 `true`。 + +> 💡 有用的链接 +> +> [文档中的“If statement”](/develop/func/statements#if-statements) + +### 如何编写 repeat 循环 + +以指数运算为例 + +```func +int number = 2; +int multiplier = number; +int degree = 5; + +repeat(degree - 1) { + + number *= multiplier; +} +``` + +> 💡 有用的链接 +> +> [文档中的“Repeat loop”](/develop/func/statements#repeat-loop) + +### 如何编写 while 循环 + +当我们不知道要执行特定操作多少次时,while 循环很有用。例如,取一个 `cell`,我们知道它可以存储最多四个对其他 cell 的引用。 + +```func +cell inner_cell = begin_cell() ;; 创建一个新的空构建器 + .store_uint(123, 16) ;; 存储值为 123 且长度为 16 位的 uint + .end_cell(); ;; 将构建器转换为 cell + +cell message = begin_cell() + .store_ref(inner_cell) ;; 将 cell 作为引用存储 + .store_ref(inner_cell) + .end_cell(); + +slice msg = message.begin_parse(); ;; 将 cell 转换为 slice +while (msg.slice_refs_empty?() != -1) { ;; 我们应该记住 -1 是 true + cell inner_cell = msg~load_ref(); ;; 从 slice msg 中加载 cell + ;; 做一些事情 +} +``` + +> 💡 有用的链接 +> +> [文档中的“While loop”](/develop/func/statements#while-loop) +> +> [文档中的“Cell”](/learn/overviews/cells) +> +> [文档中的“slice_refs_empty?()”](/develop/func/stdlib#slice_refs_empty) +> +> [文档中的“store_ref()”](/develop/func/stdlib#store_ref) +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“end_cell()”](/develop/func/stdlib#end_cell) +> +> [文档中的“begin_parse()”](/develop/func/stdlib#begin_parse) + +### 如何编写 do until 循环 + +当我们需要循环至少运行一次时,我们使用 `do until`。 + +```func +int flag = 0; + +do { + ;; 即使 flag 是 false (0) 也做一些事情 +} until (flag == -1); ;; -1 是 true +``` + +> 💡 有用的链接 +> +> [文档中的“Until loop”](/develop/func/statements#until-loop) + +### 如何确定 slice 是否为空 + +在处理 `slice` 之前,需要检查它是否有数据以便正确处理。我们可以使用 `slice_empty?()` 来做到这一点,但我们必须考虑到,如果有至少一个 `bit` 的数据或一个 `ref`,它将返回 `-1`(`true`)。 + +```func +;; 创建空 slice +slice empty_slice = ""; +;; `slice_empty?()` 返回 `true`,因为 slice 没有任何 `bits` 和 `refs` +empty_slice.slice_empty?(); + +;; 创建仅包含 bits 的 slice +slice slice_with_bits_only = "Hello, world!"; +;; `slice_empty?()` 返回 `false`,因为 slice 有 `bits` +slice_with_bits_only.slice_empty?(); + +;; 创建仅包含 refs 的 slice +slice slice_with_refs_only = begin_cell() + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_empty?()` 返回 `false`,因为 slice 有 `refs` +slice_with_refs_only.slice_empty?(); + +;; 创建包含 bits 和 refs 的 slice +slice slice_with_bits_and_refs = begin_cell() + .store_slice("Hello, world!") + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_empty?()` 返回 `false`,因为 slice 有 `bits` 和 `refs` +slice_with_bits_and_refs.slice_empty?(); +``` +> 💡 有用的链接 +> +> [文档中的“slice_empty?()”](/develop/func/stdlib#slice_empty) +> +> [文档中的“store_slice()”](/develop/func/stdlib#store_slice) +> +> [文档中的“store_ref()”](/develop/func/stdlib#store_ref) +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“end_cell()”](/develop/func/stdlib#end_cell) +> +> [文档中的“begin_parse()”](/develop/func/stdlib#begin_parse) + + +### 如何确定 slice 是否为空(不含任何 bits,但可能包含 refs) + +如果我们只需要检查 `bits`,不关心 `slice` 中是否有任何 `refs`,那么我们应该使用 `slice_data_empty?()`。 + +```func +;; 创建空 slice +slice empty_slice = ""; +;; `slice_data_empty?()` 返回 `true`,因为 slice 没有任何 `bits` +empty_slice.slice_data_empty?(); + +;; 创建仅包含 bits 的 slice +slice slice_with_bits_only = "Hello, world!"; +;; `slice_data_empty?()` 返回 `false`,因为 slice 有 `bits` +slice_with_bits_only.slice_data_empty?(); + +;; 创建仅包含 refs 的 slice +slice slice_with_refs_only = begin_cell() + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_data_empty?()` 返回 `true`,因为 slice 没有 `bits` +slice_with_refs_only.slice_data_empty?(); + +;; 创建包含 bits 和 refs 的 slice +slice slice_with_bits_and_refs = begin_cell() + .store_slice("Hello, world!") + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_data_empty?()` 返回 `false`,因为 slice 有 `bits` +slice_with_bits_and_refs.slice_data_empty?(); +``` + +> 💡 有用的链接 +> +> [文档中的“slice_data_empty?()”](/develop/func/stdlib#slice_data_empty) +> +> [文档中的“store_slice()”](/develop/func/stdlib#store_slice) +> +> [文档中的“store_ref()”](/develop/func/stdlib#store_ref) +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“end_cell()”](/develop/func/stdlib#end_cell) +> +> [文档中的“begin_parse()”](/develop/func/stdlib#begin_parse) + +### 如何确定 slice 是否为空(没有任何 refs,但可能有 bits) + +如果我们只对 `refs` 感兴趣,我们应该使用 `slice_refs_empty?()` 来检查它们的存在。 + +```func +;; 创建空 slice +slice empty_slice = ""; +;; `slice_refs_empty?()` 返回 `true`,因为 slice 没有任何 `refs` +empty_slice.slice_refs_empty?(); + +;; 创建只包含 bits 的 slice +slice slice_with_bits_only = "Hello, world!"; +;; `slice_refs_empty?()` 返回 `true`,因为 slice 没有任何 `refs` +slice_with_bits_only.slice_refs_empty?(); + +;; 创建只包含 refs 的 slice +slice slice_with_refs_only = begin_cell() + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_refs_empty?()` 返回 `false`,因为 slice 有 `refs` +slice_with_refs_only.slice_refs_empty?(); + +;; 创建包含 bits 和 refs 的 slice +slice slice_with_bits_and_refs = begin_cell() + .store_slice("Hello, world!") + .store_ref(null()) + .end_cell() + .begin_parse(); +;; `slice_refs_empty?()` 返回 `false`,因为 slice 有 `refs` +slice_with_bits_and_refs.slice_refs_empty?(); +``` + +> 💡 有用的链接 +> +> [文档中的“slice_refs_empty?()”](/develop/func/stdlib#slice_refs_empty) +> +> [文档中的“store_slice()”](/develop/func/stdlib#store_slice) +> +> [文档中的“store_ref()”](/develop/func/stdlib#store_ref) +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“end_cell()”](/develop/func/stdlib#end_cell) +> +> [文档中的“begin_parse()”](/develop/func/stdlib#begin_parse) + +### 如何确定 cell 是否为空 + +要检查 `cell` 中是否有任何数据,我们应首先将其转换为 `slice`。如果我们只对 `bits` 感兴趣,应使用 `slice_data_empty?()`;如果只对 `refs` 感兴趣,则使用 `slice_refs_empty?()`。如果我们想检查是否有任何数据,无论是 `bit` 还是 `ref`,我们需要使用 `slice_empty?()`。 + +```func +cell cell_with_bits_and_refs = begin_cell() + .store_uint(1337, 16) + .store_ref(null()) + .end_cell(); + +;; 将 `cell` 类型更改为 slice,使用 `begin_parse()` +slice cs = cell_with_bits_and_refs.begin_parse(); + +;; 确定 slice 是否为空 +if (cs.slice_empty?()) { + ;; cell 为空 +} +else { + ;; cell 不为空 +} +``` + +> 💡 有用的链接 +> +> [文档中的“slice_empty?()”](/develop/func/stdlib#slice_empty) +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“store_uint()”](/develop/func/stdlib#store_uint) +> +> [文档中的“end_cell()”](/develop/func/stdlib#end_cell) +> +> [文档中的“begin_parse()”](/develop/func/stdlib#begin_parse) + +### 如何确定 dict 是否为空 + +有一个 `dict_empty?()` 方法可以检查 dict 中是否有数据。这个方法相当于 `cell_null?()`,因为通常一个空的 cell 就是一个空字典。 + +```func +cell d = new_dict(); +d~udict_set(256, 0, "hello"); +d~udict_set(256, 1, "world"); + +if (d.dict_empty?()) { ;; 确定 dict 是否为空 + ;; dict 为空 +} +else { + ;; dict 不为空 +} +``` + +> 💡 有用的链接 +> +> [文档中的“dict_empty?()”](/develop/func/stdlib#dict_empty) +> +> [文档中的“new_dict()”](/develop/func/stdlib/#new_dict) 创建空字典 +> +> [文档中的“dict_set()”](/develop/ + +func/stdlib/#dict_set) 为 dict d 添加一些元素,所以它不为空 + +### 如何确定 tuple 是否为空 + +在处理 `tuples` 时,始终知道内部是否有值以供提取是很重要的。如果我们尝试从空的 `tuple` 中提取值,将会得到一个错误:“not a tuple of valid size”,exit code 7。 + +```func +;; 声明 tlen 函数,因为它在 stdlib 中没有提供 +(int) tlen (tuple t) asm "TLEN"; + +() main () { + tuple t = empty_tuple(); + t~tpush(13); + t~tpush(37); + + if (t.tlen() == 0) { + ;; tuple 为空 + } + else { + ;; tuple 不为空 + } +} +``` + +> 💡 注意 +> +> 我们声明了 tlen 汇编函数。你可以在[这里](/develop/func/functions#assembler-function-body-definition)阅读更多,并查看[所有汇编指令列表](/learn/tvm-instructions/instructions)。 + +> 💡 有用的链接 +> +> [文档中的“empty_tuple?()”](/develop/func/stdlib#empty_tuple) +> +> [文档中的“tpush()”](/develop/func/stdlib/#tpush) +> +> [文档中的“Exit codes”](/learn/tvm-instructions/tvm-exit-codes) + +### 如何确定 Lisp 类型的列表是否为空 + +```func +tuple numbers = null(); +numbers = cons(100, numbers); + +if (numbers.null?()) { + ;; Lisp 类型的列表为空 +} else { + ;; Lisp 类型的列表不为空 +} +``` + +我们使用 [cons](/develop/func/stdlib/#cons) 函数将数字 100 添加到我们的 Lisp 类型列表中,所以它不为空。 + +### 如何确定合约的状态是否为空 + +假设我们有一个 `counter`,用于存储交易次数。在智能合约状态的第一次交易中,这个变量不可用,因为状态为空,因此需要处理这种情况。如果状态为空,我们创建一个变量 `counter` 并保存它。 + +```func +;; `get_data()` 将从合约状态返回数据 cell +cell contract_data = get_data(); +slice cs = contract_data.begin_parse(); + +if (cs.slice_empty?()) { + ;; 合约数据为空,所以我们创建 counter 并保存 + int counter = 1; + ;; 创建 cell,添加 counter 并保存在合约状态中 + set_data(begin_cell().store_uint(counter, 32).end_cell()); +} +else { + ;; 合约数据不为空,所以我们获取我们的 counter,增加它并保存 + ;; 我们应该指定 counter 的正确的位长度 + int counter = cs~load_uint(32) + 1; + set_data(begin_cell().store_uint(counter, 32).end_cell()); +} +``` + +> 💡 注意 +> +> 我们可以通过确定 [cell 是否为空](/develop/func/cookbook#how-to-determine-if-cell-is-empty) 来确定合约的状态是否为空。 + +> 💡 有用的链接 +> +> [文档中的“get_data()”](/develop/func/stdlib#get_data) +> +> [文档中的“begin_parse()”](/develop/func/stdlib/#begin_parse) +> +> [文档中的“slice_empty?()”](/develop/func/stdlib/#slice_empty) +> +> [文档中的“set_data?()”](/develop/func/stdlib#set_data) + +### 如何构建内部消息 cell + +如果我们希望合约发送一个内部消息,我们应该首先正确地创建它为一个 cell,指定技术标志位、接收地址和其余数据。 + +```func +;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice +slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a; +int amount = 1000000000; +;; 我们使用 `op` 来识别操作 +int op = 0; +cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header 部(参见发送消息页面) + .store_uint(op, 32) +.end_cell(); + +send_raw_message(msg, 3); ;; 模式 3 - 分别支付费用并忽略错误 +``` + +> 💡 注意 +> +> 在这个例子中,我们使用字面量 `a` 获取地址。你可以在[文档](/develop/func/literals_identifiers#string-literals)中找到更多关于字符串字面量的信息。 + +> 💡 注意 +> +> 你可以在[文档](/develop/smart-contracts/messages)中找到更多信息。也可以通过这个链接跳转到[布局](/develop/smart-contracts/messages#message-layout)。 + +> 💡 有用的链接 +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“store_uint()”](/develop/func/stdlib#store_uint) +> +> [文档中的“store_slice()”](/develop/func/stdlib#store_slice) +> +> [文档中的“store_coins()”](/develop/func/stdlib#store_coins) +> +> [文档中的“end_cell()”](/develop/func/stdlib/#end_cell) +> +> [文档中的“send_raw_message()”](/develop/func/stdlib/#send_raw_message) + +### 如何在内部消息 cell 中包含 body 作为 ref + +在跟着标志位和其他技术数据的消息体中,我们可以发送 `int`、`slice` 和 `cell`。在后者的情况下,在 `store_ref()` 之前必须将位设置为 `1`,以表明 `cell` 将继续传输。 + +如果我们确信有足够的空间,我们也可以在与 header 相同的 `cell` 中发送消息体。在这种情况下,我们需要将位设置为 `0`。 + +```func +;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice +slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a; +int amount = 1000000000; +int op = 0; +cell message_body = begin_cell() ;; 创建包含消息的 cell + .store_uint(op, 32) + .store_slice("❤") +.end_cell(); + +cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; 默认消息 header 部(参见发送消息页面) + .store_uint(1, 1) ;; 设置位为 1,表明 cell 将继续传输 + .store_ref(message_body) +.end_cell(); + +send_raw_message(msg, 3); ;; mode 3 - 分别支付费用并忽略错误 +``` + +> 💡 注意 +> +> 在这个例子中,我们使用字面量 `a` 获取地址。你可以在[文档](/develop/func/literals_identifiers#string-literals)中找到更多关于字符串字面量的信息。 + +> 💡 注意 +> +> 在这个例子中,我们使用node 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。 + +> 💡 注意 +> +> 我们正在[构建消息](/develop/func/cookbook#how-to-build-an-internal-message-cell),但单独添加消息体。 + +> 💡 有用的链接 +> +> [文档中的“begin_cell()”](/develop/func/stdlib#begin_cell) +> +> [文档中的“store_uint()”](/develop/func/stdlib#store_uint) +> +> [文档中的“store_slice()”](/develop/func/stdlib#store_slice) +> +> [文档中的“store_coins()”](/develop/func/stdlib#store_coins) +> +> [文档中的“end_cell()”](/develop/func/stdlib/#end_cell) +> +> [文档中的“send_raw_message()”](/develop/func/stdlib/#send_raw_message) + +### 如何在内部消息 cell 中包含 body 作为 slice + +发送消息时,消息体可以作为 `cell` 或 `slice` 发送。在这个例子中,我们将消息体放在 `slice` 内部发送。 + +```func +;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice +slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a; +int amount = 1000000000; +int op = 0; +slice message_body = "❤"; + +cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header 部(参见发送消息页面) + .store_uint(op, 32) + .store_slice(message_body) +.end_cell(); + +send_raw_message(msg, 3); ;; + + mode 3 - 分别支付费用并忽略错误 +``` + +> 💡 注意 +> +> 在这个例子中,我们使用字面量 `a` 获取地址。你可以在[文档](/develop/func/literals_identifiers#string-literals)中找到更多关于字符串字面量的信息。 + +> 💡 注意 +> +> 在这个例子中,我们使用 mode 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。 + +> 💡 注意 +> +> 我们正在[构建消息](/develop/func/cookbook#how-to-build-an-internal-message-cell),但将消息作为 slice 添加。 + +### 如何迭代 tuples(双向) + +如果我们想在 FunC 中处理数组或栈,那么 tuple 是必需的。首先我们需要能够迭代值来处理它们。 + +```func +(int) tlen (tuple t) asm "TLEN"; +forall X -> (tuple) to_tuple (X x) asm "NOP"; + +() main () { + tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + int len = t.tlen(); + + int i = 0; + while (i < len) { + int x = t.at(i); + ;; 使用 x 做一些事情 + i = i + 1; + } + + i = len - 1; + while (i >= 0) { + int x = t.at(i); + ;; 使用 x 做一些事情 + i = i - 1; + } +} +``` + +> 💡 注意 +> +> 我们声明了 `tlen` 汇编函数。你可以在[这里](/develop/func/functions#assembler-function-body-definition)阅读更多,并查看[所有汇编指令列表](/learn/tvm-instructions/instructions)。 +> +> 我们还声明了 `to_tuple` 函数。它只是改变任何输入的数据类型为 tuple,因此在使用时要小心。 + +### 如何使用 `asm` 关键字编写自己的函数 + +当使用任何功能时,实际上我们使用的是为我们预先准备好的 `stdlib.fc` 中的方法。但事实上,我们有更多的机会可以使用,我们需要学会自己编写它们。 + +例如,我们有 `tpush` 方法,它可以向 `tuple` 中添加元素,但没有 `tpop`。在这种情况下,我们应该这样做: +```func +;; ~ 表示它是修改方法 +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; +``` + +如果我们想知道 `tuple` 的长度以进行迭代,我们应该使用 `TLEN` 汇编指令编写一个新函数: +```func +int tuple_length (tuple t) asm "TLEN"; +``` + +stdlib.fc 中我们已知的一些函数示例: +```func +slice begin_parse(cell c) asm "CTOS"; +builder begin_cell() asm "NEWC"; +cell end_cell(builder b) asm "ENDC"; +``` + +> 💡 有用的链接: +> +> [文档中的“modifying method”](/develop/func/statements#modifying-methods) +> +> [文档中的“stdlib”](/develop/func/stdlib) +> +> [文档中的“TVM instructions”](/learn/tvm-instructions/instructions) + +### 迭代嵌套的 n 个 tuples + +有时我们想迭代嵌套的 tuples。以下示例将从头开始迭代并打印格式为 `[[2,6],[1,[3,[3,5]]], 3]` 的 tuple 中的所有项目 + +```func +int tuple_length (tuple t) asm "TLEN"; +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; +forall X -> int is_tuple (X x) asm "ISTUPLE"; +forall X -> tuple cast_to_tuple (X x) + + asm "NOP"; +forall X -> int cast_to_int (X x) asm "NOP"; +forall X -> (tuple) to_tuple (X x) asm "NOP"; + +;; 定义全局变量 +global int max_value; + +() iterate_tuple (tuple t) impure { + repeat (t.tuple_length()) { + var value = t~tpop(); + if (is_tuple(value)) { + tuple tuple_value = cast_to_tuple(value); + iterate_tuple(tuple_value); + } + else { + if(value > max_value) { + max_value = value; + } + } + } +} + +() main () { + tuple t = to_tuple([[2,6], [1, [3, [3, 5]]], 3]); + int len = t.tuple_length(); + max_value = 0; ;; 重置 max_value; + iterate_tuple(t); ;; 迭代 tuple 并找到最大值 + ~dump(max_value); ;; 6 +} +``` + +> 💡 有用的链接 +> +> [文档中的“Global variables”](/develop/func/global_variables) +> +> [文档中的“~dump”](/develop/func/builtins#dump-variable) +> +> [文档中的“TVM instructions”](/learn/tvm-instructions/instructions) + +### 基本的 tuple 操作 + +```func +(int) tlen (tuple t) asm "TLEN"; +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; + +() main () { + ;; 创建一个空的 tuple + tuple names = empty_tuple(); + + ;; 添加新项目 + names~tpush("Naito Narihira"); + names~tpush("Shiraki Shinichi"); + names~tpush("Akamatsu Hachemon"); + names~tpush("Takaki Yuichi"); + + ;; 弹出最后一项 + slice last_name = names~tpop(); + + ;; 获取第一项 + slice first_name = names.first(); + + ;; 按索引获取项 + slice best_name = names.at(2); + + ;; 获取列表长度 + int number_names = names.tlen(); +} +``` + +### 解析类型 X + +下面的示例检查 tuple 中是否包含某个值,但 tuple 包含值 X(cell, slice, int, tuple, int)。我们需要检查值并相应地转换。 + +```func +forall X -> int is_null (X x) asm "ISNULL"; +forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_tuple (X x) asm "ISTUPLE"; +forall X -> int cast_to_int (X x) asm "NOP"; +forall X -> cell cast_to_cell (X x) asm "NOP"; +forall X -> slice cast_to_slice (X x) asm "NOP"; +forall X -> tuple cast_to_tuple (X x) asm "NOP"; +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; + +forall X -> () resolve_type (X value) impure { + ;; value 是类型 X,由于我们不知道确切的值是什么 - 我们需要检查值然后转换它 + + if (is_null(value)) { + ;; 对 null 做一些事情 + } + elseif (is_int(value)) { + int valueAsInt = cast_to_int(value); + ;; 对 int 做一些事情 + } + elseif (is_slice(value)) { + slice valueAsSlice = cast_to_slice(value); + ;; 对 slice 做一些事情 + } + elseif (is_cell(value)) { + cell valueAsCell = cast_to_cell(value); + ;; 对 cell 做一些事情 + } + elseif (is_tuple(value)) { + tuple valueAsTuple = cast_to_tuple(value); + ;; 对 tuple 做一些事情 + } +} + +() main () { + ;; 创建一个空的 tuple + tuple stack = empty_tuple(); + ;; 假设我们有一个 tuple 并且不知道它们的确切类型 + stack~tpush("Some text"); + stack~tpush(4); + ;; 我们使用 var 因为我们不知道值的类型 + var value = stack~tpop(); + resolve_type(value); +} +``` + +> 💡 有用的链接 +> +> [文档中的“TVM 指令”](/learn/tvm-instructions/instructions) + + +### 如何获取当前时间 + +```func +int current_time = now(); + +if (current_time > 1672080143) { + ;; 做一些事情 +} +``` + +### 如何生成随机数 + +:::caution 草稿 +请注意,这种生成随机数的方法不安全。 + +待办事项:添加关于生成随机数的文章链接 +::: + +```func +randomize_lt(); ;; 只需做一次 + +int a = rand(10); +int b = rand(1000000); +int c = random(); +``` + +### 模运算 + +例如,假设我们想对所有 256 个数字运行以下计算:`(xp + zp)*(xp-zp)`。由于这些操作大多用于密码学,在下面的示例中,我们使用模运算符进行蒙哥马利曲线(montogomery curves)。注意 xp+zp 是一个有效的变量名(没有空格)。 + +```func +(int) modulo_operations (int xp, int zp) { + ;; 2^255 - 19 是蒙哥马利曲线的素数,意味着所有操作都应该对其素数进行 + int prime = 57896044618658097711785492504343953926634992332820282019728792003956564819949; + + ;; muldivmod 自身处理以下两行 + ;; int xp+zp = (xp + zp) % prime; + ;; int xp-zp = (xp - zp + prime) % prime; + (_, int xp+zp*xp-zp) = muldivmod(xp + zp, xp - zp, prime); + return xp+zp*xp-zp; +} +``` + +> 💡 有用的链接 +> +> [文档中的“muldivmod”](/learn/tvm-instructions/instructions#52-division) + + +### 如何抛出错误 + +```func +int number = 198; + +throw_if(35, number > 50); ;; 只有当数字大于 50 时才会触发错误 + +throw_unless(39, number == 198); ;; 只有当数字不等于 198 时才会触发错误 + +throw(36); ;; 无论如何都会触发错误 +``` + +[标准 TVM 异常代码](/learn/tvm-instructions/tvm-exit-codes.md) + +### 反转 tuples + +因为 tuple 以堆栈的方式存储数据,有时我们必须反转 tuple 以从另一端读取数据。 + +```func +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; +int tuple_length (tuple t) asm "TLEN"; +forall X -> (tuple) to_tuple (X x) asm "NOP"; + +(tuple) reverse_tuple (tuple t1) { + tuple t2 = empty_tuple(); + repeat (t1.tuple_length()) { + var value = t1~tpop(); + t2~tpush(value); + } + return t2; +} + +() main () { + tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + tuple reversed_t = reverse_tuple(t); + ~dump(reversed_t); ;; [10 9 8 7 6 5 4 3 2 1] +} +``` + +> 💡 有用的链接 +> +> [文档中的“tpush()”](/develop/func/stdlib/#tpush) + + +### 如何从列表中移除特定索引的项 + +```func +int tlen (tuple t) asm "TLEN"; + +(tuple, ()) remove_item (tuple old_tuple, int place) { + tuple new_tuple = empty_tuple(); + + int i = 0; + while (i < old_tuple.tlen()) { + int el = old_tuple.at(i); + if (i != place) { + new_tuple~tpush(el); + } + i += 1; + } + return (new_tuple, ()); +} + +() main () { + tuple numbers = empty_tuple(); + + numbers~tpush(19); + numbers~tpush(999); + numbers~tpush(54); + + ~dump(numbers); ;; [19 999 54] + + numbers~remove_item(1); + + ~dump(numbers); ;; [19 54] +} + +### 判断切片是否相等 + +我们有两种不同的方法可以判断切片是否相等。一种是基于切片哈希,另一种则是使用 SDEQ 汇编指令。 + +```func +int are_slices_equal_1? (slice a, slice b) { + return a.slice_hash() == b.slice_hash(); +} + +int are_slices_equal_2? (slice a, slice b) asm "SDEQ"; + +() main () { + slice a = "Some text"; + slice b = "Some text"; + ~dump(are_slices_equal_1?(a, b)); ;; -1 = true + + a = "Text"; + ;; 我们使用字面量 `a` 来从包含地址的字符串中获取切片的有效地址 + b = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a; + ~dump(are_slices_equal_2?(a, b)); ;; 0 = false +} +``` + +#### 💡 有用的链接 + + * ["slice_hash()" in docs](/develop/func/stdlib/#slice_hash) + * ["SDEQ" in docs](/learn/tvm-instructions/instructions#62-other-comparison) + +### 判断cell是否相等 + +我们可以根据它们的哈希轻松确定cell的相等性。 + +```func +int are_cells_equal? (cell a, cell b) { + return a.cell_hash() == b.cell_hash(); +} + +() main () { + cell a = begin_cell() + .store_uint(123, 16) + .end_cell(); + + cell b = begin_cell() + .store_uint(123, 16) + .end_cell(); + + ~dump(are_cells_equal?(a, b)); ;; -1 = true +} +``` + +> 💡 有用的链接 +> +> ["cell_hash()" in docs](/develop/func/stdlib/#cell_hash) + +### 判断元组是否相等 + +一个更高级的示例是迭代并比较每个元组的值。由于它们是 X,我们需要检查并转换为相应的类型,并且如果它是元组,则递归地迭代它。 + +```func +int tuple_length (tuple t) asm "TLEN"; +forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP"; +forall X -> int cast_to_int (X x) asm "NOP"; +forall X -> cell cast_to_cell (X x) asm "NOP"; +forall X -> slice cast_to_slice (X x) asm "NOP"; +forall X -> tuple cast_to_tuple (X x) asm "NOP"; +forall X -> int is_null (X x) asm "ISNULL"; +forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_tuple (X x) asm "ISTUPLE"; +int are_slices_equal? (slice a, slice b) asm "SDEQ"; + +int are_cells_equal? (cell a, cell b) { + return a.cell_hash() == b.cell_hash(); +} + +(int) are_tuples_equal? (tuple t1, tuple t2) { + int equal? = -1; ;; 初始值为 true + + if (t1.tuple_length() != t2.tuple_length()) { + ;; 如果元组长度不同,它们就不能相等 + return 0; + } + + int i = t1.tuple_length(); + + while (i > 0 & equal?) { + var v1 = t1~tpop(); + var v2 = t2~tpop(); + + if (is_null(t1) & is_null(t2)) { + ;; nulls are always equal + } + elseif (is_int(v1) & is_int(v2)) { + if (cast_to_int(v1) != cast_to_int(v2)) { + + + equal? = 0; + } + } + elseif (is_slice(v1) & is_slice(v2)) { + if (~ are_slices_equal?(cast_to_slice(v1), cast_to_slice(v2))) { + equal? = 0; + } + } + elseif (is_cell(v1) & is_cell(v2)) { + if (~ are_cells_equal?(cast_to_cell(v1), cast_to_cell(v2))) { + equal? = 0; + } + } + elseif (is_tuple(v1) & is_tuple(v2)) { + ;; 递归地判断嵌套元组 + if (~ are_tuples_equal?(cast_to_tuple(v1), cast_to_tuple(v2))) { + equal? = 0; + } + } + else { + equal? = 0; + } + + i -= 1; + } + + return equal?; +} + +() main () { + tuple t1 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]); + tuple t2 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]); + + ~dump(are_tuples_equal?(t1, t2)); ;; -1 +} +``` + +> 💡 有用的链接 +> +> ["cell_hash()" in docs](/develop/func/stdlib/#cell_hash) +> +> ["TVM instructions" in docs](/learn/tvm-instructions/instructions) + +### 生成内部地址 + +当我们的合约需要部署新合约但不知道其地址时,我们需要生成一个内部地址。假设我们已经有了 `state_init` - 新合约的代码和数据。 + +为相应的 MsgAddressInt TLB 创建内部地址。 + +```func +(slice) generate_internal_address (int workchain_id, cell state_init) { + ;; addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + + return begin_cell() + .store_uint(2, 2) ;; addr_std$10 + .store_uint(0, 1) ;; anycast nothing + .store_int(workchain_id, 8) ;; workchain_id: -1 + .store_uint(cell_hash(state_init), 256) + .end_cell().begin_parse(); +} + +() main () { + slice deploy_address = generate_internal_address(workchain(), state_init); + ;; then we can deploy new contract +} +``` + +> 💡 注意 +> +> 在这个示例中,我们使用 `workchain()` 来获取工作链 ID。你可以在[文档](/learn/overviews/addresses#workchain-id)中找到更多关于工作链 ID 的信息。 + +> 💡 有用的链接 +> +> ["cell_hash()" in docs](/develop/func/stdlib/#cell_hash) + +### 生成外部地址 + +我们使用 [block.tlb](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L101C1-L101C12) 中的 TL-B 方案来理解我们如何以这种格式创建一个地址。 + +```func +(int) ubitsize (int a) asm "UBITSIZE"; + +slice generate_external_address (int address) { + ;; addr_extern$01 len:(## 9) external_address:(bits len) = MsgAddressExt; + + int address_length = ubitsize(address); + + return begin_cell() + .store_uint(1, 2) ;; addr_extern$01 + .store_uint(address_length, 9) + .store_uint(address, address_length) + .end_cell().begin_parse(); +} +``` + +由于我们需要确定地址占用的位数,因此还需要[声明一个使用 `UBITSIZE` 操作码的 asm 函数](#how-to-write-own-functions-using-asm-keyword),该函数将返回存储数字所需的最小位数。 + +> 💡 有用的链接 +> +> ["TVM Instructions" in docs](/learn/tvm-instructions/instructions#53-shifts-logical-operations) + +### 如何在本地存储中存储和加载字典 + +加载字典的逻辑 + +```func +slice local_storage = get_data().begin_parse(); +cell dictionary_cell = new_dict(); +if (~ slice_empty?(local_storage)) { + dictionary_cell = local_storage~load_dict(); +} +``` + +而存储字典的逻辑如下所示: + +```func +set_data(begin_cell().store_dict(dictionary_cell).end_cell()); +``` + +> 💡 有用的链接 +> +> ["get_data()" in docs](/develop/func/stdlib/#get_data) +> +> ["new_dict()" in docs](/develop/func/stdlib/#new_dict) +> +> ["slice_empty?()" in docs](/develop/func/stdlib/#slice_empty) +> +> ["load_dict()" in docs](/develop/func/stdlib/#load_dict) +> +> ["~" in docs](/develop/func/statements#unary-operators) + + +### 如何发送简单消息 + +我们通常发送附带评论的方式实际上是一种简单消息。要指定消息正文为 `comment`,我们应在消息文本前设置 `32 bits` 为 0。 + +```func +cell msg = begin_cell() + .store_uint(0x18, 6) ;; 标志位 + .store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址 + .store_coins(100) ;; 发送的nanoTons数量 + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息header(参见发送消息页面) + .store_uint(0, 32) ;; 零操作码 - 表示带评论的简单转账消息 + .store_slice("Hello from FunC!") ;; 评论 +.end_cell(); +send_raw_message(msg, 3); ;; mode 3 - 分开支付费用,忽略错误 +``` + +> 💡 有用的链接 +> +> [文档中的“消息布局”](/develop/smart-contracts/messages) + +### 如何发送带有入账的消息 + +以下合约示例对我们有用,如果我们需要在用户和主合约之间执行一些操作,那我们就需要一个代理合约。 + +```func +() recv_internal (slice in_msg_body) { + {- + 这是一个代理合约的简单示例。 + 它将期望 in_msg_body 包含消息 mode、body 和要发送到的目的地址。 + -} + + int mode = in_msg_body~load_uint(8); ;; 第一个字节将包含消息 mode + slice addr = in_msg_body~load_msg_addr(); ;; 然后我们解析目的地址 + slice body = in_msg_body; ;; in_msg_body 中剩余的所有内容将是我们新消息的 body + + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(100) ;; 仅作示例 + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header (参见发送消息页面) + .store_slice(body) + .end_cell(); + send_raw_message(msg, mode); +} +``` + +> 💡 有用的链接 +> +> [文档中的“消息布局”](/develop/smart-contracts/messages) +> +> [文档中的“load_msg_addr()”](/develop/func/stdlib/#load_msg_addr) + +### 如何发送携带全部余额的消息 + +如果我们需要发送智能合约的全部余额,那么在这种情况下,我们需要使用发送 `mode 128`。这样的例子可能是一个接受付款并转发给主合约的代理合约。 + +```func +cell msg = begin_cell() + .store_uint(0x18, 6) ;; 标志位 + .store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址 + .store_coins(0) ;; 我们现在不关心这个值 + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header (参见发送消息页面) + .store_uint(0, 32) ;; 零操作码 - 表示带评论的简单转账消息 + .store_slice("Hello from FunC!") ;; 评论 +.end_cell(); +send_raw_message(msg, 128); ;; 模式=128 用于携带当前智能合约剩余全部余额的消息 +``` + +> 💡 有用的链接 +> +> [文档中的“消息布局”](/develop/smart-contracts/messages) +> +> [文档中的“消息模式”](/develop/func/stdlib/#send_raw_message) + +### 如何发送带有长文本评论的消息 + +我们知道,单个 `cell` (< 1023 bits) 中只能容纳 127 个字符。如果我们需要更多 - 我们需要组织蛇形cell。 + +```func +{- + 如果我们想发送带有非常长的评论的消息,我们应该将评论分成几个片段。 + 每个片段应包含 <1023 位数据(127个字符)。 + 每个片段应该有一个引用指向下一个,形成蛇形结构。 +-} + +cell body = begin_cell() + .store_uint(0, 32) ;; 零操作码 - 带评论的简单消息 + .store_slice("long long long message...") + .store_ref(begin_cell() + .store_slice(" you can store string of almost any length here.") + .store_ref(begin_cell() + .store_slice(" just don't forget about the 127 chars limit for each slice") + .end_cell()) + .end_cell()) +.end_cell(); + +cell msg = begin_cell() + .store_uint(0x18, 6) ;; 标志位 + ;; 我们使用字面量 `a` 从包含地址的字符串中获取片段内的有效地址 + .store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址 + .store_coins(100) ;; 发送的nanoTons数量 + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; 默认消息 header (参见发送消息页面) + .store_uint(1, 1) ;; 我们希望将 body 存储为引用 + .store_ref(body) +.end_cell(); +send_raw_message(msg, 3); ;; mode 3 - 分开支付费用,忽略错误 +``` + +> 💡 有用的链接 +> +> [文档中的“内部消息”](/develop/smart-contracts/guidelines/internal-messages) + +### 如何仅从片段中获取数据位(不包括引用) + +如果我们对 `slice` 内的 `refs` 不感兴趣,那么我们可以获取单独的数据并使用它。 + +```func +slice s = begin_cell() + .store_slice("Some data bits...") + .store_ref(begin_cell().end_cell()) ;; 一些引用 + .store_ref(begin_cell().end_cell()) ;; 一些引用 +.end_cell().begin_parse(); + +slice s_only_data = s.preload_bits(s.slice_bits()); +``` + +> 💡 有用的链接 +> +> [文档中的“Slice原语”](/develop/func/stdlib/#slice-primitives) +> +> [文档中的“preload_bits()”](/develop/func/stdlib/#preload_bits) +> +> [文档中的“slice_bits()”](/develop/func/stdlib/#slice_bits) + +### 如何定义自己的修改方法 + +修改方法允许在同一个变量内修改数据。这可以与其他编程语言中的引用进行比较。 + +```func +(slice, (int)) load_digit (slice s) { + int x = s~load_uint(8); ;; 从片段中加载 8 位(一个字符) + x -= 48; ;; 字符 '0' 的代码为 48,所以我们减去它以得到数字 + return (s, (x)); ;; 返回我们修改的片段和加载的数字 +} + +() main () { + slice s = "258"; + int c1 = s~load_digit(); + int c2 = s~load_digit(); + int c3 = s~load_digit(); + ;; 这里 s 等于 "",c1 = 2,c2 = 5,c3 = 8 +} +``` + +> 💡 有用的链接 +> +> [文档中的“修改方法”](/develop/func/statements#modifying-methods) + +### 如何计算 n 的幂 + +```func +;; 未优化版本 +int pow (int a, int n) { + int i = 0; + int value = a; + while (i < n - 1) { + a *= value; + i += 1; + } + return a + +; +} + +;; 优化版本 +(int) binpow (int n, int e) { + if (e == 0) { + return 1; + } + if (e == 1) { + return n; + } + int p = binpow(n, e / 2); + p *= p; + if ((e % 2) == 1) { + p *= n; + } + return p; +} + +() main () { + int num = binpow(2, 3); + ~dump(num); ;; 8 +} +``` + +### 如何将字符串转换为 int + +```func +slice string_number = "26052021"; +int number = 0; + +while (~ string_number.slice_empty?()) { + int char = string_number~load_uint(8); + number = (number * 10) + (char - 48); ;; 我们使用 ASCII 表 +} + +~dump(number); +``` + +### 如何将 int 转换为 string + +```func +int n = 261119911; +builder string = begin_cell(); +tuple chars = null(); +do { + int r = n~divmod(10); + chars = cons(r + 48, chars); +} until (n == 0); +do { + int char = chars~list_next(); + string~store_uint(char, 8); +} until (null?(chars)); + +slice result = string.end_cell().begin_parse(); +~dump(result); +``` + +### 如何遍历字典 + +字典在处理大量数据时非常有用。我们可以使用内置方法 `dict_get_min?` 和 `dict_get_max?` 分别获取最小和最大键值。此外,我们可以使用 `dict_get_next?` 遍历字典。 + +```func +cell d = new_dict(); +d~udict_set(256, 1, "value 1"); +d~udict_set(256, 5, "value 2"); +d~udict_set(256, 12, "value 3"); + +;; 从小到大遍历键 +(int key, slice val, int flag) = d.udict_get_min?(256); +while (flag) { + ;; 使用 key->val 对,做某些事情 + + (key, val, flag) = d.udict_get_next?(256, key); +} +``` + +> 💡 有用的链接 +> +> [文档中的“字典原语”](/develop/func/stdlib/#dictionaries-primitives) +> +> [文档中的“dict_get_max?()”](/develop/func/stdlib/#dict_get_max) +> +> [文档中的“dict_get_min?()”](/develop/func/stdlib/#dict_get_min) +> +> [文档中的“dict_get_next?()”](/develop/func/stdlib/#dict_get_next) +> +> [文档中的“dict_set()”](/develop/func/stdlib/#dict_set) + +### 如何从字典中删除值 + +```func +cell names = new_dict(); +names~udict_set(256, 27, "Alice"); +names~udict_set(256, 25, "Bob"); + +names~udict_delete?(256, 27); + +(slice val, int key) = names.udict_get?(256, 27); +~dump(val); ;; null() -> 表示在字典中未找到该键 +``` + +### 如何递归遍历cell树 + +我们知道,一个 `cell` 可以存储多达 `1023 bits` 的数据和最多 `4 refs`。要绕过这个限制,我们可以使用cell树,但为此我们需要能够迭代它,以便正确处理数据。 + +```func +forall X -> int is_null (X x) asm "ISNULL"; +forall X -> (tuple, ()) push_back (tuple tail, X head) asm "CONS"; +forall X -> (tuple, (X)) pop_back (tuple t) asm "UNCONS"; + +() main () { + ;; 仅作为示例的一些cell + cell c = begin_cell() + .store_uint(1, 16) + .store_ref(begin_cell() + .store_uint(2, 16) + .end_cell()) + .store_ref(begin_cell() + .store_uint(3, 16) + .store_ref(begin_cell() + .store_uint(4, 16) + .end_cell()) + .store_ref(begin_cell() + .store_uint(5, 16) + .end_cell()) + .end_cell()) + .end_cell(); + + ;; 创建一个没有数据的元组,充当栈的角色 + tuple stack = null(); + ;; 将主cell放入栈中以便在循环中处理 + stack~push_back(c); + ;; 在栈不为空时执行 + while (~ stack.is_null()) { + ;; 从栈中获取cell,并将其转换为 slice 以便处理 + slice s = stack~pop_back().begin_parse(); + + ;; 对 s 数据做一些操作 + + ;; 如果当前 slice 有任何 refs,将它们添加到栈中 + repeat (s.slice_refs()) { + stack~push_back(s~load_ref()); + } + } +} +``` + +> 💡 有用的链接 +> +> + + [文档中的“Lisp类型列表”](/develop/func/stdlib/#lisp-style-lists) +> +> [文档中的“null()”](/develop/func/stdlib/#null) +> +> [文档中的“slice_refs()”](/develop/func/stdlib/#slice_refs) + +### 如何遍历 Lisp 类型列表 + +数据类型 tuple 最多可以容纳 255 个值。如果这还不够,我们应该使用 Lisp 类型的列表。我们可以将一个 tuple 放入另一个 tuple 中,从而绕过限制。 + +```func +forall X -> int is_null (X x) asm "ISNULL"; +forall X -> (tuple, ()) push_back (tuple tail, X head) asm "CONS"; +forall X -> (tuple, (X)) pop_back (tuple t) asm "UNCONS"; + +() main () { + ;; 一些示例列表 + tuple l = null(); + l~push_back(1); + l~push_back(2); + l~push_back(3); + + ;; 遍历元素 + ;; 注意这种迭代是倒序的 + while (~ l.is_null()) { + var x = l~pop_back(); + + ;; 对 x 做一些操作 + } +} +``` + +> 💡 有用的链接 +> +> [文档中的“Lisp风格列表”](/develop/func/stdlib/#lisp-style-lists) +> +> [文档中的“null()”](/develop/func/stdlib/#null) + +### 如何发送部署消息(仅使用 stateInit 或使用 stateInit 和 body) + +```func +() deploy_with_stateinit(cell message_header, cell state_init) impure { + var msg = begin_cell() + .store_slice(begin_parse(msg_header)) + .store_uint(2 + 1, 2) ;; init:(Maybe (Either StateInit ^StateInit)) + .store_uint(0, 1) ;; body:(Either X ^X) + .store_ref(state_init) + .end_cell(); + + ;; mode 64 - 在新消息中携带剩余值 + send_raw_message(msg, 64); +} + +() deploy_with_stateinit_body(cell message_header, cell state_init, cell body) impure { + var msg = begin_cell() + .store_slice(begin_parse(msg_header)) + .store_uint(2 + 1, 2) ;; init:(Maybe (Either StateInit ^StateInit)) + .store_uint(1, 1) ;; body:(Either X ^X) + .store_ref(state_init) + .store_ref(body) + .end_cell(); + + ;; mode 64 - 在新消息中携带剩余值 + send_raw_message(msg, 64); +} +``` + +### 如何构建 stateInit cell + +```func +() build_stateinit(cell init_code, cell init_data) { + var state_init = begin_cell() + .store_uint(0, 1) ;; split_depth:(Maybe (## 5)) + .store_uint(0, 1) ;; special:(Maybe TickTock) + .store_uint(1, 1) ;; (Maybe ^Cell) + .store_uint(1, 1) ;; (Maybe ^Cell) + .store_uint(0, 1) ;; (HashmapE 256 SimpleLib) + .store_ref(init_code) + .store_ref(init_data) + .end_cell(); +} +``` + +### 如何计算合约地址(使用 stateInit) + +```func +() calc_address(cell state_init) { + var future_address = begin_cell() + .store_uint(2, 2) ;; addr_std$10 + .store_uint(0, 1) ;; anycast:(Maybe Anycast) + .store_uint(0, 8) ;; workchain_id:int8 + .store_uint(cell_hash(state_init), 256) ;; address:bits256 + .end_cell(); +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/builtins.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/builtins.md new file mode 100644 index 0000000000..6c44809487 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/builtins.md @@ -0,0 +1,31 @@ +# 内置功能 + +本节描述了一些比之前文章中描述的语言结构更不基础的构造。它们可以在 [stdlib.fc](/develop/func/stdlib) 中定义,但这样会减少 FunC 优化器的操作空间。 + +## 抛出异常 + +可以通过条件原语 `throw_if`、`throw_unless` 和无条件的 `throw` 来抛出异常。第一个参数是错误代码;第二个是条件(`throw` 只有一个参数)。这些原语有参数化版本 `throw_arg_if`、`throw_arg_unless` 和 `throw_arg`。第一个参数是任何类型的异常参数;第二个是错误代码;第三个是条件(`throw_arg` 只有两个参数)。 + +## 布尔值 + +- `true` 是 `-1` 的别名 +- `false` 是 `0` 的别名 + +## 变量转储 + +变量可以通过 `~dump` 函数转储到调试日志。 + +## 字符串转储 + +字符串可以通过 `~strdump` 函数转储到调试日志。 + +## 整数操作 + +- `muldiv` 是一个先乘后除的操作。中间结果存储在 513 位整数中,因此如果实际结果适合于 257 位整数,它不会溢出。 +- `divmod` 是一个取两个数字作为参数并给出它们除法的商和余数的操作。 + +## 其他原语 + +- `null?` 检查参数是否为 `null`。对于 TVM 类型的 `null` 值,FunC 的 `Null` 表示某些原子类型值的缺失;参见 [null 值](/develop/func/types#null-values)。 +- `touch` 和 `~touch` 将变量移至栈顶 +- `at` 获取指定位置上的元组组件的值 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/comments.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/comments.md new file mode 100644 index 0000000000..0087359834 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/comments.md @@ -0,0 +1,32 @@ +# 注释 + +FunC 有单行注释,以 `;;`(双分号)开始。例如: + +```func +int x = 1; ;; assign 1 to x +``` + +它还有多行注释,以 `{-` 开始并以 `-}` 结束。请注意,与许多其他语言不同的是,FunC 的多行注释可以嵌套。例如: + +```func +{- This is a multi-line comment + {- this is a comment in the comment -} +-} +``` + +此外,多行注释中可以有单行注释,且单行注释 `;;` 比多行注释 `{- -}`“更强”。换句话说,在以下示例中: + +```func +{- + Start of the comment + +;; this comment ending is itself commented -> -} + +const a = 10; +;; this comment begining is itself commented -> {- + + End of the comment +-} +``` + +`const a = 10;` 在多行注释内,因此被注释掉了。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/compiler_directives.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/compiler_directives.md new file mode 100644 index 0000000000..2c7dd677e4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/compiler_directives.md @@ -0,0 +1,90 @@ +# 编译指令 + +这些是以 `#` 开始的关键字,指示编译器执行某些动作、检查或更改参数。 + +这些指令只能在最外层使用(不在任何函数定义内部)。 + +## #include + +`#include` 指令允许包含另一个 FunC 源代码文件,该文件将代替 include 处进行解析。 + +语法为 `#include "filename.fc";`。文件会自动检查是否重复包含,且默认情况下,尝试多次包含同一文件将被忽略,并在详细级别不低于 2 时发出警告。 + +如果在解析包含的文件期间发生错误,此外,将打印包含的堆栈,其中包含链中每个包含的文件的位置。 + +## #pragma + +`#pragma` 指令用于向编译器提供超出语言本身所传达的附加信息。 + +### #pragma version + +版本编译指令用于在编译文件时强制使用特定版本的 FunC 编译器。 + +版本以 semver 格式指定,即 *a.b.c*,其中 *a* 是主版本号,*b* 是次版本号,*c* 是修订号。 + +开发者可用的比较运算符有几种: + +- *a.b.c* 或 *=a.b.c* — 要求编译器版本正好为 *a.b.c* +- *>a.b.c* — 要求编译器版本高于 *a.b.c* + - *>=a.b.c* — 要求编译器版本高于或等于 *a.b.c* +- *\*, *>=*, *\<*, *\<=*)简略格式假定省略部分为零,即: + +- *>a.b* 等同于 *>a.b.0*(因此不匹配 *a.b.0* 版本) +- *\<=a* 等同于 *\<=a.0.0*(因此不匹配 *a.0.1* 版本) +- *^a.b.0* **不** 等同于 *^a.b* + +例如,*^a.1.2* 匹配 *a.1.3* 但不匹配 *a.2.3* 或 *a.1.0*,然而,*^a.1* 匹配它们所有。 + +可以多次使用此指令;编译器版本必须满足所有提供的条件。 + +### #pragma not-version + +此编译指令的语法与版本编译指令相同,但如果条件满足则会失败。 + +例如,它可以用于将已知有问题的特定版本列入黑名单。 + +### #pragma allow-post-modification + +*funC v0.4.1* + +默认情况下,禁止在同一表达式中先使用变量后修改它。换句话说,表达式 `(x, y) = (ds, ds~load_uint(8))` 无法编译,而 `(x, y) = (ds~load_uint(8), ds)` 是有效的。 + +可以通过 `#pragma allow-post-modification` 覆盖此规则,允许在批量赋值和函数调用中在使用后修改变量;如常规,子表达式将从左到右计算:`(x, y) = (ds, ds~load_bits(8))` 将导致 `x` 包含初始 `ds`;`f(ds, ds~load_bits(8))` `f` 的第一个参数将包含初始 `ds`,第二个参数 - `ds` 的 8 位。 + +`#pragma allow-post-modification` 仅适用于编译指令之后的代码。 + +### #pragma compute-asm-ltr + +*funC v0.4.1* + +Asm 声明可以覆盖参数的顺序,例如在以下表达式中 + +```func +idict_set_ref(ds~load_dict(), ds~load_uint(8), ds~load_uint(256), ds~load_ref()) +``` + +解析顺序将是:`load_ref()`、`load_uint(256)`、`load_dict()` 和 `load_uint(8)`,由于以下 asm 声明(注意 `asm(value index dict key_len)`): + +```func +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +``` + +可以通过 `#pragma compute-asm-ltr` 更改为严格的从左到右的计算顺序 + +因此,在 + +```func +#pragma compute-asm-ltr +... +idict_set_ref(ds~load_dict(), ds~load_uint(8), ds~load_uint(256), ds~load_ref()); +``` + +中解析顺序将是 `load_dict()`、`load_uint(8)`、`load_uint(256)`、`load_ref()`,所有 asm 排列将在计算之后发生。 + +`#pragma compute-asm-ltr` 仅适用于编译指令之后的代码。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/functions.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/functions.md new file mode 100644 index 0000000000..b36806c2fe --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/functions.md @@ -0,0 +1,384 @@ +# 函数 + +FunC 程序本质上是一系列函数声明/定义和全局变量声明。本节涵盖了第一个主题。 + +任何函数声明或定义都以一个共同的模式开始,接下来有三种情况之一: + +- 单个 `;`,表示函数已声明但尚未定义。它可能会在同一文件中的后面或在传递给 FunC 编译器的其他文件中定义。例如, + ```func + int add(int x, int y); + ``` + 是一个名为 `add` 类型为 `(int, int) -> int` 的函数的简单声明。 + +- 汇编函数体定义。这是通过低层级 TVM 原语定义函数以便在 FunC 程序中后续使用的方法。例如, + ```func + int add(int x, int y) asm "ADD"; + ``` + 是同一个 `add` 函数的汇编定义,类型为 `(int, int) -> int`,将转换为 TVM 操作码 `ADD`。 + +- 常规块语句函数体定义。这是定义函数的常用方式。例如, + ```func + int add(int x, int y) { + return x + y; + } + ``` + 是 `add` 函数的常规定义。 + +## 函数声明 + +如前所述,任何函数声明或定义都以一个共同的模式开始。以下是该模式: + +```func +[] () +``` + +其中 `[ ... ]` 对应于可选条目。 + +### 函数名 + +函数名可以是任何[标识符](/develop/func/literals_identifiers#identifiers),也可以以 `.` 或 `~` 符号开头。这些符号的含义在[声明](/develop/func/statements#methods-calls)部分解释。 + +例如,`udict_add_builder?`、`dict_set` 和 `~dict_set` 都是有效且不同的函数名。(它们在 [stdlib.fc](/develop/func/stdlib) 中定义。) + +#### 特殊函数名 + +FunC(实际上是 Fift 汇编器)有几个预定义的保留函数名,具有预定义的[id](/develop/func/functions#method_id)。 + +- `main` 和 `recv_internal` 的 id 为 0 +- `recv_external` 的 id 为 -1 +- `run_ticktock` 的 id 为 -2 + +每个程序必须有一个 id 为 0 的函数,即 `main` 或 `recv_internal` 函数。 +`run_ticktock` 在特殊智能合约的 ticktock 交易中被调用。 + +#### 接收内部消息 + +`recv_internal` 在智能合约接收到内部入站消息时被调用。 +当 [TVM 初始化](/learn/tvm-instructions/tvm-overview#initialization-of-tvm) 时,栈上有一些变量,通过在 `recv_internal` 中设置参数,我们使智能合约代码能够了解其中的一些变量。那些代码不知道的变量将永远躺在栈底,从未被触及。 + +因此,以下每个 `recv_internal` 声明都是正确的,但具有较少变量的声明将稍微节省一些gas(每个未使用的参数都会增加额外的 `DROP` 指令) + +```func + +() recv_internal(int balance, int msg_value, cell in_msg_cell, slice in_msg) {} +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) {} +() recv_internal(cell in_msg_cell, slice in_msg) {} +() recv_internal(slice in_msg) {} +``` + +#### 接收外部消息 + +`recv_external` 用于入站外部消息。 + +### 返回类型 + +返回类型可以是[类型](/develop/func/types.md)部分中描述的任何原子或复合类型。例如, + +```func +int foo(); +(int, int) foo'(); +[int, int] foo''(); +(int -> int) foo'''(); +() foo''''(); +``` + +都是有效的函数声明。 + +也允许类型推断。例如, + +```func +_ pyth(int m, int n) { + return (m * m - n * n, 2 * m * n, m * m + n * n); +} +``` + +是 `pyth` 函数的有效定义,类型为 `(int, int) -> (int, int, int)`,用于计算勾股数。 + +### 函数参数 + +函数参数由逗号分隔。以下是参数的有效声明方式: + +- 普通声明:类型 + 名称。例如,`int x` 是函数声明 `() foo(int x);` 中类型为 `int`、名称为 `x` 的参数声明。 +- 未使用的参数声明:只有类型。例如, + ```func + int first(int x, int) { + return x; + } + ``` + 是类型为 `(int, int) -> int` 的有效函数定义。 +- 推断类型的参数声明:只有名称。例如, + ```func + int inc(x) { + return x + 1; + } + ``` + 是类型为 `int -> int` 的有效函数定义。`x` 的 `int` 类型由类型检查器推断。 + +请注意,尽管函数可能看起来像是多个参数的函数,实际上它是一个单一[张量类型](/develop/func/types#tensor-types)参数的函数。要了解差异,请参阅[函数应用](/develop/func/statements#function-application)。然而,参数张量的组成部分通常被称为函数参数。 + +### 函数调用 + +#### 非修改方法 + +:::info +非修改函数支持使用 `.` 的简短函数调用形式 +::: + +```func +example(a); +a.example(); +``` + +如果函数至少有一个参数,它可以作为非修改方法被调用。例如,`store_uint` 的类型为 `(builder, int, int) -> builder`(第二个参数是要存储的值,第三个是位长度)。`begin_cell` 是创建新构建器的函数。以下代码等效: + +```func +builder b = begin_cell(); +b = store_uint(b, 239, 8); +``` + +```func +builder b = begin_cell(); +b = b.store_uint(239, 8); +``` + +因此,函数的第一个参数可以在函数名前传递给它,如果用 `.` 分隔。代码可以进一步简化: + +```func +builder b = begin_cell().store_uint(239, 8); +``` + +也可以进行多次方法调用: + +```func +builder b = begin_cell().store_uint(239, 8) + .store_int(-1, 16) + .store_uint(0xff, 10); +``` + +#### 修改函数 + +:::info +修改函数支持使用 `~` 和 `.` 运算符的简短形式。 +::: + +如果函数的第一个参数的类型为 `A`,并且函数的返回值形状为 `(A, B)`,其中 `B` 是某种任意类型,则该函数可以作为修改方法被调用。 + +修改函数调用可以接受一些参数并返回一些值,但它们会修改第一个参数,即将返回值的第一个组件分配给第一个参数中的变量。 + +```func +a~example(); +a = example(a); +``` + +例如,假设 `cs` 是一个cell切片,`load_uint` 的类型为 `(slice, int) -> (slice, int)`:它接受一个cell切片和要加载的位数,然后返回切片的剩余部分和加载的值。以下代码等效: + +```func +(cs, int x) = load_uint(cs, 8); +``` + +```func +(cs, int x) = cs.load_uint(8); +``` + +```func +int x = cs~load_uint(8); +``` + +在某些情况下,我们希望将不返回任何值并且只修改第一个参数的函数用作修改方法。可以使用cell类型如下操作:假设我们想定义类型为 `int -> int` 的函数 `inc`,它用于递增一个整数,并将其用作修改方法。然后我们应该将 `inc` 定义为类型为 `int -> (int, ())` 的函数: + +```func +(int, ()) inc(int x) { + return (x + 1, ()); +} +``` + +这样定义后,它可以用作修改方法。以下将递增 `x`。 + +```func +x~inc(); +``` + +#### `.` 和 `~` 在函数名中 + +假设我们还想将 `inc` 用作非修改方法。我们可以写类似的东西: + +```func +(int y, _) = inc(x); +``` + +但可以重写 `inc` 作为修改方法的定义。 + +```func +int inc(int x) { + return x + 1; +} +(int, ()) ~inc(int x) { + return (x + 1, ()); +} +``` + +然后像这样调用它: + +```func +x~inc(); +int y = inc(x); +int z = x.inc(); +``` + +第一个调用将修改 x;第二个和第三个不会。 + +总结一下,当以非修改或修改方法(即使用 `.foo` 或 `~foo` 语法)调用名为 `foo` 的函数时,如果存在 `.foo` 或 `~foo` 的定义,FunC 编译器将分别使用 `.foo` 或 `~foo` 的定义,如果没有,则使用 `foo` 的定义。 + +### 修饰符 + +有三种类型的修饰符:`impure`,`inline`/`inline_ref` 和 `method_id`。可以在函数声明中放置一种、几种或不放置任何修饰符,但目前它们必须以正确的顺序呈现。例如,不允许在 `inline` 之后放置 `impure`。 + +#### 非纯修饰符(Impure specifier) + +`impure` 修饰符意味着函数可能有一些不可忽略的副作用。例如,如果函数可以修改合约存储、发送消息或在某些数据无效时抛出异常,并且函数旨在验证这些数据,那么我们应该放置 `impure` 修饰符。 + +如果未指定 `impure`,并且未使用函数调用的结果,则 FunC 编译器可能会并将删除此函数调用。 + +例如,在 [stdlib.fc](/develop/func/stdlib) 函数中 + +```func +int random() impure asm "RANDU256"; +``` + +被定义。使用 `impure` 是因为 `RANDU256` 改变了随机数生成器的内部状态。 + +#### 内联修饰符(Inline specifier) + +如果函数具有 `inline` 修饰符,则其代码实际上在调用该函数的每个地方都被替换。不言而喻,递归调用内联函数是不可能的。 + +例如,您可以在此示例中像这样使用 `inline`:[ICO-Minter.fc](https://github.com/ton-blockchain/token-contract/blob/f2253cb0f0e1ae0974d7dc0cef3a62cb6e19f806/ft/jetton-minter-ICO.fc#L16) + +```func +() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { + set_data(begin_cell() + .store_coins(total_supply) + .store_slice(admin_address) + .store_ref(content) + .store_ref(jetton_wallet_code) + .end_cell() + ); +} +``` + +#### Inline_ref 修饰符(Inline_ref specifier) + +带有 `inline_ref` 修饰符的函数代码放在单独的cell中,每次调用该函数时,TVM 都会执行 `CALLREF` 命令。因此,它与 `inline` 类似,但因为cell可以在没有重复的情况下在多个地方重复使用,所以几乎总是更有效率地使用 `inline_ref` 修饰符而不是 `inline`,除非该函数确实只被调用一次。`inline_ref` 函数的递归调用仍然不可能,因为 TVM cell中没有循环引用。 + +#### method_id + +TVM 程序中的每个函数都有一个内部整数 id,可以通过该 id 调用它。普通函数通常由从 1 开始的连续整数编号,但合约的 get 方法由其名称的 crc16 散列编号。`method_id()` 修饰符允许将函数的 id 设置为指定的值,而 `method_id` 使用默认值 `(crc16() & 0xffff) | 0x10000`。如果函数具有 `method_id` 修饰符,那么它可以通过其名称作为 get 方法在 lite-client 或 ton-explorer 中被调用。 + +例如, + +```func +(int, int) get_n_k() method_id { + (_, int n, int k, _, _, _, _) = unpack_state(); + return (n, k); +} +``` + +是多重签名合约的 get 方法。 + +### 使用 forall 的多态性 + +在任何函数声明或定义之前,都可以有 `forall` 类型变量声明符。它具有以下语法: + +```func +forall -> +``` + +其中类型变量名称可以是任何[标识符](/develop/func/literals_identifiers#identifiers)。通常,它们以大写字母命名。 + +例如, + +```func +forall X, Y -> [Y, X] pair_swap([X, Y] pair) { + [X p1, Y p2] = pair; + return [p2, p1]; +} +``` + +是一个接受长度恰好为 2 的元组的函数,但组件中的值可以是任何(单个堆栈条目)类型,并将它们互换。 + +`pair_swap([2, 3])` 将产生 `[3, 2]`,而 `pair_swap([1, [2, 3, 4]])` 将产生 `[[2, 3, 4], 1]`。 + +在此示例中,`X` 和 `Y` 是[类型变量](/develop/func/types#polymorphism-with-type-variables)。当调用函数时,类型变量被实际类型替换,函数的代码被执行。请注意,尽管函数是多态的,但每种类型替换的实际汇编代码是相同的。这本质上是通过堆栈操作原语的多态性实现的。目前,不支持其他形式的多态性(如带有类型类的特设多态性)。 + +另外,值得注意的是,`X` 和 `Y` 的类型宽度假定为 1;也就是说,`X` 或 `Y` 的值必须占据单个堆栈条目。因此,您实际上不能在类型为 `[(int, int), int]` 的元组上调用函数 `pair_swap`,因为类型 `(int, int)` 的宽度为 2,即它占据 2 个堆栈条目。 + +## 汇编函数体定义 + +如上所述,可以通过汇编代码定义函数。语法是 `asm` 关键字,后跟一个或多个表示为字符串的汇编命令。 +例如,可以定义: + +```func +int inc_then_negate(int x) asm "INC" "NEGATE"; +``` + +– 一个递增整数然后取反的函数。对这个函数的调用将被转换为两个汇编命令 `INC` 和 `NEGATE`。定义该函数的另一种方式是: + +```func +int inc_then_negate'(int x) asm "INC NEGATE"; +``` + +`INC NEGATE` 将被 FunC 视为一个汇编命令,但这是可以的,因为 Fift 汇编器知道这是两个单独的命令。 + +:::info +汇编命令列表可以在这里找到:[TVM 指令](/learn/tvm-instructions/instructions)。 +::: + +### 重新排列堆栈条目 + +在某些情况下,我们希望以与汇编函数所需的顺序不同的顺序传递参数,或/和以不同于命令返回的堆栈条目顺序获取结果。我们可以通过添加相应的堆栈原语来手动重新排列堆栈,但 FunC 可以自动完成此操作。 + +:::info +请注意,在手动重新排列的情况下,参数将按重新排列的顺序计算。要覆盖此行为,请使用 `#pragma compute-asm-ltr`:[compute-asm-ltr](compiler_directives#pragma-compute-asm-ltr) +::: + +例如,假设汇编命令 STUXQ 接受一个整数、构建器和整数;然后返回构建器以及表示操作成功或失败的整数标志。 +我们可以定义函数: + +```func +(builder, int) store_uint_quite(int x, builder b, int len) asm "STUXQ"; +``` + +但是,假设我们想重新排列参数。那么我们可以定义: + +```func +(builder, int) store_uint_quite(builder b, int x, int len) asm(x b len) "STUXQ"; +``` + +因此,您可以在 `asm` 关键字后面指示所需的参数顺序。 + +我们还可以像这样重新排列返回值: + +```func +(int, builder) store_uint_quite(int x, builder b, int len) asm( -> 1 0) "STUXQ"; +``` + +数字对应于返回值的索引(0 是返回值中最深的堆栈条目)。 + +这些技术的组合也是可能的。 + +```func +(int, builder) store_uint_quite(builder b, int x, int len) asm(x b len -> 1 0) "STUXQ"; +``` + +### 多行 asms + +多行汇编命令甚至 Fift 代码片段可以通过以 `"""` 开始和结束的多行字符串定义。 + +```func +slice hello_world() asm """ + "Hello" + " " + "World" + $+ $+ $>s + PUSHSLICE +"""; +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/global_variables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/global_variables.md new file mode 100644 index 0000000000..390671d127 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/global_variables.md @@ -0,0 +1,73 @@ +# 全局变量 + +FunC 程序本质上是函数声明/定义和全局变量声明的列表。本节涵盖了第二个主题。 + +可以使用 `global` 关键字,后跟变量类型和变量名来声明全局变量。例如, + +```func +global ((int, int) -> int) op; + +int check_assoc(int a, int b, int c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main() { + op = _+_; + return check_assoc(2, 3, 9); +} +``` + +是一个简单的程序,它将加法运算符 `_+_` 写入全局函数变量 `op`,并检查三个样本整数的加法关联性;2、3和9。。 + +在内部,全局变量存储在 TVM 的 c7 控制寄存器中。 + +可以省略全局变量的类型。如果省略,将根据变量的使用推断类型。例如,我们可以重写程序如下: + +```func +global op; + +int check_assoc(int a, int b, int c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main() { + op = _+_; + return check_assoc(2, 3, 9); +} +``` + +可以在同一个 `global` 关键字后声明多个变量。以下代码等效: + +```func +global int A; +global cell B; +global C; +``` + +```func +global int A, cell B, C; +``` + +不允许声明与已声明的全局变量同名的局部变量。例如,此代码将无法编译: + +```func +global cell C; + +int main() { + int C = 3; + return C; +} +``` + +请注意,以下代码是正确的: + +```func +global int C; + +int main() { + int C = 3; + return C; +} +``` + +但这里的 `int C = 3;` 等同于 `C = 3;`,即这是对全局变量 `C` 的赋值,而不是局部变量 `C` 的声明(您可以在[声明](/develop/func/statements#variable-declaration)中找到此效果的解释)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/literals_identifiers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/literals_identifiers.md new file mode 100644 index 0000000000..323fc80fd7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/literals_identifiers.md @@ -0,0 +1,104 @@ +# 字面量和标识符 + +## 数字字面量 + +FunC 支持十进制和十六进制整数字面量(允许前导零)。 + +例如,`0`、`123`、`-17`、`00987`、`0xef`、`0xEF`、`0x0`、`-0xfFAb`、`0x0001`、`-0`、`-0x0` 都是有效的数字字面量。 + +## 字符串字面量 + +FunC 中的字符串使用双引号 `"` 包裹,如 `"this is a string"`。不支持特殊符号如 `\n` 和多行字符串。 +可选地,字符串字面量后可以指定类型,如 `"string"u`。 + +支持以下字符串类型: + +- 无类型 —— 用于 asm 函数定义和通过 ASCII 字符串定义 slice 常量 +- `s` —— 通过其内容(十六进制编码并可选地位填充)定义原始 slice 常量 +- `a` —— 从指定地址创建包含 `MsgAddressInt` 结构的 slice 常量 +- `u` —— 创建对应于提供的 ASCII 字符串的十六进制值的 int 常量 +- `h` —— 创建字符串的 SHA256 哈希的前 32 位的 int 常量 +- `H` —— 创建字符串的 SHA256 哈希的所有 256 位的 int 常量 +- `c` —— 创建字符串的 crc32 值的 int 常量 + +例如,以下值会生成对应的常量: + +- `"string"` 变成 `x{737472696e67}` slice 常量 +- `"abcdef"s` 变成 `x{abcdef}` slice 常量 +- `"Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"a` 变成 `x{9FE6666666666666666666666666666666666666666666666666666666666666667_}` slice 常量(`addr_std$10 anycast:none$0 workchain_id:int8=0xFF address:bits256=0x33...33`) +- `"NstK"u` 变成 `0x4e73744b` int 常量 +- `"transfer(slice, int)"h` 变成 `0x7a62e8a8` int 常量 +- `"transfer(slice, int)"H` 变成 `0x7a62e8a8ebac41bd6de16c65e7be363bc2d2cbc6a0873778dead4795c13db979` int 常量 +- `"transfer(slice, int)"c` 变成 `2235694568` int 常量 + +## 标识符 + +FunC 允许使用非常广泛的标识符类别(函数和变量名)。具体来说,任何不包含特殊符号 `;`、`,`、`(`、`)`、` `(空格或制表符)、`~` 和 `.`,不以注释或字符串字面量(以 `"` 开头)开始,不是数字字面量,不是下划线 `_`,也不是关键字的单行字符串都是有效的标识符(唯一的例外是,如果它以 `` ` `` 开头,则必须以相同的 `` ` `` 结尾,并且不能包含除这两个外的任何其他 `` ` ``)。 + +此外,函数定义中的函数名可以以 `.` 或 `~` 开头。 + +例如,以下是有效的标识符: + +- `query`、`query'`、`query''` +- `elem0`、`elem1`、`elem2` +- `CHECK` +- `_internal_value` +- `message_found?` +- `get_pubkeys&signatures` +- `dict::udict_set_builder` +- `_+_`(标准加法运算符,类型为 `(int, int) -> int`,虽然已被定义) +- `fatal!` + +变量名末尾的 `'` 通常用于表示某个旧值的修改版本。例如,几乎所有用于 hashmap 操作的内置修改原语(除了以 `~` 为前缀的原语)都会接收一个 hashmap 并返回新版本的 hashmap 及必要时的其他数据。将这些值命名为相同名称后加 `'` 很方便。 + +后缀 `?` 通常用于布尔变量(TVM 没有内置的 bool 类型;bools 由整数表示:0 为 false,-1 为 true)或返回某些标志位的函数,通常表示操作的成功(如 [stdlib.fc](/develop/func/stdlib) 中的 `udict_get?`)。 + +以下是无效的标识符: + +- `take(first)Entry` +- `"not_a_string` +- `msg.sender` +- `send_message,then_terminate` +- `_` + +一些不太常见的有效标识符示例: + +- `123validname` +- `2+2=2*2` +- `-alsovalidname` +- `0xefefefhahaha` +- `{hehehe}` +- ``pa{--}in"`aaa`"`` + +这些也是无效的标识符: + +- ``pa;;in"`aaa`"``(因为禁止使用 `;`) +- `{-aaa-}` +- `aa(bb` +- `123`(它是一个数字) + +此外,FunC 有一种特殊类型的标识符,用反引号 `` ` `` 引用。 +在引号内,任何符号都是允许的,除了 `\n` 和引号本身。 + +例如,`` `I'm a variable too` `` 是一个有效的标识符,`` `any symbols ; ~ () are allowed here...` `` 也是。 + +## 常量 + +FunC 允许定义编译时的常量,这些常量在编译期间被替换和预计算。 + +常量的定义格式为 `const optional-type identifier = value-or-expression;` + +`optional-type` 可用于强制指定常量的特定类型,也用于更好的可读性。 + +目前,支持 `int` 和 `slice` 类型。 + +`value-or-expression` 可以是字面量或由字面量和常量组成的可预计算表达式。 + +例如,可以这样定义常量: + +- `const int101 = 101;` 定义等同于数字字面量 `101` 的 `int101` 常量 +- `const str1 = "const1", str2 = "aabbcc"s;` 定义两个等于其对应字符串的常量 +- `const int int240 = ((int1 + int2) * 10) << 3;` 定义等于计算结果的 `int240` 常量 +- `const slice str2r = str2;` 定义等于 `str2` 常量值的 `str2r` 常量 + +由于数字常量在编译期间被替换,所有在编译期间进行的优化和预计算都能成功执行(与旧方法通过内联 asm `PUSHINT` 定义常量不同)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/statements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/statements.md new file mode 100644 index 0000000000..a2ee169eef --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/statements.md @@ -0,0 +1,437 @@ +# 语句 + +本节简要讨论构成普通函数体代码的 FunC 语句。 + +## 表达式语句 + +最常见的语句类型是表达式语句。它是一个表达式后跟 `;`。表达式的描述相当复杂,因此这里只提供一个概述。通常所有子表达式都是从左到右计算的,唯一的例外是[汇编堆栈重排](functions#rearranging-stack-entries),它可能手动定义顺序。 + +### 变量声明 + +不可能声明一个局部变量而不定义其初始值。 + +以下是一些变量声明的示例: + +```func +int x = 2; +var x = 2; +(int, int) p = (1, 2); +(int, var) p = (1, 2); +(int, int, int) (x, y, z) = (1, 2, 3); +(int x, int y, int z) = (1, 2, 3); +var (x, y, z) = (1, 2, 3); +(int x = 1, int y = 2, int z = 3); +[int, int, int] [x, y, z] = [1, 2, 3]; +[int x, int y, int z] = [1, 2, 3]; +var [x, y, z] = [1, 2, 3]; +``` + +变量可以在同一作用域内“重新声明”。例如,以下是正确的代码: + +```func +int x = 2; +int y = x + 1; +int x = 3; +``` + +事实上,`int x` 的第二次出现不是声明,而只是编译时确认 `x` 的类型为 `int`。因此第三行实际上等同于简单的赋值 `x = 3;`。 + +在嵌套作用域中,变量可以像在 C 语言中一样真正重新声明。例如,考虑以下代码: + +```func +int x = 0; +int i = 0; +while (i < 10) { + (int, int) x = (i, i + 1); + ;; here x is a variable of type (int, int) + i += 1; +} +;; here x is a (different) variable of type int +``` + +但如在全局变量[章节](/develop/func/global_variables.md)中提到的,不允许重新声明全局变量。 + +请注意,变量声明**是**表达式语句,因此像 `int x = 2` 这样的结构实际上是完整的表达式。例如,以下是正确的代码: + +```func +int y = (int x = 3) + 1; +``` + +它声明了两个变量 `x` 和 `y`,分别等于 `3` 和 `4`。 + +#### 下划线 + +下划线 `_` 用于表示不需要的值。例如,假设函数 `foo` 的类型为 `int -> (int, int, int)`。我们可以获取第一个返回值并忽略第二个和第三个,如下所示: + +```func +(int fst, _, _) = foo(42); +``` + +### 函数应用 + +函数调用看起来像在常规语言中那样。函数调用的参数在函数名之后列出,用逗号分隔。 + +```func +;; suppose foo has type (int, int, int) -> int +int x = foo(1, 2, 3); +``` + +但请注意,`foo` 实际上是**一个**参数类型为 `(int, int, int)` 的函数。为了看到区别,假设 `bar` 是类型为 `int -> (int, int, int)` 的函数。与常规语言不同,你可以这样组合函数: + +```func +int x = foo(bar(42)); +``` + +而不是类似但更长的形式: + +```func +(int a, int b, int c) = bar(42); +int x = foo(a, b, c); +``` + +也可以进行 Haskell 类型的调用,但不总是可行(稍后修复): + +```func +;; suppose foo has type int -> int -> int -> int +;; i.e. it's carried +(int a, int b, int c) = (1, 2, 3); +int x = foo a b c; ;; ok +;; int y = foo 1 2 3; wouldn't compile +int y = foo (1) (2) (3); ;; ok +``` + +### Lambda 表达式 + +暂不支持 Lambda 表达式。 + +### 方法调用 + +#### 非修改方法 + +如果函数至少有一个参数,它可以作为非修改方法调用。例如,`store_uint` 的类型为 `(builder, int, int) -> builder`(第二个参数是要存储的值,第三个是位长度)。`begin_cell` 是创建新构建器的函数。以下代码是等价的: + +```func +builder b = begin_cell(); +b = store_uint(b, 239, 8); +``` + +```func +builder b = begin_cell(); +b = b.store_uint(239, 8); +``` + +因此,函数的第一个参数可以在函数名前传递给它,如果用 `.` 分隔。代码可以进一步简化: + +```func +builder b = begin_cell().store_uint(239, 8); +``` + +也可以进行多次方法调用: + +```func +builder b = begin_cell().store_uint(239, 8) + .store_int(-1, 16) + .store_uint(0xff, 10); +``` + +#### 修改方法 + +如果函数的第一个参数的类型为 `A`,并且函数的返回值的形状为 `(A, B)`,其中 `B` 是某种任意类型,那么该函数可以作为修改方法调用。修改方法调用可以接受一些参数并返回一些值,但它们会修改其第一个参数,即将返回值的第一个组件赋值给第一个参数的变量。例如,假设 `cs` 是一个cell切片,`load_uint` 的类型为 `(slice, int) -> (slice, int)`:它接受一个cell切片和要加载的位数,并返回切片的剩余部分和加载的值。以下代码是等价的: + +```func +(cs, int x) = load_uint(cs, 8); +``` + +```func +(cs, int x) = cs.load_uint(8); +``` + +```func +int x = cs~load_uint(8); +``` + +在某些情况下,我们希望将不返回任何值且只修改第一个参数的函数用作修改方法。可以使用cell类型如下操作:假设我们想定义类型为 `int -> int` 的函数 `inc`,它用于递增整数,并将其用作修改方法。然后我们应该将 `inc` 定义为类型为 `int -> (int, ())` 的函数: + +```func +(int, ()) inc(int x) { + return (x + 1, ()); +} +``` + +像这样定义时,它可以用作修改方法。以下将递增 `x`。 + +```func +x~inc(); +``` + +#### 函数名中的 `.` 和 `~` + +假设我们也想将 `inc` 用作非修改方法。我们可以写类似以下内容: + +```func +(int y, _) = inc(x); +``` + +但可以覆盖 `inc` 作为修改方法的定义。 + +```func +int inc(int x) { + return x + 1; +} +(int, ()) ~inc(int x) { + return (x + 1, ()); +} +``` + +然后这样调用它: + +```func +x~inc(); +int y = inc(x); +int z = x.inc(); +``` + +第一次调用将修改 x;第二次和第三次调用不会。 + +总结一下,当以非修改或修改方法(即使用 `.foo` 或 `~foo` 语法)调用名为 `foo` 的函数时,如果存在 `.foo` 或 `~foo` 的定义,FunC 编译器将分别使用 `.foo` 或 `~foo` 的定义,如果没有,则使用 `foo` 的定义。 + +### 运算符 + +请注意,目前所有的一元和二元运算符都是整数运算符。逻辑运算符表示为位整数运算符(参见[没有布尔类型](/develop/func/types#absence-of-boolean-type))。 + +#### 一元运算符 + +有两个一元运算符: + +- `~` 是按位非(优先级 75) +- `-` 是整数取反(优先级 20) + +它们应该与参数分开: + +- `- x` 是可以的。 +- `-x` 不可以(它是单个标识符) + +#### 二元运算符 + +优先级为 30(左结合性): + +- `*` 是整数乘法 +- `/` 是整数除法(向下取整) +- `~/` 是整数除法(四舍五入) +- `^/` 是整数除法(向上取整) +- `%` 是整数取模运算(向下取整) +- `~%` 是整数取模运算(四舍五入) +- `^%` 是整数取模运算(向上取整) +- `/%` 返回商和余数 +- `&` 是按位与 + +优先级为 20(左结合性): + +- `+` 是整数加法 +- `-` 是整数减法 +- `|` 是按位或 +- `^` 是按位异或 + +优先级为 17(左结合性): + +- `<<` 是按位左移 +- `>>` 是按位右移 +- `~>>` 是按位右移(四舍五入) +- `^>>` 是按位右移(向上取整) + +优先级为 15(左结合性): + +- `==` 是整数等值检查 +- `!=` 是整数不等检查 +- `<` 是整数比较 +- `<=` 是整数比较 +- `>` 是整数比较 +- `>=` 是整数比较 +- `<=>` 是整数比较(返回 -1、0 或 1) + +它们也应该与参数分开: + +- `x + y` 是可以的 +- `x+y` 不可以(它是单个标识符) + +#### 条件运算符 + +它具有通常的语法。 + +```func + ? : +``` + +例如: + +```func +x > 0 ? x * fac(x - 1) : 1; +``` + +优先级为 13。 + +#### 赋值 + +优先级 10。 + +简单赋值 `=` 以及二元运算的对应项:`+=`、`-=`、`*=`、`/=`、`~/=`、`^/=`、`%=`、`~%=`、`^%=`、`<<=`、`>>=`、`~>>=`、`^>>=`、`&=`、`|=`、`^=`。 + +## 循环 + +FunC 支持 `repeat`、`while` 和 `do { ... } until` 循环。不支持 `for` 循环。 + +### Repeat 循环 + +语法是 `repeat` 关键字后跟一个类型为 `int` 的表达式。指定次数重复代码。示例: + +```func +int x = 1; +repeat(10) { + x *= 2; +} +;; x = 1024 +``` + +```func +int x = 1, y = 10; +repeat(y + 6) { + x *= 2; +} +;; x = 65536 +``` + +```func +int x = 1; +repeat(-1) { + x *= 2; +} +;; x = 1 +``` + +如果次数小于 `-2^31` 或大于 `2^31 - 1`,将抛出范围检查异常。 + +### While 循环 + +具有通常的语法。示例: + +```func +int x = 2; +while (x < 100) { + x = x * x; +} +;; x = 256 +``` + +请注意,条件 `x < 100` 的真值是类型为 `int` 的(参见[没有布尔类型](/develop/func/types#absence-of-boolean-type))。 + +### Until 循环 + +具有以下语法: + +```func +int x = 0; +do { + x += 3; +} until (x % 17 == 0); +;; x = 51 +``` + +## If 语句 + +示例: + +```func +;; usual if +if (flag) { + do_something(); +} +``` + +```func +;; equivalent to if (~ flag) +ifnot (flag) { + do_something(); +} +``` + +```func +;; usual if-else +if (flag) { + do_something(); +} +else { + do_alternative(); +} +``` + +```func +;; Some specific features +if (flag1) { + do_something1(); +} else { + do_alternative4(); +} +``` + +花括号是必需的。以下代码将无法编译: + +```func +if (flag1) + do_something(); +``` + +## Try-Catch 语句 + +*自 func v0.4.0 起可用* + +执行 `try` 块中的代码。如果失败,完全回滚在 `try` 块中所做的更改,并执行 `catch` 块;`catch` 接收两个参数:任何类型的异常参数(`x`)和错误代码(`n`,整数)。 + +与许多其他语言不同,在 FunC 的 try-catch 语句中,try 块中所做的更改,特别是局部和全局变量的修改,所有寄存器的更改(即 `c4` 存储寄存器、`c5` 操作/消息寄存器、`c7` 上下文寄存器等)**被丢弃**,如果 try 块中有错误,因此所有合约存储更新和消息发送将被撤销。需要注意的是,一些 TVM 状态参数,如 *codepage* 和gas计数器不会回滚。这意味着,尤其是,try 块中花费的所有gas将被计入,以及改变gas限制的操作(`accept_message` 和 `set_gas_limit`)的效果将被保留。 + +请注意,异常参数可以是任何类型(可能在不同异常情况下不同),因此 funC 无法在编译时预测它。这意味着开发者需要通过将异常参数转换为某种类型来“帮助”编译器(请参见下面的示例 2): + +示例: + +```func +try { + do_something(); +} catch (x, n) { + handle_exception(); +} +``` + +```func +forall X -> int cast_to_int(X x) asm "NOP"; +... +try { + throw_arg(-1, 100); +} catch (x, n) { + x.cast_to_int(); + ;; x = -1, n = 100 + return x + 1; +} +``` + +```func +int x = 0; +try { + x += 1; + throw(100); +} catch (_, _) { +} +;; x = 0 (not 1) +``` + +## 区块语句 + +也允许使用区块语句。它们打开一个新的嵌套作用域: + +```func +int x = 1; +builder b = begin_cell(); +{ + builder x = begin_cell().store_uint(0, 8); + b = x; +} +x += 1; +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/stdlib.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/stdlib.mdx new file mode 100644 index 0000000000..337a060e8d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/stdlib.mdx @@ -0,0 +1,942 @@ +--- +toc_min_heading_level: 2 +toc_max_heading_level: 6 +--- + +# FunC 标准库 + +:::info +本节讨论了 [stdlib.fc](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc) 库,它包含了在 FunC 中使用的标准函数。 +::: + +目前,该库只是最常用的 TVM 命令的汇编器的包装,这些命令不是内置的。库中使用的每个 TVM 命令的描述都可以在 [TVM 文档](/learn/tvm-instructions/tvm-overview)部分找到。本文档也借用了一些描述。 + +文件中的一些函数被注释掉了。这意味着它们已经成为了优化目的的内置函数。然而,类型签名和语义保持不变。 + +请注意,stdlib 中没有呈现一些不太常见的命令。总有一天它们也会被添加。 + +## 元组操作原语 +名称和类型大多是自解释的。有关多态函数的更多信息,请参见 [多态性与 forall](/develop/func/functions#polymorphism-with-forall)。 + +请注意,目前原子类型 `tuple` 的值不能转换为复合元组类型(例如 `[int, cell]`),反之亦然。 + +### Lisp 类型列表 +列表可以表示为嵌套的 2 元组。空列表通常表示为 TVM `null` 值(可以通过调用 `null()` 获得)。例如,元组 `(1, (2, (3, null)))` 表示列表 `[1, 2, 3]`。列表的元素可以是不同类型。 +#### cons +```func +forall X -> tuple cons(X head, tuple tail) asm "CONS"; +``` +在 Lisp 类型列表的开头添加一个元素。 +#### uncons +```func +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +``` +提取 Lisp 类型列表的头和尾。 +#### list_next +```func +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +``` +提取 Lisp 类型列表的头和尾。可用作 [(非)修改方法](/develop/func/statements#methods-calls)。 +```func +() foo(tuple xs) { + (_, int x) = xs.list_next(); ;; 获取第一个元素,`_` 表示不使用尾列表 + int y = xs~list_next(); ;; 弹出第一个元素 + int z = xs~list_next(); ;; 弹出第二个元素 +} +``` +#### car +```func +forall X -> X car(tuple list) asm "CAR"; +``` +返回 Lisp 类型列表的头部。 +#### cdr +```func +tuple cdr(tuple list) asm "CDR"; +``` +返回 Lisp 类型列表的尾部。 +### 其他元组原语 +#### empty_tuple +```func +tuple empty_tuple() asm "NIL"; +``` +创建 0 元素元组。 +#### tpush +```func +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +``` +将值 `x` 追加到 `Tuple t = (x1, ..., xn)`,但只有在结果 `Tuple t' = (x1, ..., xn, x)` 不超过 255 个字符时才有效。否则,会抛出类型检查异常。 +#### single +```func +forall X -> [X] single(X x) asm "SINGLE"; +``` +创建单例,即长度为一的元组。 +#### unsingle +```func +forall X -> X unsingle([X] t) asm "UNSINGLE"; +``` +解包单例。 +#### pair +```func +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +``` +创建一对。 +#### unpair +```func +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +``` +解包一对。 +#### triple +```func +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +``` +创建三元组。 + +#### untriple +```func +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +``` +解包三元组。 + +#### tuple4 +```func +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +``` +创建四元组。 + +#### untuple4 +```func +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +``` +解包四元组。 + +#### first +```func +forall X -> X first(tuple t) asm "FIRST"; +``` +返回元组的第一个元素。 + +#### second +```func +forall X -> X second(tuple t) asm "SECOND"; +``` +返回元组的第二个元素。 + +#### third +```func +forall X -> X third(tuple t) asm "THIRD"; +``` +返回元组的第三个元素。 + +#### fourth +```func +forall X -> X fourth(tuple t) asm "3 INDEX"; +``` +返回元组的第四个元素。 + +#### pair_first +```func +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +``` +返回一对的第一个元素。 + +#### pair_second +```func +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +``` +返回一对的第二个元素。 + +#### triple_first +```func +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +``` +返回三元组的第一个元素。 + +#### triple_second +```func +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +``` +返回三元组的第二个元素。 + +#### triple_third +```func +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +``` +返回三元组的第三个元素。 + +## 特定领域原语 +### 从 c7 提取信息 +关于智能合约调用的一些有用信息可以在 [c7 特殊寄存器](/learn/tvm-instructions/tvm-overview#control-registers)中找到。这些原语用于方便地提取数据。 +#### now +```func +int now() asm "NOW"; +``` +返回当前 Unix 时间作为整数。 +#### my_address +```func +slice my_address() asm "MYADDR"; +``` +以 Slice 形式返回当前智能合约的内部地址,其中包含 `MsgAddressInt`。如果需要,可以进一步使用诸如 `parse_std_addr` 之类的原语进行解析。 +#### get_balance +```func +[int, cell] get_balance() asm "BALANCE"; +``` +以 `tuple` 形式返回智能合约的剩余余额,其中包括 `int`(剩余余额,以nanoton计)和 `cell`(一个包含 32 位键的字典,代表“额外代币”的余额)。注意,RAW 原语(如 `send_raw_message`)不会更新此字段。 +#### cur_lt +```func +int cur_lt() asm "LTIME"; +``` +返回当前交易的逻辑时间。 +#### block_lt +```func +int block_lt() asm "BLOCKLT"; +``` +返回当前区块的起始逻辑时间。 +#### config_param +```func +cell config_param(int x) asm "CONFIGOPTPARAM"; +``` +以 `cell` 或 `null` 值的形式返回全局配置参数的值,其中整数索引为 `i`。 + +### 哈希 +#### cell_hash +```func +int cell_hash(cell c) asm "HASHCU"; +``` +计算`cell c`的 representation hash ,并将其作为一个256位无符号整数`x`返回。用于签名和检查由cell树表示的任意实体的签名。 +#### slice_hash +```func +int slice_hash(slice s) asm "HASHSU"; +``` +计算`slice s`的哈希,并将其作为一个256位无符号整数`x`返回。结果与创建一个只包含`s`的数据和引用的普通cell,并通过`cell_hash`计算其哈希的情况相同。 +#### string_hash +```func +int string_hash(slice s) asm "SHA256U"; +``` +计算`slice s`数据位的sha256。如果`s`的位长度不能被八整除,则抛出一个cell下溢异常。哈希值作为一个256位无符号整数`x`返回。 + +### 签名检查 +#### check_signature +```func +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +``` +使用`public_key`(也表示为一个256位无符号整数)检查`hash`(通常作为某些数据的哈希计算得出的256位无符号整数)的Ed25519 `signature`。签名必须包含至少512个数据位;只使用前512位。如果签名有效,结果为`-1`;否则,为`0`。请注意,`CHKSIGNU`创建一个包含哈希的256位切片,并调用`CHKSIGNS`。也就是说,如果`hash`是作为某些数据的哈希计算的,这些数据会被_两次_哈希,第二次哈希发生在`CHKSIGNS`内部。 +#### check_data_signature +```func +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; +``` +检查`signature`是否是使用`public_key`的`slice data`数据部分的有效Ed25519签名,类似于`check_signature`。如果`data`的位长度不能被八整除,则抛出一个cell下溢异常。Ed25519签名的验证是标准的,使用sha256将`data`简化为实际签名的256位数字。 + +### 计算boc大小 +下面的原语可能对于计算用户提供数据的存储费用有用。 +#### compute_data_size? +```func +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +``` +返回`(x, y, z, -1)`或`(null, null, null, 0)`。递归地计算以`cell c`为根的DAG中不同cell的数量`x`、数据位`y`和cell引用`z`,有效地返回此DAG使用的总存储量,同时考虑到相等cell的识别。`x`、`y`和`z`的值通过对此DAG进行深度优先遍历来计算,并使用访问过的cell哈希的哈希表来防止已访问cell的重复访问。访问的cell总数`x`不能超过非负的`max_cells`;否则,在访问第`(max_cells + 1)`个cell之前,计算将被中止,并返回零标志以指示失败。如果`c`为`null`,则返回`x = y = z = 0`。 +#### slice_compute_data_size? +```func +(int, int, int, int) slice_compute_data_size?(slice s, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +``` +类似于`compute_data_size?`,但接受的是`slice s`而不是`cell`。返回的`x`值不考 +虑包含切片`s`本身的cell;然而,`s`的数据位和cell引用在`y`和`z`中要被考虑。 +#### compute_data_size +```func +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +``` +`compute_data_size?`的非静默版本,失败时抛出cell溢出异常(8)。 +#### slice_compute_data_size +```func +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +``` +`slice_compute_data_size?`的非静默版本,失败时抛出cell溢出异常(8)。 +### 持久存储保存和加载 +#### get_data +```func +cell get_data() asm "c4 PUSH"; +``` +返回持久化合约存储cell。稍后可以使用切片和构建器原语对其进行解析或修改。 +#### set_data +```func +() set_data(cell c) impure asm "c4 POP"; +``` +将cell`c`设置为持久化合约数据。您可以使用这个原语更新持久化合约存储。 +### Continuation 原语 +#### get_c3 +```func +cont get_c3() impure asm "c3 PUSH"; +``` +通常`c3`有一个由合约的整个代码初始化的continuation。它用于函数调用。原语返回`c3`的当前值。 +#### set_c3 +```func +() set_c3(cont c) impure asm "c3 POP"; +``` +更新`c3`的当前值。通常,它用于实时更新智能合约代码。请注意,在执行此原语之后,当前代码(以及递归函数调用堆栈)不会改变,但任何其他函数调用将使用新代码中的函数。 +#### bless +```func +cont bless(slice s) impure asm "BLESS"; +``` +将`slice s`转换为一个简单的普通 continuation `c`,其中`c.code = s`,堆栈和保存列表为空。 + +### 与 gas 相关的原语 +#### accept_message +```func +() accept_message() impure asm "ACCEPT"; +``` +将当前 gas 限制`gl`设置为其允许的最大值`gm`,并将 gas 信用`gc`重置为零,同时减少`gr`的值`gc`。换句话说,当前智能合约同意购买一些 gas 以完成当前交易。这个动作是处理不携带价值(因此不含 gas )的外部消息所必需的。 + +有关更多详细信息,请查看[accept_message effects](/develop/smart-contracts/guidelines/accept) +#### set_gas_limit +```func +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +``` +将当前 gas 限制`gl`设置为`limit`和`gm`的最小值,并将 gas 信用`gc`重置为零。此时,如果消耗的 gas 量(包括当前指令)超过`gl`的结果值,则在设置新 gas 限制之前会抛出(未处理的) gas 不足异常。请注意,带有`limit ≥ 2^63 − 1`参数的`set_gas_limit`等同于`accept_message`。 + +有关更多详细信息,请查看[accept_message effects](/develop/smart-contracts/guidelines/accept) +#### commit +```func +() commit() impure asm "COMMIT"; +``` +提交寄存器`c4`(“持久数据”)和`c5`(“动作”)的当前状态,以便即使稍后抛出异常,当前执行也被视为“成功”,并保存这些值。 +#### buy_gas +```func +() buy_gas(int gram) impure asm "BUYGAS"; +``` +:::caution +`BUYGAS`操作码目前尚未实现 +::: + +计算可以用`gram`nanoton币购买的gas 量,并以与`set_gas_limit`相同的方式相应地设置`gl`。 + +### 动作原语 +#### raw_reserve +```func +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +``` +创建一个输出动作,该动作将准确地预留`amount` nanoton 币(如果`mode = 0`),最多`amount` nanoton 币(如果`mode = 2`),或除`amount` nanoton 币以外的所有 nanoton 币(如果`mode = 1`或`mode = 3`)从账户的剩余余额中。它大致等同于创建一个携带`amount` nanoton 币(或`b − amount` nanoton 币,其中`b`是剩余余额)的出站消息发送给自己,这样随后的输出动作就不会花费超过剩余部分的金额。`mode`中的+2位意味着外部动作在无法预留指定金额时不会失败;相反,将预留所有剩余余额。`mode`中的+8位意味着`amount <- -amount`在进行任何进一步的动作之前。`mode`中的+4位意味着在进行任何其他检查和动作之前,`amount`会增加当前账户的原始余额(在 Compute Phase 之前),包括所有额外代币。目前,`amount`必须是非负整数,`mode`必须在`0..15`范围内。 +#### raw_reserve_extra +```func +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +``` +类似于`raw_reserve`,但还接受一个由`cell`或`null`表示的额外代币字典`extra_amount`。这样,除了Toncoin以外的其他代币也可以被预留。 +#### send_raw_message +```func +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +``` +发送包含在`msg`中的原始消息,它应该包含一个正确序列化的消息对象X,唯一的例外是源地址可以有一个虚拟值`addr_none`(自动替换为当前智能合约地址),以及`ihr_fee`、`fwd_fee`、`created_lt`和`created_at`字段可以有任意值(在当前交易的 Action Phase 期间用正确的值重写)。整数参数`mode`包含标志。 + +目前有3种消息Modes和3种消息Flags。您可以将单一Mode与多个(也许没有)标志组合以获得所需的`mode`。组合只是意味着获取它们值的总和。下面给出了Modes和Flags的描述表格。 + +| Mode | 描述 | +|:-|:-| +| `0` | 普通消息 | +| `64` | 除了最初在新消息中指示的值之外,还携带入站消息的所有剩余价值 | +| `128` | 携带当前智能合约的所有剩余余额,而不是最初在消息中指示的值 | + +| Flag | 描述 | +|:-|:-| +| `+1` | 单独支付消息价值之外的转移费用 | +| `+2` | 忽略在 Action Phase 处理此消息时出现的任何错误 | +| `+16` | 在动作失败的情况下 - 弹回交易。如果使用`+2`,则无效。 | +| `+32` | 如果当前账户的最终余额为零,则必须销毁该账户(通常与模式128一起使用) | + +例如,如果您想发送常规消息并单独支付转账费用,请使用Mode`0`和Flag`+1`以获得`mode = 1`。如果 + +您想发送整个合约余额并立即销毁它,请使用Mode`128`和Flag`+32`以获得`mode = 160`。 + +#### set_code +```func +() set_code(cell new_code) impure asm "SETCODE"; +``` +创建一个输出动作,该动作将更改此智能合约的代码为cell`new_code`给出的代码。请注意,此更改仅在当前智能合约的当前运行成功终止后才生效。(参见[set_c3](/develop/func/stdlib#set_c3.)) + +### 随机数生成器原语 +伪随机数生成器使用随机种子(一个无符号的256位整数)和(有时)[c7](/learn/tvm-instructions/tvm-overview#control-registers)中保存的其他数据。在TON区块链中执行智能合约之前,随机种子的初始值是智能合约地址和全局区块随机种子的哈希。如果在一个区块内有多次运行相同的智能合约,那么所有这些运行都将具有相同的随机种子。例如,可以通过在第一次使用伪随机数生成器之前运行`randomize_lt`来解决这个问题。 + +:::caution +请记住,如果您不使用额外的技巧,下面函数生成的随机数是可以预测的。 + - [随机数生成](/develop/smart-contracts/guidelines/random-number-generation) +::: +#### random +```func +int random() impure asm "RANDU256"; +``` +生成一个新的伪随机无符号256位整数`x`。算法如下:如果`r`是旧的随机种子值,被视为一个32字节的数组(通过构造一个无符号256位整数的大端表示),那么计算其`sha512(r)`;这个哈希的前32字节被存储为随机种子的新值`r'`,剩余的32字节作为下一个随机值`x`返回。 +#### rand +```func +int rand(int range) impure asm "RAND"; +``` +在范围`0..range−1`(或`range..−1`,如果`range < 0`)内生成一个新的伪随机整数`z`。更准确地说,生成一个无符号随机值`x`,如`random`中一样;然后计算`z := x * range / 2^256`。 + +#### get_seed +```func +int get_seed() impure asm "RANDSEED"; +``` +以一个无符号的256位整数返回当前随机种子。 +#### set_seed +```func +int set_seed(int seed) impure asm "SETRAND"; +``` +将随机种子设置为一个无符号的256位`seed`。 + +#### randomize +```func +() randomize(int x) impure asm "ADDRAND"; +``` +通过将随机种子设置为两个32字节字符串的串联的sha256来将一个无符号的256位整数`x`混合到随机种子`r`中,这两个32字节字符串:第一个包含旧种子`r`的大端表示,第二个包含`x`的大端表示。 + +#### randomize_lt +```func +() randomize_lt() impure asm "LTIME" "ADDRAND"; +``` +相当于`randomize(cur_lt());`。 + +### 地址操作原语 +下面列出的地址操作原语根据以下TL-B方案序列化和反序列化值。 +```func +addr_none$00 = MsgAddressExt; + +addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + +anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + +addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + +addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + work + +chain_id:int32 address:(bits addr_len) = MsgAddressInt; +_ _:MsgAddressInt = MsgAddress; +_ _:MsgAddressExt = MsgAddress; + +int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + +ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; +``` +反序列化的`MsgAddress`由元组`t`表示,如下所述: +* `addr_none`表示为`t = (0)`,即包含一个等于零的整数的元组 +* `addr_extern`表示为`t = (1, s)`,其中切片`s`包含字段`external_address`。换句话说,`t`是一个对(包含两个条目的元组),包含一个等于一的整数和切片`s` +* `addr_std`表示为`t = (2, u, x, s)`,其中`u`要么是`null`(如果`anycast`不存在),要么是包含`rewrite_pfx`的切片`s'`(如果`anycast`存在)。接下来,整数`x`是`workchain_id`,切片`s`包含地址 +* `addr_var`表示为`t = (3, u, x, s)`,其中`u`、`x`和`s`的含义与`addr_std`相同 + +#### load_msg_addr +```func +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +``` +从 `slice s` 加载唯一有效的 `MsgAddress` 前缀,并返回此前缀 `s'` 及 `s` 的其余部分 `s''` 作为切片。 +#### parse_addr +```func +tuple parse_addr(slice s) asm "PARSEMSGADDR"; +``` +将包含有效 `MsgAddress` 的 `slice s` 分解为 `tuple t`,并包含此 `MsgAddress` 的独立字段。如果 `s` 不是有效的 `MsgAddress`,则抛出 cell 反序列化异常。 + +#### parse_std_addr +```func +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +``` +解析包含有效 `MsgAddressInt`(通常为 `msg_addr_std`)的切片 `s`,将重写 `anycast`(如果存在)应用到地址相同长度前缀,并返回工作链和256位地址作为整数。如果地址不是256位,或者 `s` 不是 `MsgAddressInt` 的有效序列化,抛出cell `deserialization` 异常。 +#### parse_var_addr +```func +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; +``` +`parse_std_addr` 的变体,即使地址不是正好256位长(由 `msg_addr_var` 表示),也以切片 `s` 返回(重写后的)地址。 + +## 调试原语 +目前,只有一个函数可用。 +#### dump_stack +```func +() dump_stack() impure asm "DUMPSTK"; +``` +转储堆栈(最多前255个值)并显示总堆栈深度。 + +## 切片原语 +据说,如果原语返回数据及其余部分的切片,则称其为*加载*数据(因此也可用作[修改方法](/develop/func/statements#modifying-methods))。 + +据说,如果原语仅返回数据,则称其为*预加载*数据(可用作[非修改方法](/develop/func/statements#non-modifying-methods))。 + +除非另有说明,加载和预加载原语从切片的前缀读取数据。 +#### begin_parse +```func +slice begin_parse(cell c) asm "CTOS"; +``` + +将 `cell` 转换为 `slice`。注意,`c` 必须是普通cell或特殊cell(见 [TVM.pdf](https://ton.org/tvm.pdf), 3.1.2),自动加载以产生普通cell `c'`,然后转换为 `slice`。 + +#### end_parse +```func +() end_parse(slice s) impure asm "ENDS"; +``` +检查 `s` 是否为空。如果不是,则抛出异常。 +#### load_ref +```func +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +``` +从切片中加载第一个引用。 +#### preload_ref +```func +cell preload_ref(slice s) asm "PLDREF"; +``` +从切片中预加载第一个引用。 +#### load_int +```func +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +``` +从切片中加载一个有符号的 `len` 位整数。 +#### load_uint +```func +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +``` +从切片中加载一个无符号的 `len` 位整数。 +#### preload_int +```func +;; int preload_int(slice s, int len) asm "PLDIX"; +``` +从切片中预加载一个有符号的 `len` 位整数。 +#### preload_uint +```func +;; int preload_uint(slice s, int len) asm "PLDUX"; +``` +从切片中预加载一个无符号的 `len` 位整数。 +#### load_bits +```func +;; (slice, slice) load_bits(slice s, int len) asm(s len -> + + 1 0) "LDSLICEX"; +``` +从切片 `s` 中加载前 `0 ≤ len ≤ 1023` 位到一个单独的切片 `s''`。 + +#### preload_bits +```func +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +``` +从切片 `s` 中预加载前 `0 ≤ len ≤ 1023` 位到一个单独的切片 `s''`。 + +#### load_coins +```func +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; +``` +加载序列化的 Toncoins 数量(任何最高为 `2^120 - 1` 的无符号整数)。 + +#### skip_bits +```func +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +``` +返回 `s` 的前 `0 ≤ len ≤ 1023` 位以外的所有值。 +#### first_bits +```func +slice first_bits(slice s, int len) asm "SDCUTFIRST"; +``` +返回 `s` 的前 `0 ≤ len ≤ 1023` 位。 +#### skip_last_bits +```func +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +``` +返回 `s` 中除最后 `0 ≤ len ≤ 1023` 位之外的所有值。 +#### slice_last +```func +slice slice_last(slice s, int len) asm "SDCUTLAST"; +``` +返回 `s` 的最后 `0 ≤ len ≤ 1023` 位。 +#### load_dict +```func +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +``` +从切片 `s` 中加载字典 `D`。可应用于字典或任意 `Maybe ^Y` 类型的值(如果使用 `nothing` 构造器,则返回 `null`)。 +#### preload_dict +```func +cell preload_dict(slice s) asm "PLDDICT"; +``` +从切片 `s` 中预加载字典 `D`。 +#### skip_dict +```func +slice skip_dict(slice s) asm "SKIPDICT"; +``` +像 `load_dict` 一样加载字典,但只返回切片的其余部分。 +### 切片大小原语 +#### slice_refs +```func +int slice_refs(slice s) asm "SREFS"; +``` +返回切片 `s` 中的引用数量。 +#### slice_bits +```func +int slice_bits(slice s) asm "SBITS"; +``` +返回切片 `s` 中的数据位数。 +#### slice_bits_refs +```func +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +``` +返回 `s` 中的数据位数和引用数量。 +#### slice_empty? +```func +int slice_empty?(slice s) asm "SEMPTY"; +``` +检查切片 `s` 是否为空(即,不包含数据位和 cell 引用)。 +#### slice_data_empty? +```func +int slice_data_empty?(slice s) asm "SDEMPTY"; +``` +检查切片 `s` 是否没有数据位。 +#### slice_refs_empty? +```func +int slice_refs_empty?(slice s) asm "SREMPTY"; +``` +检查切片 `s` 是否没有引用。 +#### slice_depth +```func +int slice_depth(slice s) asm "SDEPTH"; +``` +返回切片 `s` 的深度。如果 `s` 没有引用,则返回 `0`;否则,返回值是 `s` 中引用的 cell 的深度最大值加一。 + +## 构建器原语 +据说,如果原语将值 `x` 存储到构建器 `b` 中,则返回构建器 `b'` 的修改版本,并在其末尾存储值 `x`。这可以用作[非修改方法](/develop/func/statements#non-modifying-methods)。 + +下面列出的所有原语首先检查构建器中是否有足够的空间,然后是被序列化值的范围。 +#### begin_cell +```func +builder begin_cell() asm "NEWC"; +``` +创建一个新的空 `builder`。 +#### end_cell +```func +cell end_cell(builder b) asm "ENDC"; +``` +将 `builder` 转换为普通的 `cell`。 +#### store_ref +```func +builder store_ref(builder b, cell c) asm(c b) "STREF"; +``` +将对 cell `c` 的引用存储到构建器 `b` 中。 +#### store_uint +```func +builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +``` +将无符号的 `len` 位整数 `x` 存储到 `b` 中,`0 ≤ len ≤ 256`。 +#### store_int +```func +builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +``` +将有符号的 `len` 位整数 `x` 存储到 `b` 中,`0 ≤ len ≤ 257`。 +#### store_slice +```func +builder store_slice(builder b, slice s) asm "STSLICER"; +``` +将切片 `s` 存储到构建器 `b` 中。 +#### store_grams +```func +builder store_grams(builder b, int x) asm "STGRAMS"; +``` +#### store_coins +```func +builder store_coins(builder b, int x) asm "STGRAMS"; +``` +将范围 `0..2^120 − 1` 内的整数 `x` 存储(序列化)到构建器 `b` 中。`x` 的序列化包含一个4位无符号大端整数 `l`,它是最小的整数 `l ≥ 0`,使得 `x < 2^8l`,后跟 `8l` 位无符号大端表示的 `x`。如果 `x` 不属于支持范围,则抛出范围检查异常。 + +这是存储 Toncoins 的最常见方法。 + +#### store_dict +```func +builder store_dict(builder b, cell c) asm(c b) "STDICT"; +``` +将由 cell `c` 或 `null` 表示的字典 `D` 存储到构建器 `b` 中。换句话说,如果 `c` 不是 `null`,则存储1位和对 `c` 的引用;否则存储0位。 +#### store_maybe_ref +```func +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; +``` +等同于 `store_dict`。 + +### 构建器大小原语 +#### builder_refs +```func +int builder_refs(builder b) asm "BREFS"; +``` +返回已经存储在构建器 `b` 中的 cell 引用数量。 +#### builder_bits +```func +int builder_bits(builder b) asm "BBITS"; +``` +返回已经存储在构建器 `b` 中的数据位数。 +#### builder_depth +```func +int builder_depth(builder b) asm "BDEPTH"; +``` +返回构建器 `b` 的深度。如果 `b` 中没有存储任何 cell 引用,则返回 `0`;否则,返回值是从 `b` 中引用的 cell 的最大深度加一。 + +## Cell原语 +#### cell_depth +```func +int cell_depth(cell c) asm "CDEPTH"; +``` +返回 cell `c` 的深度。如果 `c` 没有引用,则返回 `0`;否则,返回值是从 `c` 中引用的 cell 的最大深度加一。如果 `c` 是 `null`而不是 cell ,则返回零。 +#### cell_null? +```func +int cell_null?(cell c) asm "ISNULL"; +``` +检查 `c` 是否为 `null`。通常 `null`-cell 表示一个空字典。FunC 也有多态的 `null?` 内置函数。(见 [内置函数](/develop/func/builtins#other-primitives)。) + +## 字典原语 + +:::caution +下面的字典原语是低层级的,不检查它们应用到的 cell 的结构是否与操作签名匹配。对“非字典”执行字典操作,或对具有不同键类型的字典执行操作(例如,同时对8位有符号键和7位无符号键的字典键值进行写入),是**未定义行为**。通常在这种情况下会抛出异常,但在极少数情况下可能写入/读取错误值。强烈建议开发者避免这种代码。 +::: + +如 [TVM.pdf](https://ton.org/tvm.pdf) 所述: +> 字典在 TVM 堆栈值中有两种不同的表示方式: +> * 切片 `s`,包含类型为 `HashmapE(n, X)` 的 TL-B 值的序列化。换句话说,`s` 要么由等于零的一位(如果字典为空)组成,要么由等于一的一位和对包含二叉树 root 的 cell 的引用组成,即类型为 `Hashmap(n, X)` 的序列化值。 +> * “也许是 cell ” `c^?`,即要么是 cell (包含如前所述类型为 `Hashmap(n, X)` 的序列化值),要么是 `null`(对应于空字典,参见 [null 值](/develop/func/types#null-values))。当使用“也许是 cell ” `c^?` 表示字典时,我们通常用 `D` 表示。 +> +> 下面列出的大多数字典原语接受并返回第二种形式的字典,这种形式更适合堆栈操作。然而,更大的 TL-B 对象中的序列化字典使用第一种表示。 + +在 FunC 中,字典也由 `cell` 类型表示,隐含假设它可能是 `null` 值。字典没有不同键长或值类型的单独类型(毕竟,这是 FunC,不是 FunC++)。 + +### 分类说明 +字典原语可能将字典的键解释为无符号 `l` 位整数、有符号 `l` 位整数或 `l` 位切片。下面列出的原语名称中的前缀不同。`i` 表示有符号整数键,`u` 表示无符号整数键,空前缀表示切片键。 + +例如,`udict_set` 是带有无符号整数键的字典的按键设置函数;`idict_set` 是带有有符号整数键的字典的相应函数;`dict_set` 是带有切片键的字典的函数。 + +标题中使用了空前缀。 + +此外,一些原语有以 `~` 为前缀的对应项。这使得可以将它们用作[修改方法](/develop/func/statements#modifying-methods)。 + +### 字典的值 +字典中的值可以直接存储为内部字典 cell 的子切片,也可以作为对单独 cell 的引用存储。在第一种情况下,不能保证一个足够小以适应 cell 格的值也将适应字典 ,则适合字典(因为内部 cell 的一部分可能已经被对应键的一部分占用)。另一方面,第二种存储方式的 gas 效率较低。使用第二种方法存储一个值等同于在第一种方法中插入一个没有数据位的切片和一个对该值的单一引用。 + +#### dict_set +```func +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +``` +在字典 `dict` 中设置与 `key_len` 位键 `index` 关联的值 `value`(一个切片),并返回结果字典。 +#### dict_set_ref +```func +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +``` +类似于 `dict_set`,但值设置为对 cell `value` 的引用。 +#### dict_get? +```func +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +``` +在字典 `dict` 中查找 `key_len` 位键 `index`。成功时,返回找到的值作为切片以及表示成功的 `-1` 标志位。如果失败,则返回 `(null, 0)`。 +#### dict_get_ref? +```func +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; +``` +类似于 `dict_get?`,但返回找到的值的第一个引用。 +#### dict_get_ref +```func +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +``` +`dict_get_ref?` 的变体,如果键 `index` 不在字典 `dict` 中,则返回 `null` 而不是值。 + +#### dict_set_get_ref +```func +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +``` +将与 `index` 关联的值设置为 `value`(如果 `value` 为 `null`,则删除键),并返回旧值(如果值不存在,则为 `null`)。 +#### dict_delete? +```func +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +``` +从字典 `dict` 中删除 `key_len` 位键 `index`。如果键存在,则返回修改后的字典 `dict'` 和成功标志位 `−1`。否则,返回原始字典 `dict` 和 `0`。 +#### dict_delete_get? +```func +(cell, slice, int) idict_delete + +_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +``` +从字典 `dict` 中删除 `key_len` 位键 `index`。如果键存在,则返回修改后的字典 `dict'`、与键 k 关联的原始值 `x`(由一个切片表示),以及成功标志位 `−1`。否则,返回 `(dict, null, 0)`。 +#### dict_add? +```func +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +``` +`dict_set` 的 `add` 对应项,将字典 `dict` 中与键 `index` 关联的值设置为 `value`,但仅当它尚未出现在 `D` 中时。返回修改后的字典和 `-1` 标志位或 `(dict, 0)`。 +#### dict_replace? +```func +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +``` +类似于 `dict_set` 的 `replace` 操作,但只有在键 `index` 已经出现在 `dict` 中时才将字典 `dict` 中键 `index` 的值设置为 `value`。返回修改后的字典和 `-1` 标志位或 `(dict, 0)`。 +### 构建器对应项 +下面的原语接受新值作为构建器而不是切片,如果需要从堆栈中计算的几个组件序列化值,这通常更方便。其效果大致相当于将 b 转换为切片并执行上面列出的相应原语。 +#### dict_set_builder +```func +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +``` +类似于 `dict_set`,但接受构建器。 +#### dict_add_builder? +```func +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +``` +类似于 `dict_add?`,但接受构建器。 +#### dict_replace_builder? +```func +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +``` +类似于 `dict_replace?`,但接受构建器。 +#### dict_delete_get_min +```func +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +``` +计算字典 `dict` 中的最小键 `k`,将其移除,并返回 `(dict', k, x, -1)`,其中 `dict'` 是修改后的 `dict`,`x` 是与 `k` 关联的值。如果字典为空,则返回 `(dict, null, null, 0)`。 + +请注意,`idict_delete_get_min` 返回的键可能与 `dict_delete_get_min` 和 `udict_delete_get_min` 返回的键不同。 + +#### dict_delete_get_max +```func +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +``` +计算字典 `dict` 中的最大键 `k`,将其移除,并返回 `(dict', k, x, -1)`,其中 `dict'` 是修改后的 `dict`,`x` 是与 `k` 关联的值。如果字典为空,则返回 `(dict, null, null, 0)`。 + +#### dict_get_min? +```func +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +``` +计算字典 `dict` 中的最小键 `k` 及其关联值 `x`,并返回 `(k, x, -1)`。如果字典为空,则返回 `(null, null, 0)`。 +#### dict_get_max? +```func +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +``` +计算字典 `dict` 中的最大键 `k` 及其关联值 `x`,并返回 `(k, x, -1)`。如果字典为空,则返回 `(null, null, 0)`。 +#### dict_get_min_ref? +```func +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +``` +类似于 `dict_get_min?`,但返回值中唯一的引用作为引用。 +#### dict_get_max_ref? +```func +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +``` +类似于 `dict_get_max?`,但返回值中唯一的引用作为引用。 +#### dict_get_next? +```func +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +``` +计算字典 `dict` 中大于 `pivot` 的最小键 `k`;返回 `k`、关联值和表示成功的标志位。如果字典为空,则返回 `(null, null, 0)`。 +#### dict_get_nexteq? +```func +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +``` +类似于 `dict_get_next?`,但计算大于或等于 `pivot` 的最小键 `k`。 +#### dict_get_prev? +```func +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +``` +类似于 `dict_get_next?`,但计算小于 `pivot` 的最大键 `k`。 +#### dict_get_preveq? +```func +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +``` +类似于 `dict_get_prev?`,但计算小于或等于 `pivot` 的最大键 `k`。 +#### new_dict +```func +cell new_dict() asm "NEWDICT"; +``` +创建一个空字典,实际上是一个 `null` 值。`null()` 的特例。 +#### dict_empty? +```func +int dict_empty?(cell c) asm "DICTEMPTY"; +``` +检查字典是否为空。等同于 `cell_null?`。 + +## 前缀字典原语 +TVM 还支持具有非固定长度键的字典,这些键形成前缀码(即,没有键是另一个键的前缀)。在 [TVM 指令](/learn/tvm-instructions/tvm-overview) 部分了解更多信息。 + +#### pfxdict_get? +```func +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +``` +返回 `(s', x, s'', -1)` 或 `(null, null, s, 0)`。在前缀码字典 `dict` 中查找切片 `key` 的唯一前缀。如果找到,返回 `s` 的前缀作为 `s'` 和相应的值(也是切片)作为 `x`。`s` 的剩余部分作为切片 `s''` 返回。如果 `s` 的任何前缀都不是前缀码字典 `dict` 中的键,则返回未更改的 `s` 和零标志位以表示失败。 +#### pfxdict_set? +```func +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +``` +类似于 `dict_set`,但如果键是字典中另一个键的前缀,则可能失败。表示成功,返回一个标志位。 +#### pfxdict_delete? +```func +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; +``` +类似于 `dict_delete?`。 + +## 特殊原语 +#### null +```func +forall X -> X null() asm "PUSHNULL"; +``` +通过 TVM 类型 `Null`,FunC 表示某些原子类型的值的缺失。因此 `null` 实际上可以具有任何原子类型。 +#### ~impure_touch +```func +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; +``` +标记一个变量为已使用,以便即使它不是非纯的(impure),产生它的代码也不会被删除。(参见 [非纯修饰符](/develop/func/functions#impure-specifier)) + +## 其他原语 +#### min +```func +int min(int x, int y) asm "MIN"; +``` +计算两个整数 `x` 和 `y` 的最小值。 +#### max +```func +int max(int x, int y) asm "MAX"; +``` +计算两个整数 `x` 和 `y` 的最大值。 +#### minmax +```func +(int, int) minmax(int x, int y) asm "MINMAX"; +``` +对两个整数进行排序。 +#### abs +```func +int abs(int x) asm "ABS"; +``` +计算整数 `x` 的绝对值。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/types.md new file mode 100644 index 0000000000..bf8fa38843 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/docs/types.md @@ -0,0 +1,86 @@ +# 类型 + +:::info + +FunC 文档最初由 [@akifoq](https://github.com/akifoq) 编写。 + +::: + +FunC 有以下内置类型。 + +## 原子类型 + +- `int` 是 257 位有符号整数的类型。默认情况下,启用溢出检查,会导致整数溢出异常。 +- `cell` 是 TVM cell的类型。TON 区块链中的所有持久数据都存储在cell树中。每个cell最多有 1023 位任意数据和最多四个对其他cell的引用。cell在基于堆栈的 TVM 中用作内存。 +- `slice` 是cell切片的类型。cell可以转换成切片,然后可以通过从切片加载数据位和对其他cell的引用来获得cell中的数据。 +- `builder` 是cell构建器的类型。数据位和对其他cell的引用可以存储在构建器中,然后构建器可以最终化为新cell。 +- `tuple` 是 TVM 元组的类型。元组是有序集合,最多包含 255 个组件,这些组件的值类型可能不同。 +- `cont` 是 TVM continuation的类型。Continuations 用于控制 TVM 程序执行的流程。从 FunC 的角度来看,它是相当低层级的对象,尽管从概念上讲相当通用。 + +请注意,上述任何类型都只占用 TVM 堆栈中的单个条目。 + +### 没有布尔类型 + +在 FunC 中,布尔值被表示为整数;`false` 表示为 `0`,`true` 表示为 `-1`(二进制表示为 257 个一)。逻辑运算作为位运算执行。当检查条件时,每个非零整数都被视为 `true` 值。 + +### Null值 + +通过 TVM 类型 `Null` 的值 `null`,FunC 表示某些原子类型的值缺失。标准库中的一些原语可能被类型化为返回原子类型,并在某些情况下实际返回 `null`。其他原语可能被类型化为接受原子类型的值,但也可以与 `null` 值一起正常工作。这种行为在原语规范中明确说明。默认情况下,禁止 `null` 值,这会导致运行时异常。 + +这样,原子类型 `A` 可能被隐式转换为类型 `A^?`,也就是 `Maybe A`(类型检查器对这种转换无感知)。 + +## Hole类型 + +FunC 支持类型推断。类型 `_` 和 `var` 表示类型“holes”,稍后可以在类型检查期间用某些实际类型填充。例如,`var x = 2;` 是变量 `x` 等于 `2` 的定义。类型检查器可以推断出 `x` 的类型为 `int`,因为 `2` 的类型为 `int`,赋值的左右两边必须类型相等。 + +## 复合类型 + +类型可以组合成更复杂的类型。 + +### 函数类型 + +形式为 `A -> B` 的类型表示具有指定域和陪域的函数。例如,`int -> cell` 是一个函数类型,它接受一个整数参数并返回一个 TVM cell。 + +在内部,这种类型的值被表示为continuations。 + +### 张量类型 + +形式为 `(A, B, ...)` 的类型本质上表示有序的值集合,这些值的类型为 `A`、`B`、`...`,它们一起占用多个 TVM 堆栈条目。 + +例如,如果函数 `foo` 的类型为 `int -> (int, int)`,这意味着该函数接受一个整数并返回一对整数。 + +调用此函数可能看起来像 `(int a, int b) = foo(42);`。在内部,该函数消耗一个堆栈条目并留下两个。 + +请注意,从低层级角度来看,类型 `(int, (int, int))` 的值 `(2, (3, 9))` 和类型 `(int, int, int)` 的值 `(2, 3, 9)`,在内部以三个堆栈条目 `2`、`3` 和 `9` 的形式表示。对于 FunC 类型检查器,它们是**不同**类型的值。例如,代码 `(int a, int b, int c) = (2, (3, 9));` 将无法编译。 + +张量类型的特殊情况是**cell类型** `()`。它通常用于表示函数不返回任何值或没有参数。例如,函数 `print_int` 的类型将为 `int -> ()`,而函数 `random` 的类型为 `() -> int`。它有一个唯一的inhabitant `()`,它占用 0 个堆栈条目。 + +类型 `(A)` 被类型检查器视为与 `A` 相同的类型。 + +### 元组类型 + +形式为 `[A, B, ...]` 的类型表示在编译时已知长度和组件类型的 TVM 元组。例如,`[int, cell]` 是一个元组类型,其长度恰好为 2,其中第一个组件是整数,第二个是cell。`[]` 是空元组的类型(具有唯一的inhabitant——空元组)。请注意,与cell类型 `()` 相反,`[]` 的值占用一个堆栈条目。 + +## 带有类型变量的多态 + +FunC拥有支持多态函数的 Miller-Rabin 类型系统。例如,以下是一个函数: + +```func +forall X -> (X, X) duplicate(X value) { + return (value, value); +} +``` + +是一个多态函数,它接受一个(单堆栈条目)值并返回这个值的两个副本。`duplicate(6)` 将产生值 `6 6`,而 `duplicate([])` 将产生两个空元组 `[] []` 的副本。 + +在这个例子中,`X` 是一个类型变量。 + +有关此主题的更多信息,请参阅[函数](/develop/func/functions#polymorphism-with-forall)部分。 + +## 用户定义类型 + +目前,FunC 不支持定义除上述类型构造之外的类型。 + +## 类型宽度 + +您可能已经注意到,每种类型的值都占用一定数量的堆栈条目。如果所有该类型的值都占用相同数量的条目,则该数字称为**类型宽度(type width)**。目前只能为具有固定且预先知道的类型宽度的类型定义多态函数。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/libraries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/libraries.md new file mode 100644 index 0000000000..9d90b62b92 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/libraries.md @@ -0,0 +1,17 @@ +# FunC SDK和库 + +## 标准库 + +- [stdlib](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc) — FunC的标准库 +- [mathlib](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/mathlib.fc) — 固定点数学库 + +## 社区库 + +- [continuation-team/openlib.func](https://github.com/continuation-team/openlib.func) - 在常见场景中减少交易费用。 +- [open-contracts/utils](https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/utils) — 实用工具库 +- [open-contracts/strings](https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/strings) — 提供字符串操作 +- [open-contracts/math](https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/math) — 扩展FunC算术运算的数学库 +- [open-contracts/tuples](https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/tuples) — 为FunC收集与元组相关的函数 +- [open-contracts/crypto](https://github.com/TonoxDeFi/open-contracts/tree/main/contracts/crypto) — 提供secp256k1曲线的操作 +- [toncli/test-libs](https://github.com/disintar/toncli/tree/master/src/toncli/lib/test-libs) - 对TLB的操作,生成和解析典型消息和类型 +- [ston-fi/funcbox](https://github.com/ston-fi/funcbox) - FunC片段和实用工具的集合。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/overview.mdx new file mode 100644 index 0000000000..d623519c67 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/func/overview.mdx @@ -0,0 +1,141 @@ +import Button from '@site/src/components/button' + +# 概述 + +高级语言 FunC 用于在 TON 上编程智能合约。 + +FunC 是一种领域特定的、类 C 语言的、静态类型语言。 +这是一个用 FunC 编写的发送资金的简单示例方法: + +```func +() send_money(slice address, int amount) impure inline { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce + .store_slice(address) + .store_coins(amount) + .end_cell(); + + send_raw_message(msg, 64); +} +``` + +FunC 程序被编译成 Fift 汇编代码,生成对应的 [TON 虚拟机](/learn/tvm-instructions/tvm-overview) 字节码。 + +进一步地,这个字节码(实际上是 [cell树](/learn/overviews/cells),就像 TON 区块链中的任何其他数据一样)可以用于在区块链中创建智能合约,或者可以在 TVM 的本地实例上运行。 + + + + + +## 编译器 + +### 用 JS 编译 + +开始开发和编译智能合约最方便快捷的方式是使用 Blueprint 框架。更多信息请参阅 [Blueprint](/develop/smart-contracts/sdk/javascript) 部分。 + +```bash +npm create ton@latest +``` + +### 使用原始二进制文件编译 + +如果您想在本地使用原生 TON 编译器 FunC,您需要在机器上设置二进制文件。可以从以下位置下载 FunC 编译器二进制文件,适用于 Windows、MacOS(Intel/M1)和 Ubuntu: + +- [环境设置页面](/develop/smart-contracts/environment/installation) + +:::info +同时,您始终可以从源代码构建二进制文件,如: +[FunC 编译器源代码](https://github.com/ton-blockchain/ton/tree/master/crypto/func)(阅读[如何从源代码编译](/develop/howto/compile#func) FunC 编译器)。 +::: + +## TON 课程:FunC + +[TON 区块链课程](https://stepik.org/course/201638/) 是关于 TON 区块链开发的全面指南。 + +第 4 模块完整覆盖了 FunC 语言和智能合约开发。 + + + + + + + + + + + +## 教程 + +:::tip 新手提示 +开始使用 FunC 进行开发的最佳起点:[入门介绍](/develop/smart-contracts/) +::: + +社区专家提供的其他材料: + +- [TON Speed Run 系列](https://tonspeedrun.com/) + - [🚩 挑战 1:简单 NFT 部署](https://github.com/romanovichim/TONQuest1) + - [🚩 挑战 2:聊天机器人合约](https://github.com/romanovichim/TONQuest2) + - [🚩 挑战 3:Jetton 自动售卖机](https://github.com/romanovichim/TONQuest3) + - [🚩 挑战 4:彩票/抽奖](https://github.com/romanovichim/TONQuest4) + - [🚩 挑战 5:5 分钟内创建与合约交互的 UI](https://github.com/romanovichim/TONQuest5) + - [🚩 挑战 6:分析 Getgems 市场上的 NFT 销售](https://github.com/romanovichim/TONQuest6) + +\ + +- [Func & Blueprint](https://www.youtube.com/watch?v=7omBDfSqGfA&list=PLtUBO1QNEKwtO_zSyLj-axPzc9O9rkmYa) 由 **@MarcoDaTr0p0je** 提供 +- [Learn FunC in Y Minutes](https://learnxinyminutes.com/docs/func/) 由 **@romanovichim** 提供 +- [TON Hello World:编写您的第一个智能合约的逐步指南](https://ton-community.github.io/tutorials/02-contract/) +- [TON Hello World:测试您的第一个智能合约的逐步指南](https://ton-community.github.io/tutorials/04-testing/) +- [10 FunC 课程](https://github.com/romanovichim/TonFunClessons_Eng) 由 **@romanovichim** 提供,使用 blueprint +- [10 FunC 课程(俄文版)](https://github.com/romanovichim/TonFunClessons_ru) 由 **@romanovichim** 提供,使用 blueprint +- [FunC 测验](https://t.me/toncontests/60) 由 **Vadim**提供—适合自我检查。这将需要 10-15 分钟。问题主要关于 FunС,以及一些关于 TON 的常规问题 +- [FunC 测验(俄文版)](https://t.me/toncontests/58?comment=14888) 由 **Vadim** 提供 —— 俄文版 FunC 测验 + +## 竞赛 + +参加 [竞赛](https://t.me/toncontests) 是学习 FunC 的绝佳方式。 + +您也可以学习以前的学习课程。 + +#### 竞赛传承 + +| 竞赛描述 | 任务 | 解决方案 | +| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- | +| TSC #5(2023年12月) | [Tasks](https://github.com/ton-community/tsc5) | | +| TSC #4(2023年9月) | [Tasks](https://github.com/ton-community/tsc4) | [Solutions](/develop/smart-contracts/examples#ton-smart-challenge-4) | +| TSC #3(2022年12月) | [Tasks](https://github.com/ton-blockchain/func-contest3) | [Solutions](https://github.com/nns2009/TON-FunC-contest-3) | +| TSC #2(2022年7月) | [Tasks](https://github.com/ton-blockchain/func-contest2) | [Solutions](https://github.com/ton-blockchain/func-contest2-solutions) | +| TSC #1(2022年3月) | [Tasks](https://github.com/ton-blockchain/func-contest1) | [Solutions](https://github.com/ton-blockchain/func-contest1-solutions) | + +## 智能合约示例 + +标准的基础智能合约,如钱包、选举器(管理 TON 的验证)、多签钱包等,可作为学习时的参考。 + +- [智能合约示例](/develop/smart-contracts/examples) + +## 更新日志 + +[FunC 更新历史](/develop/func/changelog)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/ide-plugins.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/ide-plugins.md new file mode 100644 index 0000000000..6ca3460a1f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/ide-plugins.md @@ -0,0 +1,31 @@ +# IDE 插件 + +## IntelliJ IDEs 插件 + +![](/img/docs/ton-jetbrains-plugin.png) + +:::info +此插件可与任何 JetBrains 产品一起使用。 +(IntelliJ IDEA、WebStorm、PyCharm、CLion 等) +::: + +有几种安装插件的方法: + +- 在 IDE 插件部分直接搜索带有 "**TON**" 关键词的插件 +- [Marketplace 链接](https://plugins.jetbrains.com/plugin/23382-ton) +- [GitHub 代码库](https://github.com/ton-blockchain/intellij-ton) + +## VS Code 插件 + +Visual Studio Code 是开发者的免费且受欢迎的 IDE。 + +- [Marketplace 链接](https://marketplace.visualstudio.com/items?itemName=tonwhales.func-vscode) +- [GitHub 代码库](https://github.com/ton-foundation/vscode-func) + +## FunC Sublime Text 插件 + +- [GitHub 代码库](https://github.com/savva425/func_plugin_sublimetext3) + +## Neovim + +要在 Neovim 中启用语法高亮,请按照 [nvim-treesitter 快速入门指南](https://github.com/nvim-treesitter/nvim-treesitter#quickstart) 中的安装说明进行操作。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/javascript.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/javascript.mdx new file mode 100644 index 0000000000..c0ac07806e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/javascript.mdx @@ -0,0 +1,58 @@ +import Button from '@site/src/components/button' + +# Blueprint SDK + +![Blueprint](\img\blueprint\logo.svg) + +一个用于TON的开发环境,用于编写、测试和部署智能合约。 + +## 快速开始 🚀 + +在终端中运行以下命令以创建一个新项目,并按照屏幕上的指示操作: + +```bash +npm create ton@latest +``` + + + +### 核心特性 + +- 构建、测试和部署智能合约的流畅工作流 +- 使用您最喜欢的钱包(例如Tonkeeper)轻松部署到主网/测试网 +- 在进程中运行的隔离区块链上快速测试多个智能合约 + +### 技术栈 + +- 编译FunC https://github.com/ton-community/func-js (无需 cli) +- 测试智能合约 https://github.com/ton-org/sandbox +- 使用TON Connect 2.0兼容钱包或`ton://`深层链接部署智能合约 + +### 要求 + +- [Node.js](https://nodejs.org/) 使用像v18这样的近期版本,用`node -v`验证版本 +- 支持TypeScript和FunC的IDE,如[Visual Studio Code](https://code.visualstudio.com/),配合[FunC插件](https://marketplace.visualstudio.com/items?itemName=tonwhales.func-vscode) + +## 参考资料 + +### GitHub + +- https://github.com/ton-org/blueprint + +### 资料 + +- [在DoraHacks直播中使用Blueprint](https://www.youtube.com/watch?v=5ROXVM-Fojo) +- [创建一个新项目](https://github.com/ton-org/blueprint#create-a-new-project) +- [开发一个新的智能合约](https://github.com/ton-org/blueprint#develop-a-new-contract) +- [\[YouTube\] 使用Blueprint EN 的FunC](https://www.youtube.com/watch?v=7omBDfSqGfA&list=PLtUBO1QNEKwtO_zSyLj-axPzc9O9rkmYa) ([俄文版](https://youtube.com/playlist?list=PLyDBPwv9EPsA5vcUM2vzjQOomf264IdUZ)) + +## 参阅 + +- [开发智能合约介绍](/develop/smart-contracts/) +- [如何使用钱包智能合约工作](/develop/smart-contracts/tutorials/wallet) +- [使用toncli](/develop/smart-contracts/sdk/toncli) +- [SDKs](/develop/dapps/apis/sdk) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/testnet.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/testnet.md new file mode 100644 index 0000000000..19f353b906 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/getting-started/testnet.md @@ -0,0 +1,27 @@ +# 了解测试网络 + +在开发和测试过程中使用 TON 测试网络。 + +:::info +测试网络中的代币没有价值,测试网络可以被重置。 +::: + +- 测试网络全局配置: https://ton.org/testnet-global.config.json +- 您可以在 [@test_giver_ton_bot](https://t.me/testgiver_ton_bot) 获取免费的测试代币 +- 在这个 Telegram 频道中查看测试网络的状态: [@testnetstatus](https://t.me/testnetstatus) + +## 服务 + +为了方便起见,几乎整个主网的基础设施(钱包、API、桥接等)都已在测试网络中重建。 + +- 浏览器: https://testnet.tonscan.org +- Web 钱包: https://wallet.ton.org?testnet=true +- 浏览器扩展: 使用 [mainnet browser extension](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd) 并 [进行此操作](https://github.com/toncenter/ton-wallet#switch-between-mainnettestnet-in-extension)。 +- 测试网 TON Center API: https://testnet.toncenter.com +- 测试网 HTTP API: https://testnet.tonapi.io/ +- 测试网桥接: https://ton.org/bridge?testnet=true + +## 一些第三方工具 + +- 要切换到 [Tonkeeper's testnet](https://tonkeeper.com/),在设置中点击版本 5 次。 +- 测试网 CryptoBot: https://t.me/CryptoTestnetBot diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/guidelines.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/guidelines.mdx new file mode 100644 index 0000000000..22917696a0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/guidelines.mdx @@ -0,0 +1,46 @@ +import Button from '@site/src/components/button' + +# 概览 + +本页面收集了一些建议和最佳实践,可在开发TON区块链上的新智能合约时遵循。 + +- [内部消息](/develop/smart-contracts/guidelines/internal-messages) +- [外部消息](/develop/smart-contracts/guidelines/external-messages) +- [使用不可弹回消息](/develop/smart-contracts/guidelines/non-bouncable-messages) +- [Get方法](/develop/smart-contracts/guidelines/get-methods) +- ["accept_message"作用](/develop/smart-contracts/guidelines/accept) +- [支付处理查询和发送响应的费用](/develop/smart-contracts/guidelines/processing) +- [如何及为何对您的TON智能合约进行分片。研究TON的Jettons结构](https://blog.ton.org/how-to-shard-your-ton-smart-contract-and-why-studying-the-anatomy-of-tons-jettons) +- [TON Keeper创始人Oleg Andreev和Oleg Illarionov关于TON jettons的谈话](https://www.youtube.com/watch?v=oEO29KmOpv4) + +## TON 课程:合约开发 + +[TON区块链课程](https://stepik.org/course/201638/)是关于TON区块链开发的全面指南。 + +- 第2模块专注于**TVM、交易、可扩展性和商业案例**。 +- 第3模块专注于**智能合约开发的全过程**。 + + + + + + + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/external-messages.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/external-messages.md new file mode 100644 index 0000000000..10c03dddce --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/external-messages.md @@ -0,0 +1,29 @@ +# 外部消息 + +外部消息是`从外部发送`到 TON 区块链中的智能合约,以使它们执行特定操作。 + +例如,钱包智能合约期望接收包含钱包所有者签名的订单的外部消息(例如,从钱包智能合约发送的内部消息)。当这样的外部消息被钱包智能合约接收时,它首先检查签名,然后接受消息(通过运行 TVM 原语 `ACCEPT`),然后执行所需的任何操作。 + +:::danger +请注意,所有外部消息`必须受到保护`,以防止重放攻击。验证者通常会从建议的外部消息池中移除一条外部消息(从网络接收);然而,在某些情况下,`另一个验证者`可能会两次处理同一个外部消息(从而为同一个外部消息创建第二个交易,导致原始操作的重复)。更糟糕的是,`恶意行为者可以从`包含要处理的交易的区块中提取外部消息并稍后重新发送。这可能会迫使钱包智能合约重复付款,例如。 +::: + +export const Highlight = ({children, color}) => ( + +{children} + +); + +保护智能合约免受与外部消息相关的重放攻击的最简单方法 是在智能合约的持久数据中存储一个 32 位计数器 `cur-seqno`,并在任何入站外部消息的(已签名部分)中获取一个 `req-seqno` 值。然后只有在签名有效且 `req-seqno` 等于 `cur-seqno` 时,才接受外部消息。在成功处理后,持久数据中的 `cur-seqno` 值增加一,因此相同的外部消息将不再被接受。 + +而且也可以在外部消息中包含一个 `expire-at` 字段,并且只有在当前 Unix 时间小于此字段的值时,才接受外部消息。这种方法可以与 `seqno` 结合使用;或者,接收信息的智能合约可以在其持久数据中存储所有最近(未过期)接受的外部消息的(哈希)集合,如果它是存储消息之一的副本会拒绝新的外部消息。此集合中对过期消息的一些垃圾回收也应该执行,以避免持久数据膨胀。 + +:::note +一般来说,外部消息以一个 256 位签名(如果需要)、一个 32 位 `req-seqno`(如果需要)、一个 32 位 `expire-at`(如果需要),以及可能的 32 位 `op` 和其他根据 `op` 所需的参数开始。外部消息的布局不需要像内部消息那样标准化,因为外部消息不用于不同(由不同开发者编写和不同所有者管理)智能合约之间的互动。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/internal-messages.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/internal-messages.md new file mode 100644 index 0000000000..06af641c52 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/internal-messages.md @@ -0,0 +1,107 @@ +# 内部消息 + +## 概览 + +智能合约通过发送所谓的**内部消息**来相互交互。当内部消息到达其预定目的地时,会代表目的地账户创建一个普通交易,并按照该账户(智能合约)的代码和持久数据的所指定的去处理内部消息。 + +:::info +特别地,这个处理交易可以创建一个或多个出站内部消息,其中一些可能被发送到正在处理内部消息的来源地址。这可以用于创建简单的“客户端-服务器应用程序”,当查询被封装在一个内部消息中并发送到另一个智能合约,该智能合约处理查询并再次作为内部消息发送响应。 +::: + +这种方法导致需要区分内部消息是作为“查询”、“响应”,还是不需要任何额外处理的(如“简单的资金转移”)。此外,当收到响应时,必须有办法理解它对应于哪个查询。 + +为了实现这一目标,可以使用以下方法进行内部消息布局(注意TON区块链不对消息体施加任何限制,因此这些确实只是建议)。 + +### 内部消息结构 + +消息体可以嵌入到消息本身中,或者存储在消息引用的单独cell中,如TL-B方案片段所示: + +```tlb +message$_ {X:Type} ... body:(Either X ^X) = Message X; +``` + +接收智能合约应至少接受嵌入消息体的内部消息(只要它们适合包含消息的 cell)。如果它接受单独cell中的消息体(使用`(Either X ^X)`的`right`构造函数),那么入站消息的处理不应依赖于消息体的特定嵌入选项。另一方面,对于更简单的查询和响应来说,完全可以不支持单独cell中的消息体。 + +### 内部消息体 + +消息体通常以以下字段开始: + +``` +* A 32-bit (big-endian) unsigned integer `op`, identifying the `operation` to be performed, or the `method` of the smart contract to be invoked. +* A 64-bit (big-endian) unsigned integer `query_id`, used in all query-response internal messages to indicate that a response is related to a query (the `query_id` of a response must be equal to the `query_id` of the corresponding query). If `op` is not a query-response method (e.g., it invokes a method that is not expected to send an answer), then `query_id` may be omitted. +* The remainder of the message body is specific for each supported value of `op`. +``` + +### 带评论的简单消息 + +如果`op`为零,则消息是一个“带评论的简单转移消息”。评论包含在消息体的其余部分中(没有任何`query_id`字段,即从第五个字节开始)。如果它不是以字节`0xff`开头的,则评论是一个文本评论;它可以“原样”显示给钱包的最终用户(在过滤掉无效和控制字符并检查它是一个有效的UTF-8字符串之后)。 + +当评论足够长,以至于不适合在一个cell中,不适合的行尾被放置在cell的第一个引用中。这个过程递归地继续,来描述不适合在两个或更多cell中的评论: + +``` +root_cell("0x00000000" - 32 bit, "string" up to 123 bytes) + ↳1st_ref("string continuation" up to 127 bytes) + ↳1st_ref("string continuation" up to 127 bytes) + ↳.... +``` + +同样的格式用于NFT和[jetton](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#forward_payload-format)转移的评论。 + +例如,用户可以在此文本字段中指明从他们的钱包向另一个用户的钱包进行简单转移的目的。另一方面,如果评论以字节`0xff`开头,则其余部分是一个“二进制评论”,不应将其作为文本显示给最终用户(如有必要,只能作为十六进制转储显示)。"二进制评论"的预期用途是,例如,包含商店支付中的购买标识符,由商店的软件自动生成和处理。 + +大多数智能合约在收到“简单转移消息”时不应执行非平凡的操作或拒绝入站消息。这样,一旦发现`op`为零,用于处理入站内部消息的智能合约函数(通常称为`recv_internal()`)应立即终止,并显示exit code 0,表示成功终止(例如,如果智能合约没有安装自定义异常处理程序,则会抛出异常`0`)。这将导致接收账户被记入消息转移的价,没有任何进一步的影响。 + +### 带加密评论的消息 + +如果`op`是`0x2167da4b`,那么消息是一个“带加密评论的转移消息”。此消息的序列化方式如下: + +输入: + +- `pub_1`和`priv_1` - 发送者的Ed25519公钥和私钥,各32字节。 +- `pub_2` - 接收者的Ed25519公钥,32字节。 +- `msg` - 要加密的消息,任意字节字符串。`len(msg) <= 960`。 + +加密算法如下: + +1. 使用`priv_1`和`pub_2`计算`shared_secret`。 +2. 让`salt`是发送者钱包地址的[bas64url表示](https://docs.ton.org/learn/overviews/addresses#user-friendly-address),`isBounceable=1`和`isTestnetOnly=0`。 +3. 选择长度在16到31之间的字节字符串`prefix`,使得`len(prefix+msg)`可以被16整除。`prefix`的第一个字节等于`len(prefix)`,其它字节是随机的。让`data = prefix + msg`。 +4. 让`msg_key`是`hmac_sha512(salt, data)`的前16字节。 +5. 计算`x = hmac_sha512(shared_secret, msg_key)`。让`key=x[0:32]`和`iv=x[32:48]`。 +6. 使用AES-256在CBC模式下,`key`和`iv`加密`data`。 +7. 构造加密评论: + 1. `pub_xor = pub_1 ^ pub_2` - 32字节。这允许每一方在不查询对方公钥的情况下解密消息。 + 2. `msg_key` - 16字节。 + 3. 加密的`data`。 +8. 消息体以4字节标签`0x2167da4b`开始。然后存储这个加密评论: + 1. 字节字符串被分成段,并存储在一系列cell`c_1,...,c_k`中(`c_1`是消息体的根)。每个cell(除了最后一个)都有一个对下一个的引用。 + 2. `c_1`包含多达35字节(不包括4字节标签),其他所有cell包含多达127字节。 + 3. 这种格式有以下限制:`k <= 16`,最大字符串长度为1024。 + +同样的格式用于NFT和jetton转移的评论,注意应使用发送者地址和接收者地址(不是jetton-钱包地址)的公钥。 + +:::info +Learn from examples of the message encryption algorithm: + +- [encryption.js](https://github.com/toncenter/ton-wallet/blob/master/src/js/util/encryption.js) +- [SimpleEncryption.cpp](https://github.com/ton-blockchain/ton/blob/master/tonlib/tonlib/keys/SimpleEncryption.cpp) + ::: + +### 不带评论的简单转移消息 + +“不带评论的简单转移消息”具有空的body(甚至没有`op`字段)。上述考虑也适用于此类消息。注意,此类消息应将其body嵌入到消息cell中。 + +### 区分查询和响应消息 + +我们期望“查询”消息具有高位清零的`op`,即在范围`1 .. 2^31-1`内,而“响应”消息具有设置了高位的`op`,即在范围`2^31 .. 2^32-1`内。如果方法既不是查询也不是响应(因此相应的消息体不包含`query_id`字段),则应使用“查询”范围内的`op`,即`1 .. 2^31 - 1`。 + +### 处理标准响应消息 + +有一些带有`op`等于`0xffffffff`和`0xfffffffe`的“标准”响应消息。一般来说,`op`的值从`0xfffffff0`到`0xffffffff`是为这类标准响应保留的。 + +``` +* `op` = `0xffffffff` means "operation not supported". It is followed by the 64-bit `query_id` extracted from the original query, and the 32-bit `op` of the original query. All but the simplest smart contracts should return this error when they receive a query with an unknown `op` in the range `1 .. 2^31-1`. +* `op` = `0xfffffffe` means "operation not allowed". It is followed by the 64-bit `query_id` of the original query, followed by the 32-bit `op` extracted from the original query. +``` + +请注意,未知的“响应”(其`op`在范围`2^31 .. 2^32-1`内)应被忽略(特别是,不应生成`op`等于`0xffffffff`的响应来回应它们),就像意外的弹回消息(带有“弹回”标志位设置)一样。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/messages-and-transactions.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/messages-and-transactions.mdx new file mode 100644 index 0000000000..c32e8d00b6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/messages-and-transactions.mdx @@ -0,0 +1,158 @@ +import ConceptImage from '@site/src/components/conceptImage'; +import ThemedImage from '@theme/ThemedImage'; + +# 消息概览 + +TON 是一个异步区块链,其结构与其他区块链非常不同。因此,新开发者经常对 TON 中的低级事物有疑问。在本文中,我们将探讨与消息传递相关的一个问题。 + +## 什么是消息? + +消息是在actor(用户、应用程序、智能合约)之间发送的数据包。它通常包含指导接收方执行某种操作的信息,如更新存储或发送新消息。 + +

+
+ +
+

+ +这种通信方式让人想起将卫星发射到太空。我们知道我们构成的消息,但在发射后,需要进行单独的观察来找出我们将获得什么结果。 + +## 什么是交易? + +TON 中的一笔交易包括以下内容: + +- 最初触发合约的传入消息(存在特殊的触发方式) +- 由传入消息引起的合约行动,例如更新合约的存储(可选) +- 发送给其他参与者的所生成的传出消息(可选) + +> 技术上,合约可以通过特殊功能如 [Tick-Tock](/develop/data-formats/transaction-layout#tick-tock) 触发,但这个功能更多用于 TON 内部的区块链核心合约。 +> +> 并非每笔交易都会导致传出消息或对合约存储的更新——这取决于合约代码所定义的操作。 + +

+ +

+ +如果我们看以太坊或几乎任何其他同步区块链,每笔交易可以包含几个智能合约调用。例如,如果选定的交易对没有流动性,DEX可以在一笔交易中执行多次交换。 + +在异步系统中,您无法在同一笔交易中从目标智能合约获得响应。合约调用可能需要几个区块来处理,具体取决于来源和目的地之间的路由长度。 + +为了实现无限分片范式,必须确保完全并行化,这意味着每笔交易的执行都独立于其他交易。因此,与其进行影响和改变多个合约状态的交易,不如每笔 TON 交易只在单个智能合约上执行,智能合约通过消息进行通信。这样,智能合约只能通过调用它们的函数与特殊消息相互作用,并稍后通过其他消息获得响应。 + +:::info +在 [交易布局](/develop/data-formats/transaction-layout) 页面上有更详细和准确的描述。 +::: + +## 什么是逻辑时间? + +在这样一个异步和并行智能合约调用的系统中,定义处理操作顺序可能很难。这就是为什么 TON 中的每个消息都有它的 _逻辑时间_ 或 _Lamport time_(后面简称 _lt_)。它用于理解哪个事件引发了另一个以及验证者首先需要处理什么。 + +严格保证由消息产生的交易将具有大于消息的 _lt_ 的 _lt_。同样,某笔交易中发送的消息的 _lt_ 严格大于引起它的交易的 _lt_。此外,从一个账户发送的消息和在一个账户上发生的交易也是严格有序的。 + +

+ +

+ +对于图像中的情况,结果是:`in_msg_lt < tx0_lt < out_msg_lt` + +由此,对于每个账户,我们总是知道交易、接收消息和发送消息的顺序。 + +此外,如果账户 _A_ 向账户 _B_ 发送了两条消息,可以保证具有较低 _lt_ 的消息将被更早处理: + +如果 `msg1_lt < msg2_lt` => `tx1_lt < tx2_lt`。 + +

+
+ +
+

+ +否则,尝试同步交付将需要在处理一个分片之前知道所有其他的状态,从而破坏了并行并破坏有效的分片。 + +对于每个区块,我们可以将 _lt_ 范围定义为从第一笔交易开始,到区块中最后一个事件(消息或交易)的 _lt_ 结束。区块的排序方式与 TON 中的其他事件相同,因此如果一个区块依赖于另一个区块,它具有更高的 _lt_。一个分片中的子区块的 _lt_ 高于其父区块。一个主链区块的 _lt_ 高于它所列出的分片区块的 _lts_,因为主区块依赖于所列分片区块。每个分片区块包含对创建分片区块时最新(创建分片区块时)主区块的有序引用,因此分片区块的 _lt_ 高于引用的主区块的 _lt_。 + +## 消息传递 + +幸运的是,TON 的工作方式是任何内部消息都一定会被目标账户接收。消息不会在来源和目的地之间的任何地方丢失。外部消息有点不同,因为它们被接受到区块中是由验证者自行决定的,但是,一旦消息被接受进入传入消息队列,它将被传递。 + +### 传递顺序 + +因此,看起来 _lt_ 解决了消息传递顺序的问题,因为我们知道具有较低 _lt_ 的交易将首先被处理。但这并不适用于每个场景。 + +假设有两个合约 - _A_ 和 _B_。_A_ 收到一个外部消息,触发它向 _B_ 发送两个内部消息,我们称这些消息为 _1_ 和 _2_。在这个简单的情况下,我们可以 100% 确定 _1_ 将在 _2_ 之前被 _B_ 处理,因为它具有较低的 _lt_。 + + + +但这只是一个简单的案例,当我们只有两个合约时。我们的系统在更复杂的情况下是如何工作的? + +### 多个智能合约 + +假设我们有三个合约 - _A_、_B_ 和 _C_。在一笔交易中,_A_ 发送两个内部消息 _1_ 和 _2_:一个给 _B_,另一个给 _C_。尽管它们是按确切顺序创建的(_1_,然后是 _2_),但我们无法确定 _1_ 将在 _2_ 之前被处理。这是因为从 _A_ 到 _B_ 和从 _A_ 到 _C_ 的路由可能在长度和验证者集中有所不同。如果这些合约位于不同的分片链中,其中一条消息可能需要几个区块才能到达目标合约。 + +为了更清晰,假设我们的合约在 `msg1` 和 `msg2` 由 `B` 和 `C` 合约执行后发送回消息 `msg1'` 和 `msg2'`。结果将在合约 `A` 上实现 `tx2'` 和 `tx1'`。我们有两种可能的交易路径, + +1. 第一种可能的顺序是 `tx1'_lt < tx2'_lt`: + +

+
+ +
+

+ +2. 第二种可能的顺序是 `tx2'_lt < tx1'_lt`: + +

+
+ +
+

+ +同样,当两个合约 _B_ 和 _C_ 向一个合约 _A_ 发送消息时,情况也是如此。即使 `B -> A` 的消息在 `C -> A` 之前发送,我们也无法知道哪一个将先被送达。`B -> A` 的路由可能需要更多的分片链转运。 + + + +在多个智能合约互动的许多可能的场景中,消息传递顺序可能是任意的。唯一的保证是,来自任何合约 _A_ 到任何合约 _B_ 的消息将按照它们的逻辑时间顺序处理。下面是一些示例。 + + + + + +## 结论 + +TON 区块链的异步结构为消息传递保证带来挑战。逻辑时间有助于确定事件和交易顺序,但由于分片链中的路由不同,它并不能保证多个智能合约之间的消息传递顺序。尽管存在这些复杂性,TON 仍然能够确保内部消息的传递,维护网络的可靠性。开发人员必须适应这些细微差别,以充分利用 TON 的潜力构建创新的去中心化应用程序。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/non-bounceable-messages.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/non-bounceable-messages.md new file mode 100644 index 0000000000..6a64a30cf1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/non-bounceable-messages.md @@ -0,0 +1,25 @@ +# 非弹回消息 + +export const Highlight = ({children, color}) => ( + +{children} + +); + +几乎所有在智能合约之间发送的内部消息都应该是可弹回的,即应该设置它们的“bounce”位。然后,如果目标智能合约不存在,或者在处理此消息时抛出未处理的异常,消息将被“bounced”,携带原始值的剩余部分(减去所有消息传输和gas费用)。弹回消息的主体将包含32位的`0xffffffff`,紧接着是原始消息的256位,但是“bounce”标志位被清除,“bounced”标志位被设置。因此,所有智能合约都应检查所有入站消息的“bounced”标志,并且要么默默接受它们(通过立即以exit code 0终止),要么执行一些特殊处理来检测哪个出站查询失败了。弹回消息主体中包含的查询永远不应执行。 + +:::info +弹回消息主体中包含的查询永远不应执行。 +::: + +在某些情况下,必须使用`不可弹回内部消息`。例如,没有发送至少一条不可弹回内部消息给它们,就无法创建新账户。除非这条消息包含一个带有新智能合约的代码和数据的`StateInit`,否则在不可弹回内部消息中拥有非空主体是没有意义的。 + +:::tip +不允许最终用户(例如,钱包的用户)发送包含大量价值(例如,超过五个Toncoin)的不可弹回消息是一个好主意,或者如果他们这样做了就警告他们。更好的做法是先发送少量金额,接着初始化新的智能合约,然后再发送更大的金额。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/sending-messages.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/sending-messages.md new file mode 100644 index 0000000000..11a5ea5c23 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/message-management/sending-messages.md @@ -0,0 +1,208 @@ +# 发送消息 + +消息的组成、解析和发送位于[TL-B schemas](/develop/data-formats/tl-b-language)、[交易阶段和TVM](/learn/tvm-instructions/tvm-overview)的交汇处。 + +事实上,FunC有[send_raw_message](/develop/func/stdlib#send_raw_message)函数,该函数期望一个序列化消息作为参数。 + +由于TON是一个功能广泛的系统,支持所有这些功能的消息可能看起来相当复杂。尽管如此,大多数情况下并不使用那么多功能,消息序列化在大多数情况下可以简化为: + +```func + cell msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_slice(message_body) + .end_cell(); +``` + +因此,开发者不用担忧,如果这份文档中的某些内容在第一次阅读时看起来难以理解,没有关系。只需把握总体思路即可。 + +有时文档中可能会提到\*\*'gram'**这个词,但大多是在代码示例中,它只是**toncoin\*\*的一个过时名称。 + +让我们深入了解! + +## 消息类型 + +有三种类型的消息: + +- 外部消息 — 从区块链外部发送到区块链内部智能合约的消息。这类消息应该在所谓的`credit_gas`阶段被智能合约明确接受。如果消息未被接受,节点不应该将其纳入进区块或转发给其他节点。 +- 内部消息 — 从一个区块链实体发送到另一个区块链实体的消息。与外部消息不同,这类消息可以携带一些TON并为自己支付费用。接收此类消息的智能合约可能没有接受它,在这种情况下,消息价值中的gas将被扣除。 +- 日志 — 从区块链实体发送到外部世界的消息。一般来说,没有将这类消息发送出区块链的机制。实际上,尽管网络中的所有节点对是否创建了消息达成共识,但没有关于如何处理它们的规则。日志可能被直接发送到`/dev/null`,记录到磁盘,保存到索引数据库,甚至通过非区块链手段(电子邮件/Telegram/短信)发送,所有这些都取决于给定节点的自行决定。 + +## 消息布局 + +我们将从内部消息布局开始。 + +描述智能合约可以发送的消息的TL-B方案如下: + +```tlb +message$_ {X:Type} info:CommonMsgInfoRelaxed + init:(Maybe (Either StateInit ^StateInit)) + body:(Either X ^X) = MessageRelaxed X; +``` + +让我们用语言来描述。任何消息的序列化都包括三个字段:info(某种标题,描述来源、目的地和其他元数据)、init(仅在消息初始化时需要的字段)和body(消息有效载荷)。 + +`Maybe`、`Either`和其他类型的表达式意味着以下内容: + +- 当我们有字段`info:CommonMsgInfoRelaxed`时,意味着`CommonMsgInfoRelaxed`的序列化直接注入到序列化cell中。 +- 当我们有字段`body:(Either X ^X)`时,意味着当我们(反)序列化某种类型`X`时,我们首先放置一个`either`位,如果`X`被序列化到同一cell,则为`0`,如果它被序列化到单独的cell,则为`1`。 +- 当我们有字段`init:(Maybe (Either StateInit ^StateInit))`时,意味着我们首先放置`0`或`1`,要取决于这个字段是否为空;如果不为空,我们序列化`Either StateInit ^StateInit`(再次,放置一个`either`位,如果`StateInit`被序列化到同一cell则为`0`,如果被序列化到单独的cell则为`1`)。 + +`CommonMsgInfoRelaxed`的布局如下: + +```tlb +int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + +ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; +``` + +让我们现在专注于`int_msg_info`。 +它以1位的前缀`0`开始,然后有三个1位的标志位,分别表示是否禁用即时超立方路由(目前始终为真)、是否在处理过程中出错时弹回消息,以及消息本身是否是弹回的结果。然后序列化来源和目的地址,接着是消息值和四个与消息转发费用和时间有关的整数。 + +如果消息是从智能合约发送的,其中一些字段将被重写为正确的值。特别是,验证者将重写`bounced`、`src`、`ihr_fee`、`fwd_fee`、`created_lt`和`created_at`。这意味着两件事:首先,另一个智能合约在处理消息时可以信任这些字段(发送者无法伪造来源地址、`bounced`标志位等);其次,在序列化时我们可以将任何有效值放入这些字段中(无论如何这些值都将被重写)。 + +消息的直接序列化如下所示: + +```func + var msg = begin_cell() + .store_uint(0, 1) ;; tag + .store_uint(1, 1) ;; ihr_disabled + .store_uint(1, 1) ;; allow bounces + .store_uint(0, 1) ;; not bounced itself + .store_slice(source) + .store_slice(destination) + ;; serialize CurrencyCollection (see below) + .store_coins(amount) + .store_dict(extra_currencies) + .store_coins(0) ;; ihr_fee + .store_coins(fwd_value) ;; fwd_fee + .store_uint(cur_lt(), 64) ;; lt of transaction + .store_uint(now(), 32) ;; unixtime of transaction + .store_uint(0, 1) ;; no init-field flag (Maybe) + .store_uint(0, 1) ;; inplace message body flag (Either) + .store_slice(msg_body) + .end_cell(); +``` + +然而,开发者通常使用快捷方式而不是逐步序列化所有字段。因此,让我们考虑如何使用[elector-code](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/elector-code.fc#L153)中的示例从智能合约发送消息。 + +```func +() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref { + ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(addr) + .store_coins(grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(ans_tag, 32) + .store_uint(query_id, 64); + if (body >= 0) { + msg~store_uint(body, 32); + } + send_raw_message(msg.end_cell(), mode); +} +``` + +首先,它将`0x18`值放入6位,即放入`0b011000`。这是什么? + +- 第一位是`0` — 1位前缀,表示它是`int_msg_info`。 + +- 然后有3位`1`、`1`和`0`,表示即时超立方路由被禁用,消息可以在处理过程中出错时回弹,消息本身不是回弹的结果。 + +- 然后应该是发送者地址,但由于它无论如何都会被重写,因此可以存储任何有效地址。最短的有效地址序列化是`addr_none`的序列化,它序列化为两位字符串`00`。 + +因此,`.store_uint(0x18, 6)`是序列化标签和前4个字段的优化后的方式。 + +下一行序列化目的地址。 + +然后我们应该序列化值。一般来说,消息值是一个`CurrencyCollection`对象,其方案如下: + +```tlb +nanograms$_ amount:(VarUInteger 16) = Grams; + +extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) + = ExtraCurrencyCollection; + +currencies$_ grams:Grams other:ExtraCurrencyCollection + = CurrencyCollection; +``` + +这个方案意味着除了TON值之外,消息可能还携带了extra-currencies的字典。然而,目前我们可以忽略它,只假设消息值被序列化为“作为变量整数的nanotons数量”和“`0` - 空字典位”。 + +事实上,在上面的选举人代码中,我们通过`.store_coins(toncoins)`序列化代币数量,但接着只放置了长度等于`1 + 4 + 4 + 64 + 32 + 1 + 1`的零字符串。这代表着什么? + +- 第一个位表示空的extra-currencies字典。 +- 然后我们有两个长度为4位的字段。它们以`VarUInteger 16`编码为0。事实上,由于`ihr_fee`和`fwd_fee`将被重写,我们同样可以在那里放置零。 +- 然后我们将零放入`created_lt`和`created_at`字段。这些字段也将被重写;然而,与费用不同,这些字段有固定长度,因此被编码为64位和32位长的字符串。 +- *(我们已经序列化了消息头并传递到init/body)* +- 接下来的零位表示没有`init`字段。 +- 最后一个零位表示消息体将就地序列化。 +- 之后,消息体(具有任意布局)就完成了编码。 + +这样,我们执行了4个序列化原语,而不是单独序列化了14个参数。 + +## 完整方案 + +消息布局和所有构成字段的完整方案(以及TON中所有对象的方案)在[block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb)中呈现。 + +## 消息大小 + +:::info cell大小 +请注意,任何[Cell](/learn/overviews/cells)最多可包含`1023`位。如果您需要存储更多数据,您应该将其分割成块并存储在引用cell中。 +::: + +例如,如果您的消息体大小为900位长,您无法将其存储在与消息头相同的cell中。 +实际上,除了消息头字段外,cell的总大小将超过1023位,在序列化过程中将出现`cell溢出`异常。在这种情况下,原本代表“就地消息体标志位(Either)”的`0`应该变成`1`,消息体应该存储在引用cell中。 + +由于某些字段具有可变大小,因此应小心处理这些事项。 + +例如,`MsgAddress`可以由四个构造器表示:`addr_none`、`addr_std`、`addr_extern`、`addr_var`,长度从2位(对于`addr_none`)到586位(对于最大形式的`addr_var`)。nanotons的数量也是如此,它被序列化为`VarUInteger 16`。这意味着,4位指示整数的字节长度,然后指示整数本身的较前面的字节。这样,0 nanotons将被序列化为`0b0000`(4位编码着零字节长度字符串,然后是零字节),而100,000,000 TON(或100000000000000000 nanotons)将被序列化为`0b10000000000101100011010001010111100001011101100010100000000000000000`(`0b1000`表示8个字节长度,然后是8个字节其本身)。 + +:::info 消息大小 + +更多配置参数及其值可在 [这里](/develop/howto/blockchain-configs#param-43) 找到。 +::: + +## 消息模式 + +如您可能已经注意到,我们使用`send_raw_message`发送消息,除了消耗消息本身外,还接受mode(模式)。要了解最适合您需求的模式,请查看以下表格: + +| Mode | 描述 | +| :---- | :---------------------------- | +| `0` | 普通消息 | +| `64` | 除了新消息中最初指示的值之外,携带来自入站消息的所有剩余值 | +| `128` | 携带当前智能合约的所有余额,而不是消息中最初指示的值 | + +| Flag | 描述 | +| :---- | :--------------------------------------- | +| `+1` | 单独支付转账费用 | +| `+2` | 忽略在 Action Phase 处理该信息时出现的一些错误(请查看下面的注释) | +| `+16` | 在action失败的情况下 - 弹回交易。如果使用了`+2`则无效。 | +| `+32` | 如果当前账户的结果余额为零,则必须销毁该账户(通常与Mode 128一起使用) | + +:::info +2 flag + +1. Toncoins 不足: + - 没有足够的值与消息一起传送(所有入站消息值都已消耗)。 + - 没有足够的资金来处理消息。 + - 没有足够的信息附加值来支付转发费用。 + - 没有足够的额外货币与消息一起发送。 + - 没有足够的资金支付出站外部消息。 +2. 消息过大(请查看 [消息大小](messages#message-size) 以获取更多信息)。 +3. 消息的 Merkle 深度太大。 + +但在以下情况下,它不会忽略错误: + +1. 消息的格式无效。 +2. 消息模式包括 64 和128 modes。 +3. 出站消息在 StateInit 中有无效的库。 +4. 外部消息不是普通消息,或包含 +16 或 +32 标志,或两者兼有。 + ::: + +要为`send_raw_message`构建一个模式,您只需通过将Mode和Flag结合来组合它们。例如,如果您想发送常规消息并单独支付转账费用,请使用Mode`0`和Flag`+1`得到`mode = 1`。如果您想发送整个合约余额并立即销毁它,请使用Mode`128`和Flag`+32`得到`mode = 160`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/infinity-sharding-paradigm.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/infinity-sharding-paradigm.mdx new file mode 100644 index 0000000000..8e932577eb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/infinity-sharding-paradigm.mdx @@ -0,0 +1,68 @@ +# 无限分片范式 + +## 理解TON区块链中的拆分合并 + +TON(Telegram Open Network)区块链引入了一些创新概念来提高区块链的可扩展性和效率。其中一个概念是拆分合并功能,这是其区块链架构的一个组成部分。本短文探讨了TON区块链中拆分合并的关键方面,重点讨论其在无限分片范式(ISP,Infinity Sharding Paradigm)中的作用。 + +#### 无限分片范式(ISP)及其应用 + +ISP是TON区块链设计的基础,将每个账户视为其独立的“账户链”的一部分。这些账户链然后被聚合到分片链块中以提高效率。一个分片链的状态包括其所有账户链的状态。因此,一个分片链区块本质上是一系列分配给它的虚拟账户区块的集合。 + +- **ShardState**:近似表示为 Hashmap(n, AccountState),其中n是account_id的位长度。 +- **ShardBlock**:近似表示为 Hashmap(n, AccountBlock)。 + +每个分片链,或更准确地说,每个分片链区块,由`workchain_id`和账户id的二进制前缀`s`的组合来标识。 + +## 分片示例 + +![](/img/docs/blockchain-fundamentals/split-merge.svg) + +在提供的图形方案中: + +- 黑线代表主链。 +- 工作链的分片按时间划分,用黑色虚线表示。 +- 区块101、102、103和80与序号为29的主链块相关。这里,101、102和103在一个分片中,而80在另一个分片中。 +- 如果发生拆分或合并事件,受影响的分片会暂停,直到下一个主链块。 + +总之,TON区块链中的拆分合并是一个复杂但高效的机制,增强了区块链网络的可扩展性和交互性。它体现了TON解决常见区块链挑战的方法,强调效率和全局一致性。 + +## 分片细节 + +#### 分片链的拆分和非拆分部分 + +一个分片链块和状态分为两部分: + +1. **拆分部分**:符合ISP形式,包含特定于账户的数据。 +2. **非拆分部分**:涉及区块与其他区块和外部世界的交互相关的数据。 + +#### 与其他块的交互 + +非拆分部分对于确保全局一致性至关重要,简化为内部和外部的局部一致性条件。它们对以下方面非常重要: + +- 分片链之间的消息转发。 +- 涉及多个分片链的交易。 +- 交付保证和验证,关于区块的初始状态与其前一个区块的一致性。 + +#### 入站和出站消息 + +分片链区块的非拆分部分的关键组成部分包括: + +- **InMsgDescr**:导入到区块中的所有消息的描述。 +- **OutMsgDescr**:区块导出或生成的所有消息的描述。 + +#### 区块头和验证者签名 + +区块头,另一个非拆分组件,包含工作链id、账户id的二进制前缀和各种哈希(例如,前一个的哈希)等重要信息。验证者签名被附加到未签名的区块上,形成签名区块。 + +#### 出站消息队列 + +分片链状态中的OutMsgQueue是一个关键的非拆分部分。它保存OutMsgDescr中的未处理消息,直到它们被处理或发送到目的地。 + +#### 分片的拆分和合并机制 + +在动态分片的背景下,分片配置可能因拆分和合并事件而变化。这些事件与主链区块同步。例如,如果发生拆分或合并,受影响的分片会等待下一个主链区块之后再继续。 + +## 参阅 + +* [区块布局](/develop/data-formats/block-layout) +* [白皮书](/learn/docs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/shards-intro.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/shards-intro.mdx new file mode 100644 index 0000000000..1c3c46c029 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/shards/shards-intro.mdx @@ -0,0 +1,33 @@ +# 分片 + +:::warning +页面正在开发中 +::: + +分片是TON区块链中的一种机制,它允许处理大量交易。TON中分片的主要思想是,当账户A向账户B发送消息,同时账户C向账户D发送消息时,这两个操作都可以异步进行。 + +默认情况下,在基本链(`workchain=0`)中只有一个分片,其分片号为 `0x8000000000000000`(或二进制表示为 `1000000000000000000000000000000000000000000000000000000000000000`)。主链(`workchain=-1`)始终只有一个分片。 + +## 拆分 + +每个分片负责一些具有某些共同二进制前缀的账户子集。这个前缀出现在分片ID中,由一个64位整数表示,其结构为:`<二进制前缀>100000...`。例如,ID为 `1011100000...` 的分片包含所有以前缀 `1011` 开头的账户。 + +当某个分片中的交易数量增长时,它会分裂成两个分片。新分片获得以下ID:`<父前缀>01000...` 和 `<父前缀>11000...`,分别负责以 `<父前缀>0` 和 `<父前缀>1` 开头的账户。分片中的区块序列号从父区块的最后一个序列号加1开始连续。拆分后,分片独立进行,可能会有不同的序列号。 + +主链区块在其头部包含分片信息。分片区块出现在主链头部后,可以认为是完成的(不能回滚)。 + +示例: + +- 主链块 `seqno=34607821` 有2个分片:`(0,4000000000000000,40485798)` 和 `(0,c000000000000000,40485843)` (https://toncenter.com/api/v2/shards?seqno=34607821)。 +- 分片 `shard=4000000000000000` 拆分成 `shard=2000000000000000` 和 `shard=6000000000000000`,主链区块 `seqno=34607822` 已经有3个分片:`(0,2000000000000000,40485799)` 和 `(0,6000000000000000,40485799)`。注意,两个新分片具有相同的序列号,但不同的分片ID (https://toncenter.com/api/v2/shards?seqno=34607822)。 +- 新分片独立进行,经过100个主链区块(在主链块 `seqno=34607921` 中),一个分片有最后一个块 `(0,2000000000000000,40485901)`,另一个分片有 `(0,6000000000000000,40485897)` (https://toncenter.com/api/v2/shards?seqno=34607921)。 + +## 合并 + +当分片负载下降时,它们可以按如下方式合并回去: + +- 如果两个分片有共同的父分片,因此,它们的分片ID分别为 `<父前缀>010...` 和 `<父前缀110...`,则可以合并。合并后的分片将具有分片ID `<父前缀>10...`(例如 `10010...` + `10110...` = `1010...`)。合并分片的第一个区块将具有 `seqno=max(seqno1, seqno2) + 1`。 + +示例: + +- 在主链区块 `seqno=34626306` 中,五个分片中的两个,最后区块为 `(0,a000000000000000,40492030)` 和 `(0,e000000000000000,40492216)`,合并成一个区块为 `(0,c000000000000000,40492217)` 的分片 (https://toncenter.com/api/v2/shards?seqno=34626306 和 https://toncenter.com/api/v2/shards?seqno=34626307)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/accept-message-effects.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/accept-message-effects.md new file mode 100644 index 0000000000..8a7861a593 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/accept-message-effects.md @@ -0,0 +1,27 @@ +# 接受消息的影响 + +`accept_message` 和 `set_gas_limit` 在执行[stdlib参考](/develop/func/stdlib#accept_message)中所说的操作时,可能会产生一些不那么直接的影响。 + +## 外部消息 + +外部消息的处理流程如下: + +- `gas_limit`被设置为`gas_credit`(ConfigParam 20和ConfigParam 21),等于10k gas。 +- 在使用这些信用额度期间,合约应该调用`accept_message`来`set_gas_limit`,表明它准备支付消息处理费用。 +- 如果`gas_credit`被消耗完或计算结束,并且没有调用`accept_message`,消息将被完全丢弃(就好像它从未存在过一样)。 +- 否则,将设置一个新的gas限制,等于`contract_balance/gas_price`(在`accept_message`的情况下)或自定义数字(在`set_gas_limit`的情况下);在交易结束后,将从合约余额中扣除完整的计算费用(这样,`gas_credit`实际上是**信用**,而不是免费gas)。 + +请注意,如果在`accept_message`之后抛出某些错误(无论是在Compute Phase还是Action Phase),交易将被写入区块链,并且费用将从合约余额中扣除。然而,存储不会被更新,操作不会被应用,就像任何带有错误退出代码的交易一样。 + +因此,如果合约接受外部消息然后由于消息数据中的错误或发送错误序列化的消息而抛出异常,它将支付处理费用,但无法阻止消息重放。**同一消息将被合约反复接受,直到消耗完整个余额。** + +## 内部消息 + +默认情况下,当合约接收到内部消息时,gas限制被设置为`message_balance`/`gas_price`。换句话说,消息为其处理支付。通过使用`accept_message`/`set_gas_limit`,合约可以在执行期间更改gas限制。 + +请注意,手动设置的gas限制不会干扰弹回行为;如果消息以可弹回模式发送,并且包含足够的钱来支付其处理和创建弹回消息的费用,消息将被弹回。 + +:::info 示例 + +如果在同一示例中,计算成本为0.5(而不是0.005),则不会发生弹回(消息余额将是`0.1 - 0.5 - 0.001 = -0.401`,因此没有弹回),合约余额将是`1 + 0.1 - 0.5` = `0.6` TON。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees-low-level.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees-low-level.md new file mode 100644 index 0000000000..8fe682b054 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees-low-level.md @@ -0,0 +1,176 @@ +# 低层级费用概述 + +:::caution +本节描述了与TON进行低层级交互的说明和手册。 +::: + +本文档提供了TON上交易费用的一般概念,特别是对FunC代码的计算费用。还有一个[TVM白皮书中的详细规范](https://ton.org/tvm.pdf)。 + +## 交易及其阶段 + +如[TVM概述](/learn/tvm-instructions/tvm-overview)中所述,交易执行包括几个阶段(phase)。在这些阶段期间,可能会扣除相应的费用。 + +通常: +```cpp +transaction_fee = storage_fees + + in_fwd_fees + + computation_fees + + action_fees + + out_fwd_fees +``` +其中: + * `storage_fees`—与合约在链状态中占用空间相关的费用 + * `in_fwd_fees`—将传入消息导入区块链的费用(仅适用于之前未在链上的消息,即`external`消息。对于合约到合约的普通消息,此费用不适用) + * `computation_fees`—与执行TVM指令相关的费用 + * `action_fees`—与处理action列表(发送消息、设置库等)相关的费用 + * `out_fwd_fees`—与将出站消息导入区块链相关的费用 + +## 计算费用 + +### Gas +所有计算成本都以gas单位标明。gas单位的价格由链配置(主链的Config 20和基本链的Config 21)决定,只能通过验证者的共识更改。请注意,与其他系统不同,用户不能设置自己的gas价格,也没有费用市场。 + +基本链当前的设置如下:1个gas单位的成本是1000 nanotons。 + +## TVM 指令成本 +在最低层级(TVM指令执行),大多数原语的gas价格等于_基本gas价格_,计算为`P_b := 10 + b + 5r`,其中`b`是指令长度(以位为单位),`r`是包含在指令中的cell引用数。 + +除了这些基本费用外,还有以下费用: + +| 指令 | GAS价格 | 描述 | +|---------------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 创建cell | **500** | 将构建器转换为cell的操作。 | +| 首次解析cell | **100** | 在当前交易期间首次将cell转换为切片的操作。 | +| 重复解析cell | **25** | 在同一交易期间已解析过的cell转换为切片的操作。 | +| 抛出异常 | **50** | | +| 与元组操作 | **1** | 此价格将乘以元组元素的数量。 | +| 隐式跳转 | **10** | 当当前continuation cell中的所有指令执行时会进行支付。然而,如果该continuation cell中存在引用,并且执行流跳转到了第一个引用。 | +| 隐式回跳 | **5** | 当当前continuation中的所有指令执行完毕,并且执行流回跳到刚刚完成的continuation被调用的那个continuation时,将会进行支付。 | +| 移动堆栈元素 | **1** | 在continuations之间移动堆栈元素的价格。每个元素都将收取相应的gas价格。然而,前32个元素的移动是免费的。 | + + +## FunC 构造的 gas 费用 + +FunC中使用的几乎所有函数都在[stdlib.func](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc)中定义,它将FunC函数映射到Fift汇编指令。反过来,Fift汇编指令在[asm.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/fift/lib/Asm.fif)中映射到位序列指令。因此,如果你想了解指令调用的确切成本,你需要在`stdlib.func`中找到`asm`表示,然后在`asm.fif`中找到位序列并计算指令长度(以位为单位)。 + +然而,通常,与位长度相关的费用与cell解析和创建以及跳转和执行指令数量的费用相比是次要的。 + +因此,如果你试图优化你的代码,首先从架构优化开始,减少cell解析/创建操作的数量,然后减少跳转的数量。 + +### 与 cell 进行的操作 +一个关于如何通过适当的cell工作显著降低gas成本的示例。 + +假设你想在出站消息中添加一些编码的有效负载。直接实现将如下: +```cpp +slice payload_encoding(int a, int b, int c) { + return + begin_cell().store_uint(a,8) + .store_uint(b,8) + .store_uint(c,8) + .end_cell().begin_parse(); +} + +() send_message(slice destination) impure { + slice payload = payload_encoding(1, 7, 12); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(destination) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page) + .store_uint(0x33bbff77, 32) ;; op-code (see smart-contract guidelines) + .store_uint(cur_lt(), 64) ;; query_id (see smart-contract guidelines) + .store_slice(payload) + .end_cell(); + send_raw_message(msg, 64); +} +``` + +这段代码的问题是什么?`payload_encoding`为了生成切片位字符串,首先通过`end_cell()`创建一个cell(+500 gas单位)。然后解析它`begin_parse()`(+100 gas单位)。通过改变一些常用类型,可以不使用这些不必要的操作来重写相同的代码: + +```cpp +;; 我们为stdlib中不存在的函数添加asm,该函数将一个构建器存储到另一个构建器中 +builder store_builder(builder to, builder what) asm(what to) "STB"; + +builder payload_encoding(int a, int b, int c) { + return + begin_cell().store_uint(a,8) + .store_uint(b,8) + .store_uint(c,8); +} + +() send_message(slice destination) impure { + builder payload = payload_encoding(1, 7, 12); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(destination) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息头(见发送消息页面) + .store_uint(0x33bbff77, 32) ;; 操作码(见智能合约指南) + .store_uint(cur_lt(), 64) ;; query_id(见智能合约指南) + .store_builder(payload) + .end_cell(); + send_raw_message(msg, 64); +} +``` +通过以另一种形式(构建器而不是切片)传递位字符串,我们通过非常轻微的代码更改显著降低了计算成本。 + +### Inline和inline_refs +默认情况下,当你有一个FunC函数时,它会获得自己的`id`,存储在id->function字典的单独叶子中,当你在程序的某个地方调用它时,会在字典中搜索函数并随后跳转。如果函数从代码中的许多地方调用,这种行为是合理的,因为跳转允许减少代码大小(通过一次存储函数体)。然而,如果该函数只在一个或两个地方使用,通常更便宜的做法是将该函数声明为`inline`或`inline_ref`。`inline`修饰符将函数体直接放入父函数的代码中,而`inline_ref`将函数代码放入引用中(跳转到引用仍然比搜索和跳转到字典条目便宜得多)。 + +### 字典 +TON中的字典是作为cell的树(更准确地说是DAG)被引入的。这意味着如果你搜索、读取或写入字典,你需要解析树的相应分支的所有cell。这意味着 + * a) 字典操作的成本不是固定的(因为分支中节点的大小和数量取决于给定的字典和键) + * b) 优化字典使用是明智的,使用特殊指令如`replace`而不是`delete`和`add` + * c) 开发者应该注意迭代操作(如next和prev)以及`min_key`/`max_key`操作,以避免不必要地遍历整个字典 + +### 堆栈操作 +注意FunC在底层操作堆栈条目。这意味着代码: +```cpp +(int a, int b, int c) = some_f(); +return (c, b, a); +``` +将被翻译成几个指令,这些指令改变堆栈上元素的顺序。 + +当堆栈条目数量大(10+),并且它们以不同的顺序被积极使用时,堆栈操作费用可能变得不可忽视。 + +## 费用计算公式 + +### storage_fees +```cpp +storage_fees = ceil( + (account.bits * bit_price + + account.cells * cell_price) + * period / 2 ^ 16) +``` +### in_fwd_fees, out_fwd_fees + +```cpp +msg_fwd_fees = (lump_price + + ceil( + (bit_price * msg.bits + cell_price * msg.cells) / 2^16) + ) + +ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor) / 2^16) +``` +// 消息的根cell中的位不包括在msg.bits中(lump_price支付它们) + +### action_fees +```cpp +action_fees = sum(out_ext_msg_fwd_fee) + sum(int_msg_mine_fee) +``` + +### 配置文件 + +所有费用都以一定量的gas提名,并且可能会更改。配置文件表示当前费用成本。 + +* storage_fees = [p18](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam18) +* in_fwd_fees = [p24](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam24), [p25](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam25) +* computation_fees = [p20](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam20), [p21](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam21) +* action_fees = [p24](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam24), [p25](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam25) +* out_fwd_fees = [p24](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam24), [p25](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam25) + +:::info +[直接链接到主网配置文件](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05) +::: + +*2022年7月24日,基于@thedailyton [文章](https://telegra.ph/Fees-calculation-on-the-TON-Blockchain-07-24) * diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees.md new file mode 100644 index 0000000000..dcfc85ff14 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/fees.md @@ -0,0 +1,178 @@ +# 交易费用 + +每个TON用户都应该记住,_手续费取决于许多因素_。 + +## Gas + +所有费用都以Gas计算。这是TON中用作费用的特殊货币。 + +所有费用都以一定数量的gas来指定和固定,但gas价格本身并不固定。今天的gas价格为: + +```cpp +1 gas = 1000 nanotons = 0,000 001 TON +``` + +### 平均交易成本 + +> **简而言之:** 今天,每笔交易的成本约为 **~0.005 TON** + +即使TON价格上涨100倍,交易仍将非常便宜;不到$0.01。此外,如果验证者认为手续费变得昂贵,他们可以降低这个值[阅读他们为何感兴趣](#gas-changing-voting-process)。 + +:::info +当前的gas数量写在网络配置[参数20](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam20)中。 +::: + +### Gas 变更投票过程 + +像TON的许多其他参数一样,gas费用是可配置的,可以通过主网上的特殊投票来更改。 + +更改任何参数都需要获得66%的验证者投票。 + +#### Gas 的成本会更高吗? + +> *这是否意味着有一天gas价格可能会上涨1000倍甚至更多?* + +从技术上讲,是的;但实际上,不会。 + +验证者从处理交易中获得少量费用,收取更高的手续费将导致交易数量减少,使验证过程变得不那么有利。 + +### 如何计算费用? + +TON上的费用难以提前计算,因为它们的数量取决于交易运行时间、账户状态、消息内容和大小、区块链网络设置以及无法在交易发送之前计算的其他许多变量。阅读关于[计算费用](/develop/howto/fees-low-level#computation-fees)的低层级文章概述。 + +这就是为什么即使NFT市场通常会额外收取大约1 TON的TON,并在稍后返还(_`1 - transaction_fee`_)。 + +然而,让我们了解更多关于费用应该如何在TON上发挥作用。 + +## 基本费用公式 + +根据[低层级费用概述](/develop/howto/fees-low-level),TON上的费用按照以下公式计算: + +```cpp +transaction_fee = storage_fees + + in_fwd_fees + + computation_fees + + action_fees + + out_fwd_fees +``` + +## 交易费用的元素 + +* `storage_fees`是您为在区块链上存储智能合约而支付的金额。实际上,您支付的是智能合约在区块链上存储的每一秒钟。 + * _示例_:您的TON钱包也是一个智能合约,每次您接收或发送交易时都会支付存储费用。阅读更多关于[如何计算存储费用](/develop/smart-contracts/fees#storage-fee)。 +* `in_fwd_fees`是从区块链外部导入消息的费用。每次您进行交易时,都必须将其传送给将处理它的验证者。 + * _示例_:您使用的每个钱包应用程序(如Tonkeeper)进行的每笔交易都需要首先在验证节点之间分发。 +* `computation_fees`是您为在虚拟机中执行代码而支付的金额。代码越大,必须支付的费用就越多。 + * _示例_:每次您使用钱包(即智能合约)发送交易时,您都会执行钱包合约的代码并为此付费。 +* `action_fees`是智能合约发送外部消息所收取的费用。 +* `out_fwd_fees`代表从TON区块链发送消息到外部服务(例如,日志)和外部区块链的费用。 + * 由于尚未实施,因此目前未使用。因此目前等于0。 + +## 存储费 + +TON验证者从智能合约收取存储费用。 + +存储费用是在任何交易的**存储阶段**从智能合约余额中收取的。阅读更多关于阶段以及TVM如何工作的内容[在此](/learn/tvm-instructions/tvm-overview#transactions-and-phases)。 + +重要的是要记住,在TON上,您既要为智能合约的执行付费,也要为**使用的存储**付费: + +```cpp +bytes * second +``` + +这意味着您必须为拥有TON钱包支付存储费用(即使非常非常小)。 + +如果您在相当长的时间内(1年)没有使用您的TON钱包,_您将不得不支付比平常更大的手续费,因为钱包在发送和接收交易时支付手续费_。 + +### 公式 + +您可以使用以下公式大致计算智能合约的存储费用: + +```cpp + storage_fee = (cells_count * cell_price + bits_count * bit_price) + / 2^16 * time_delta +``` + +让我们更仔细地检查每个值: + +* `price` — 存储`time_delta`秒的价格 +* `cells_count` — 智能合约使用的cell数量 +* `bits_count` — 智能合约使用的位数 +* `cell_price` — 单个cell的价格 +* `bit_price` — 单个位的价格 + +`cell_price`和`bit_price`都可以从网络配置[参数18](https://explorer.toncoin.org/config?workchain=-1&shard=8000000000000000&seqno=22185244&roothash=165D55B3CFFC4043BFC43F81C1A3F2C41B69B33D6615D46FBFD2036256756382&filehash=69C43394D872B02C334B75F59464B2848CD4E23031C03CA7F3B1F98E8A13EE05#configparam18)中获得。 + +当前值为: + +* 工作链 + ```cpp + bit_price_ps:1 + cell_price_ps:500 + ``` +* 主链 + ```cpp + mc_bit_price_ps:1000 + mc_cell_price_ps:500000 + ``` + +### 计算器示例 + +您可以使用此JS脚本计算工作链中1 MB存储1年的存储价格 + +```js live + +// 欢迎使用LIVE编辑器! +// 随意更改任何变量 + +function storageFeeCalculator() { + + const size = 1024 * 1024 * 8 // 1MB的位 + const duration = 60 * 60 * 24 * 365 // 1年的秒数 + + const bit_price_ps = 1 + const cell_price_ps = 500 + + const pricePerSec = size * bit_price_ps + + + Math.ceil(size / 1023) * cell_price_ps + + let fee = (pricePerSec * duration / 2**16 * 10**-9) + let mb = (size / 1024 / 1024 / 8).toFixed(2) + let days = Math.floor(duration / (3600 * 24)) + + let str = `Storage Fee: ${fee} TON (${mb} MB for ${days} days)` + + return str +} + + +``` + +## 常见问题解答 + +这里是TON访客最常问的问题: + +### 发送 TON 的费用? + +发送任何数量的TON的平均费用为0.0055 TON。 + +### 发送 Jettons 的费用? + +发送任何数量的自定义Jettons的平均费用为0.037 TON。 + +### 铸造 NFT 的成本? + +铸造一个NFT的平均费用为0.08 TON。 + +### 在 TON 上保存数据的成本? + +在TON上保存1 MB数据一年的成本为6.01 TON。请注意,您通常不需要在链上存储大量数据。如果您需要去中心化存储,请考虑[TON Storage](/participate/ton-storage/storage-daemon)。 + +### 如何在 FunC 中计算费用? + +* [在FunC中计算转发费用的智能合约函数](https://github.com/ton-blockchain/token-contract/blob/main/misc/forward-fee-calc.fc) + +## 参考资料 + +* ["低层级费用概述"](/develop/howto/fees-low-level#fees-calculation-formulas) — 阅读有关计算佣金的公式。 +* *基于[@thedailyton文章](https://telegra.ph/Commissions-on-TON-07-22)最初由[menschee](https://github.com/menschee)撰写* diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/forward-fees.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/forward-fees.md new file mode 100644 index 0000000000..21d0135331 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/smart-contracts/transaction-fees/forward-fees.md @@ -0,0 +1,13 @@ +# 转发费用 + +一般来说,如果智能合约想向另一个智能合约发送查询,它应该支付发送内部消息到目标智能合约的费用(消息转发费),在目的地处理这条消息的费用(Gas费),以及在需要时发送回复的费用(消息转发费)。 + +:::note +在大多数情况下,发送者会附加少量的Toncoin(例如,一个Toncoin)到内部消息中(足以支付这条消息的处理费用),并设置其“弹回”标志(即,发送一个可弹回的内部消息);接收者将在回答中返回收到的剩余价值(从中扣除消息转发费用)。这通常是通过调用`SENDRAWMSG`并设置`mode = 64`来实现的(参见TON VM文档的附录A)。 +::: + +如果接收者无法解析收到的消息并以非零exit code终止(例如,因为未处理的cell反序列化异常),消息将自动“弹回”到其发送者,清除“bounce”标志位并设置“bounced”标志。被弹回消息的主体将包含32位的`0xffffffff`,紧接着是原始消息的256位。在智能合约中解析`op`字段并处理相应查询之前,检查传入内部消息的“bounced”标志很重要(否则有风险,包含在被弹回消息中的查询将被其原始发送者处理为新的单独查询)。如果设置了“bounced”标志,特殊代码可以找出哪个查询失败了(例如,通过从被弹回消息中反序列化`op`和`query_id`)并采取适当行动。一个更简单的智能合约可能只是忽略所有被弹回的消息(如果设置了“bounced”标志则以exit code 0终止)。注意,“bounced”标志在发送时被重写,因此不能伪造,并且可以安全地假设如果消息带有“bounced”标志,那么它是从接收者发送的某个消息的弹回结果。 + +另一方面,接收者可能成功解析了传入的查询,并发现所请求的方法`op`不受支持,或者遇到了另一个错误条件。然后应该发送一个`op`等于`0xffffffff`或其他适当值的响应,如上所述,使用`SENDRAWMSG`并设置`mode = 64`。 + +在某些情况下,发送者希望既传输一些值给接收者,又接收确认消息或错误消息。例如,验证者选举智能合约接收选举参与请求以及作为附加值的注资。在这种情况下,附加额外的Toncoin(例如,一个Toncoin)到预期价值上是有意义的。如果出现错误(例如,出于任何原因可能不接受注资),应该将收到的全部金额(扣除处理费用)连同错误消息一起返回给发送者(例如,使用`SENDRAWMSG`并设置`mode = 64`,如前所述)。在成功的情况下,创建确认消息并精确地退还一个Toncoin(从这个值中扣除消息传输费用;这是`SENDRAWMSG`的`mode = 1`)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/ton-documentation.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/ton-documentation.mdx new file mode 100644 index 0000000000..298026af5f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/ton-documentation.mdx @@ -0,0 +1,142 @@ +import Button from '@site/src/components/button' +import Player from '@site/src/components/player' + +# TON 文档 + +欢迎来到官方TON区块链开发文档! + +这个资源旨在为您提供在TON区块链上构建、测试和部署应用程序所需的所有必要信息。 + +这是一个协作的开源计划,我们始终欢迎贡献。所有文档都可以通过GitHub进行编辑,只需[遵循这些指示](/contribute)。 + +- _TON Hello World_ 系列提供了钱包、智能合约、小型应用程序以及在TON上测试和调试智能合约的详细逐步指南。 +- _开始使用TON_ 是一个逐步指导与TON区块链互动的指南。(包括视频教程) + + + + + +### TON 课程 + +我们自豪地推出 **TON 区块链课程**,这是一个全面的TON区块链指南。该课程是为希望学习如何在TON区块链上创建智能合约和去中心化应用程序的开发者设计的,课程以有趣和互动的方式进行。 + +它包含**9个模块**,涵盖了TON区块链的基础知识、FunC编程语言和TON虚拟机(TVM)。 + + + + + + + + + + + +## 开发模块 + +如果您刚开始接触TON区块链开发,建议您从头开始,并逐步学习这些主题。 + +### 基础概念 + +- [开放网络](/learn/introduction) - TON区块链的高层级概述。 +- [区块链之链](/learn/overviews/ton-blockchain) - 对TON区块链的深入浅出解释。 +- [智能合约地址](/learn/overviews/addresses) - 地址的详细解释。 +- [作为数据结构的cell](/learn/overviews/cells) - 数据结构的高层级解释。 +- [TON网络](/learn/networking/overview) - TON点对点协议的高层级概述。 +- [TON虚拟机(TVM)](/learn/tvm-instructions/tvm-overview) - TON虚拟机的高层级概述。 +- [交易和阶段](/learn/tvm-instructions/tvm-overview#transactions-and-phases) - 交易和阶段的详细解释。 +- [交易费用](/develop/smart-contracts/fees) - 交易费用的高层级解释。 + +### 基础设施 + +- [节点类型](/participate/nodes/node-types) - 节点类型的详细解释。 +- [运行完整节点](/participate/run-nodes/full-node) - 如何运行节点的详细解释。 +- [TON DNS和网站](/participate/web3/dns) - TON DNS和网站的详细解释。 +- [TON存储](/participate/ton-storage/storage-daemon) - TON存储的详细解释。 + +### 额外资源 + +- [**常见问题解答**](/develop/howto/faq) - 常见问题 +- [FunC文档](/develop/func/overview) +- [Fift文档](/develop/fift/overview) + +## 智能合约开发 + +智能合约是TON区块链上去中心化应用程序(DApps)的构建模块。如果您希望开发自己的dApps,那么了解智能合约的运作方式至关重要。 + + + + + +



+ +以下资源为TON智能合约开发提供了宝贵信息: + +- [TON Hello World: 逐步指导编写您的第一个智能合约](https://ton-community.github.io/tutorials/02-contract/) - 使用JS的基础的简易解释。 +- [如何使用钱包智能合约](/develop/smart-contracts/tutorials/wallet) - 使用JS和GO详细解释智能合约的基础知识。 +- [通过示例学习智能合约](/develop/smart-contracts/examples)(FunC,Fift) +- [速成TON](/develop/smart-contracts/examples) - 6个互动挑战以及逐步教程,帮助学习智能合约开发。 + +## DApp 开发 + +去中心化应用程序(DApps)是在点对点计算机网络上运行的应用程序,而不是单台计算机(TON区块链)。它们类似于传统的网络应用程序,但它们是建立在区块链网络之上的。这意味着DApps是去中心化的,即没有单一实体控制它们。 + + + +### DeFi 开发 + +- [TON Connect](/develop/dapps/ton-connect/overview) — DApps的集成和认证。 +- [链下支付处理](/develop/dapps/asset-processing) — 支付处理的示例和概念。 +- [TON Jetton处理](/develop/dapps/asset-processing/jettons) — Jettons处理的示例和概念。 +- [可替代(FT)/ 不可替代(NFT)代币](/develop/dapps/defi/tokens) — 相应的智能合约、示例和工具 + +通过全面的DApps构建指南迈出DApps开发的第一步: + +- [TON Hello World: 逐步指导构建您的第一个网络客户端](https://ton-community.github.io/tutorials/03-client/) +- [通过TON Connect集成Telegram机器人](/develop/dapps/ton-connect/tg-bot-integration) + +### API 和 SDK + +- [API](/v3/guidelines/dapps/apis-sdks/api-types) +- [SDK](/develop/dapps/apis/sdk) + +## 常见问题解答 + +转到[常见问题解答](/develop/howto/faq)部分。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/changelog/tvm-upgrade-2023-07.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/changelog/tvm-upgrade-2023-07.md new file mode 100644 index 0000000000..7c060fe919 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/changelog/tvm-upgrade-2023-07.md @@ -0,0 +1,279 @@ +# TVM Upgrade 2023.07 + +:::tip +This upgrade launched [run](https://t.me/tonblockchain/223) on the Mainnet from December 2023. +::: + + +# c7 + +**c7** is the register in which local context information needed for contract execution +(such as time, lt, network configs, etc) is stored. + +**c7** tuple extended from 10 to 14 elements: +* **10**: `cell` with code of the smart contract itself. +* **11**: `[integer, maybe_dict]`: TON value of the incoming message, extracurrency. +* **12**: `integer`, fees collected in the storage phase. +* **13**: `tuple` with information about previous blocks. + +**10** Currently code of the smart contract is presented on TVM level only as executable continuation +and can not be transformed to cell. This code is often used to authorize a neighbor contract +of the same kind, for instance jetton-wallet authorizes jetton-wallet. For now we need +to explicitly store code cell in storage which make storage and init_wrapper more cumbersome than it could be. +Using **10** for code is compatible to Everscale update of tvm. + +**11** Currently value of the incoming message is presented on stack after TVM initiation, so if needed during execution, +one either need to store it to global variable or pass through local variables +(at funC level it looks like additional `msg_value` argument in all functions). +By putting it to **11** element we will repeat behavior of contract balance: it is presented both on stack and in c7. + +**12** Currently the only way to calculate storage fees is to store balance in the previous transaction, +somehow calculate gas usage in prev transaction and then compare to current balance minus message value. +Meanwhile, is often desired to account storage fees. + +**13** Currently there is no way to retrieve data on previous blocks. One of the kill features of TON is that every structure +is a Merkle-proof friendly bag (tree) of cells, moreover TVM is cell and merkle-proof friendly as well. +That way, if we include information on the blocks to TVM context it will be possible to make many trustless scenarios: contract A may check transactions on contract B (without B's cooperation), it is possible to recover broken chains of messages (when recover-contract gets and checks proofs that some transaction occurred but reverted), knowing masterchain block hashes is also required to make some validation fisherman functions onchain. + +Block ids are presented in the following format: +``` +[ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer ] = BlockId; +[ last_mc_blocks:[BlockId0, BlockId1, ..., BlockId15] + prev_key_block:BlockId ] : PrevBlocksInfo +``` +Ids of the last 16 blocks of masterchain are included (or less if masterchain seqno is less than 16), as well as the last key block. +Inclusion of data on shardblocks may cause some data availability issues (due to merge/split events), +it is not necessarily required (since any event/data can by proven using masterchain blocks) and thus we decided not to include it. + +# New opcodes +Rule of thumb when choosing gas cost on new opcodes is that it should not be less than normal (calculated from opcode length) and should take no more than 20 ns per gas unit. + +## Opcodes to work with new c7 values +26 gas for each, except for `PREVMCBLOCKS` and `PREVKEYBLOCK` (34 gas). + +| xxxxxxxxxxxxxxxxxxxxxx
Fift syntax | xxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:--------------------------------------------------------------------| +| `MYCODE` | _`- c`_ | Retrieves code of smart-contract from c7 | +| `INCOMINGVALUE` | _`- t`_ | Retrieves value of incoming message from c7 | +| `STORAGEFEES` | _`- i`_ | Retrieves value of storage phase fees from c7 | +| `PREVBLOCKSINFOTUPLE` | _`- t`_ | Retrieves PrevBlocksInfo: `[last_mc_blocks, prev_key_block]` from c7 | +| `PREVMCBLOCKS` | _`- t`_ | Retrieves only `last_mc_blocks` | +| `PREVKEYBLOCK` | _`- t`_ | Retrieves only `prev_key_block` | +| `GLOBALID` | _`- i`_ | Retrieves `global_id` from 19 network config | + +## Gas + +| xxxxxxxxxxxxxx
Fift syntax | xxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:-----------------------------------------------------------------------------| +| `GASCONSUMED` | _`- g_c`_ | Returns gas consumed by VM so far (including this instruction).
_26 gas_ | + +## Arithmetics +New variants of [the division opcode](https://docs.ton.org/learn/tvm-instructions/instructions#52-division) (`A9mscdf`) are added: +`d=0` takes one additional integer from stack and adds it to the intermediate value before division/rshift. These operations return both the quotient and the remainder (just like `d=3`). + +Quiet variants are also available (e.g. `QMULADDDIVMOD` or `QUIET MULADDDIVMOD`). + +If return values don't fit into 257-bit integers or the divider is zero, non-quiet operation throws an integer overflow exception. Quiet operations return `NaN` instead of the value that doesn't fit (two `NaN`s if the divider is zero). + +Gas cost is equal to 10 plus opcode length: 26 for most opcodes, +8 for `LSHIFT#`/`RSHIFT#`, +8 for quiet. + +| xxxxxxxxxxxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Stack | +|:-|:-| +| `MULADDDIVMOD` | _`x y w z - q=floor((xy+w)/z) r=(xy+w)-zq`_ | +| `MULADDDIVMODR` | _`x y w z - q=round((xy+w)/z) r=(xy+w)-zq`_ | +| `MULADDDIVMODC` | _`x y w z - q=ceil((xy+w)/z) r=(xy+w)-zq`_ | +| `ADDDIVMOD` | _`x w z - q=floor((x+w)/z) r=(x+w)-zq`_ | +| `ADDDIVMODR` | _`x w z - q=round((x+w)/z) r=(x+w)-zq`_ | +| `ADDDIVMODC` | _`x w y - q=ceil((x+w)/z) r=(x+w)-zq`_ | +| `ADDRSHIFTMOD` | _`x w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `ADDRSHIFTMODR` | _`x w z - q=round((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `ADDRSHIFTMODC` | _`x w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `z ADDRSHIFT#MOD` | _`x w - q=floor((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `z ADDRSHIFTR#MOD` | _`x w - q=round((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `z ADDRSHIFTC#MOD` | _`x w - q=ceil((x+w)/2^z) r=(x+w)-q*2^z`_ | +| `MULADDRSHIFTMOD` | _`x y w z - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `MULADDRSHIFTRMOD` | _`x y w z - q=round((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `MULADDRSHIFTCMOD` | _`x y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `z MULADDRSHIFT#MOD` | _`x y w - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `z MULADDRSHIFTR#MOD` | _`x y w - q=round((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `z MULADDRSHIFTC#MOD` | _`x y w - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z`_ | +| `LSHIFTADDDIVMOD` | _`x w z y - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | +| `LSHIFTADDDIVMODR` | _`x w z y - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | +| `LSHIFTADDDIVMODC` | _`x w z y - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | +| `y LSHIFT#ADDDIVMOD` | _`x w z - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | +| `y LSHIFT#ADDDIVMODR` | _`x w z - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | +| `y LSHIFT#ADDDIVMODC` | _`x w z - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq`_ | + +## Stack operations +Currently arguments of all stack operations are bounded by 256. +That means that if stack become deeper than 256 it becomes difficult to manage deep stack elements. +In most cases there are no safety reasons for that limit, i.e. arguments are not limited to prevent too expensive operations. +For some mass stack operations, such as `ROLLREV` (where computation time linearly depends on argument value) gas cost also linearly depends on argument value. +- Arguments of `PICK`, `ROLL`, `ROLLREV`, `BLKSWX`, `REVX`, `DROPX`, `XCHGX`, `CHKDEPTH`, `ONLYTOPX`, `ONLYX` are now unlimited. +- `ROLL`, `ROLLREV`, `REVX`, `ONLYTOPX` consume more gas when arguments are big: additional gas cost is `max(arg-255,0)` (for argument less than 256 the gas consumption is constant and corresponds to the current behavior) +- For `BLKSWX`, additional cost is `max(arg1+arg2-255,0)` (this does not correspond to the current behavior, since currently both `arg1` and `arg2` are limited to 255). + +## Hashes +Currently only two hash operations are available in TVM: calculation of representation hash of cell/slice, and sha256 of data, but only up to 127 bytes (only that much data fits into one cell). + +`HASHEXT[A][R]_(HASH)` family of operations is added: + +| xxxxxxxxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:--------------------------------------------------------------------------------------| +| `HASHEXT_(HASH)` | _`s_1 ... s_n n - h`_ | Calculates and returns hash of the concatenation of slices (or builders) `s_1...s_n`. | +| `HASHEXTR_(HASH)` | _`s_n ... s_1 n - h`_ | Same thing, but arguments are given in reverse order. | +| `HASHEXTA_(HASH)` | _`b s_1 ... s_n n - b'`_ | Appends the resulting hash to a builder `b` instead of pushing it to the stack. | +| `HASHEXTAR_(HASH)` | _`b s_n ... s_1 n - b'`_ | Arguments are given in reverse order, appends hash to builder. | + +Only the bits from root cells of `s_i` are used. + +Each chunk `s_i` may contain non-integer number of bytes. However, the sum of bits of all chunks should be divisible by 8. +Note that TON uses most-significant bit ordering, so when two slices with non-integer number of bytes are concatenated, bits from the first slice become most-significant bits. + +Gas consumption depends on the number of hashed bytes and the chosen algorithm. Additional 1 gas unit is consumed per chunk. + +If `[A]` is not enabled, the result of hashing will be returned as an unsigned integer if fits 256 bits or tuple of ints otherwise. + +The following algorithms are available: +- `SHA256` - openssl implementation, 1/33 gas per byte, hash is 256 bits. +- `SHA512` - openssl implementation, 1/16 gas per byte, hash is 512 bits. +- `BLAKE2B` - openssl implementation, 1/19 gas per byte, hash is 512 bits. +- `KECCAK256` - [ethereum compatible implementation](http://keccak.noekeon.org/), 1/11 gas per byte, hash is 256 bits. +- `KECCAK512` - [ethereum compatible implementation](http://keccak.noekeon.org/), 1/6 gas per byte, hash is 512 bits. + +Gas usage is rounded down. + +## Crypto +Currently the only cryptographic algorithm available is `CHKSIGN`: check the Ed25519-signature of a hash `h` for a public key `k`. + +- For compatibility with prev generation blockchains such as Bitcoin and Ethereum we also need checking `secp256k1` signatures. +- For modern cryptographic algorithms the bare minimum is curve addition and multiplication. +- For compatibility with Ethereum 2.0 PoS and some other modern cryptography we need BLS-signature scheme on bls12-381 curve. +- For some secure hardware secp256r1 == P256 == prime256v1 is needed. + +### secp256k1 +Bitcoin/ethereum signatures. Uses [libsecp256k1 implementation](https://github.com/bitcoin-core/secp256k1). + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:-| +| `ECRECOVER` | _`hash v r s - 0 or h x1 x2 -1`_ | Recovers public key from signature, identical to Bitcoin/Ethereum operations.
Takes 32-byte hash as uint256 `hash`; 65-byte signature as uint8 `v` and uint256 `r`, `s`.
Returns `0` on failure, public key and `-1` on success.
65-byte public key is returned as uint8 `h`, uint256 `x1`, `x2`.
_1526 gas_ | + +### secp256r1 +Uses OpenSSL implementation. Interface is similar to `CHKSIGNS`/`CHKSIGNU`. Compatible with Apple Secure Enclave. + + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:-| +| `P256_CHKSIGNS` | _`d sig k - ?`_ | Checks seck256r1-signature `sig` of data portion of slice `d` and public key `k`. Returns -1 on success, 0 on failure.
Public key is a 33-byte slice (encoded according to Sec. 2.3.4 point 2 of [SECG SEC 1](https://www.secg.org/sec1-v2.pdf)).
Signature `sig` is a 64-byte slice (two 256-bit unsigned integers `r` and `s`).
_3526 gas_ | +| `P256_CHKSIGNU` | _`h sig k - ?`_ | Same thing, but the signed data is 32-byte encoding of 256-bit unsigned integer `h`.
_3526 gas_ | + +### Ristretto +Extended docs are [here](https://ristretto.group/). In short, Curve25519 was developed with performance in mind, however it exhibits symmetry due to which group elements have multiple representations. Simpler protocols such as Schnorr signatures or Diffie-Hellman apply tricks at the protocol level to mitigate some issues, but break key derivation and key blinding schemes. And those tricks do not scale to more complex protocols such as Bulletproofs. Ristretto is an arithmetic abstraction over Curve25519 such that each group element corresponds to a unique point, which is the requirement for most cryptographic protocols. Ristretto is essentially a compression/decompression protocol for Curve25519 that offers the required arithmetic abstraction. As a result, crypto protocols are easy to write correctly, while benefiting from the high performance of Curve25519. + +Ristretto operations allow calculating curve operations on Curve25519 (the reverse is not true), thus we can consider that we add both Ristretto and Curve25519 curve operation in one step. + +[libsodium](https://github.com/jedisct1/libsodium/) implementation is used. + +All ristretto-255 points are represented in TVM as 256-bit unsigned integers. +Non-quiet operations throw `range_chk` if arguments are not valid encoded points. +Zero point is represented as integer `0`. + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:------------------------------------------------------------------------------------------------------------------| +| `RIST255_FROMHASH` | _`h1 h2 - x`_ | Deterministically generates a valid point `x` from a 512-bit hash (given as two 256-bit integers).
_626 gas_ | +| `RIST255_VALIDATE` | _`x -`_ | Checks that integer `x` is a valid representation of some curve point. Throws `range_chk` on error.
_226 gas_ | +| `RIST255_ADD` | _`x y - x+y`_ | Addition of two points on a curve.
_626 gas_ | +| `RIST255_SUB` | _`x y - x-y`_ | Subtraction of two points on curve.
_626 gas_ | +| `RIST255_MUL` | _`x n - x*n`_ | Multiplies point `x` by a scalar `n`.
Any `n` is valid, including negative.
_2026 gas_ | +| `RIST255_MULBASE` | _`n - g*n`_ | Multiplies the generator point `g` by a scalar `n`.
Any `n` is valid, including negative.
_776 gas_ | +| `RIST255_PUSHL` | _`- l`_ | Pushes integer `l=2^252+27742317777372353535851937790883648493`, which is the order of the group.
_26 gas_ | +| `RIST255_QVALIDATE` | _`x - 0 or -1`_ | Quiet version of `RIST255_VALIDATE`.
_234 gas_ | +| `RIST255_QADD` | _`x y - 0 or x+y -1`_ | Quiet version of `RIST255_ADD`.
_634 gas_ | +| `RIST255_QSUB` | _`x y - 0 or x-y -1`_ | Quiet version of `RIST255_SUB`.
_634 gas_ | +| `RIST255_QMUL` | _`x n - 0 or x*n -1`_ | Quiet version of `RIST255_MUL`.
_2034 gas_ | +| `RIST255_QMULBASE` | _`n - 0 or g*n -1`_ | Quiet version of `RIST255_MULBASE`.
_784 gas_ | + +### BLS12-381 +Operations on a pairing friendly BLS12-381 curve. [BLST](https://github.com/supranational/blst) implementation is used. Also, ops for BLS signature scheme which is based on this curve. + +BLS values are represented in TVM in the following way: +- G1-points and public keys: 48-byte slice. +- G2-points and signatures: 96-byte slice. +- Elements of field FP: 48-byte slice. +- Elements of field FP2: 96-byte slice. +- Messages: slice. Number of bits should be divisible by 8. + +When input value is a point or a field element, the slice may have more than 48/96 bytes. In this case only the first 48/96 bytes are taken. If the slice has less bytes (or if message size is not divisible by 8), cell underflow exception is thrown. + +#### High-level operations +These are high-level operations for verifying BLS signatures. + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BLS_VERIFY` | _`pk msg sgn - bool`_ | Checks BLS signature, return true on success, false otherwise.
_61034 gas_ | +| `BLS_AGGREGATE` | _`sig_1 ... sig_n n - sig`_ | Aggregates signatures. `n>0`. Throw exception if `n=0` or if some `sig_i` is not a valid signature.
_`gas=n*4350-2616`_ | +| `BLS_FASTAGGREGATEVERIFY`- | _`pk_1 ... pk_n n msg sig - bool`_ | Checks aggregated BLS signature for keys `pk_1...pk_n` and message `msg`. Return true on success, false otherwise. Return false if `n=0`.
_`gas=58034+n*3000`_ | +| `BLS_AGGREGATEVERIFY` | _`pk_1 msg_1 ... pk_n msg_n n sgn - bool`_ | Checks aggregated BLS signature for key-message pairs `pk_1 msg_1...pk_n msg_n`. Return true on success, false otherwise. Return false if `n=0`.
_`gas=38534+n*22500`_ | + +`VERIFY` instructions don't throw exception on invalid signatures and public keys (except for cell underflow exceptions), they return false instead. + +#### Low-level operations +These are arithmetic operations on group elements. + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BLS_G1_ADD` | _`x y - x+y`_ | Addition on G1.
_3934 gas_ | +| `BLS_G1_SUB` | _`x y - x-y`_ | Subtraction on G1.
_3934 gas_ | +| `BLS_G1_NEG` | _`x - -x`_ | Negation on G1.
_784 gas_ | +| `BLS_G1_MUL` | _`x s - x*s`_ | Multiplies G1 point `x` by scalar `s`.
Any `s` is valid, including negative.
_5234 gas_ | +| `BLS_G1_MULTIEXP` | _`x_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_n`_ | Calculates `x_1*s_1+...+x_n*s_n` for G1 points `x_i` and scalars `s_i`. Returns zero point if `n=0`.
Any `s_i` is valid, including negative.
_`gas=11409+n*630+n/floor(max(log2(n),4))*8820`_ | +| `BLS_G1_ZERO` | _`- zero`_ | Pushes zero point in G1.
_34 gas_ | +| `BLS_MAP_TO_G1` | _`f - x`_ | Converts FP element `f` to a G1 point.
_2384 gas_ | +| `BLS_G1_INGROUP` | _`x - bool`_ | Checks that slice `x` represents a valid element of G1.
_2984 gas_ | +| `BLS_G1_ISZERO` | _`x - bool`_ | Checks that G1 point `x` is equal to zero.
_34 gas_ | +| `BLS_G2_ADD` | _`x y - x+y`_ | Addition on G2.
_6134 gas_ | +| `BLS_G2_SUB` | _`x y - x-y`_ | Subtraction on G2.
_6134 gas_ | +| `BLS_G2_NEG` | _`x - -x`_ | Negation on G2.
_1584 gas_ | +| `BLS_G2_MUL` | _`x s - x*s`_ | Multiplies G2 point `x` by scalar `s`.
Any `s` is valid, including negative.
_10584 gas_ | +| `BLS_G2_MULTIEXP` | _`x_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_n`_ | Calculates `x_1*s_1+...+x_n*s_n` for G2 points `x_i` and scalars `s_i`. Returns zero point if `n=0`.
Any `s_i` is valid, including negative.
_`gas=30422+n*1280+n/floor(max(log2(n),4))*22840`_ | +| `BLS_G2_ZERO` | _`- zero`_ | Pushes zero point in G2.
_34 gas_ | +| `BLS_MAP_TO_G2` | _`f - x`_ | Converts FP2 element `f` to a G2 point.
_7984 gas_ | +| `BLS_G2_INGROUP` | _`x - bool`_ | Checks that slice `x` represents a valid element of G2.
_4284 gas_ | +| `BLS_G2_ISZERO` | _`x - bool`_ | Checks that G2 point `x` is equal to zero.
_34 gas_ | +| `BLS_PAIRING` | _`x_1 y_1 ... x_n y_n n - bool`_ | Given G1 points `x_i` and G2 points `y_i`, calculates and multiply pairings of `x_i,y_i`. Returns true if the result is the multiplicative identity in FP12, false otherwise. Returns false if `n=0`.
_`gas=20034+n*11800`_ | +| `BLS_PUSHR` | _`- r`_ | Pushes the order of G1 and G2 (approx. `2^255`).
_34 gas_ | + +`INGROUP`, `ISZERO` don't throw exception on invalid points (except for cell underflow exceptions), they return false instead. + +Other arithmetic operations throw exception on invalid curve points. Note that they don't check whether given curve points belong to group G1/G2. Use `INGROUP` instruction to check this. + +## RUNVM +Currently there is no way for code in TVM to call external untrusted code "in sandbox". In other words, external code always can irreversibly update code, data of contract, or set actions (such as sending all money). +`RUNVM` instruction allows to spawn an independent VM instance, run desired code and get needed data (stack, registers, gas consumption etc) without risks of polluting caller's state. Running arbitrary code in a safe way may be useful for [v4-style plugins](/participate/wallets/contracts#wallet-v4), Tact's `init` style subcontract calculation etc. + +| xxxxxxxxxxxxx
Fift syntax | xxxxxxxxxxxxxxxxx
Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description | +|:-|:-|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `flags RUNVM` | _`x_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]`_ | Runs child VM with code `code` and stack `x_1...x_n`. Returns the resulting stack `x'_1...x'_m` and exitcode.
Other arguments and return values are enabled by flags, see below. | +| `RUNVMX` | _`x_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] flags - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]`_ | Same thing, but pops flags from stack. | + +Flags are similar to `runvmx` in fift: +- `+1`: set c3 to code +- `+2`: push an implicit 0 before running the code +- `+4`: take `c4` from stack (persistent data), return its final value +- `+8`: take gas limit `g_l` from stack, return consumed gas `g_c` +- `+16`: take `c7` from stack (smart-contract context) +- `+32`: return final value of `c5` (actions) +- `+64`: pop hard gas limit (enabled by ACCEPT) `g_m` from stack +- `+128`: "isolated gas consumption". Child VM will have a separate set of visited cells and a separate chksgn counter. +- `+256`: pop integer `r`, return exactly `r` values from the top of the stack (only if `exitcode=0 or 1`; if not enough then `exitcode=stk_und`) + +Gas cost: +- 66 gas +- 1 gas for every stack element given to the child VM (first 32 are free) +- 1 gas for every stack element returned from the child VM (first 32 are free) + +## Sending messages +Currently it is difficult to calculate cost of sending message in contract (which leads to some approximations like in [jettons](https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-wallet.fc#L94)) and impossible to bounce request back if action phase is incorrect. It is also impossible to accurately subtract from incoming message sum of "constant fee for contract logic" and "gas expenses". + +- `SENDMSG` takes a cell and mode as input. Creates an output action and returns a fee for creating a message. Mode has the same effect as in the case of SENDRAWMSG. Additionally `+1024` means - do not create an action, only estimate fee. Other modes affect the fee calculation as follows: `+64` substitutes the entire balance of the incoming message as an outgoing value (slightly inaccurate, gas expenses that cannot be estimated before the computation is completed are not taken into account), `+128` substitutes the value of the entire balance of the contract before the start of the computation phase (slightly inaccurate, since gas expenses that cannot be estimated before the completion of the computation phase are not taken into account). +- `SENDRAWMSG`, `RAWRESERVE`, `SETLIBCODE`, `CHANGELIB` - `+16` flag is added, that means in the case of action fail - bounce transaction. No effect if `+2` is used. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-exit-codes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-exit-codes.md new file mode 100644 index 0000000000..a54863b6fc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-exit-codes.md @@ -0,0 +1,43 @@ +# TVM Exit codes + +If TVM exits with an arbitrary 16-bit unsigned integer `exit_code`. `exit_code` higher than 1, it is considered to be an _error code_, therefore an exit with such a code may cause the transaction to revert/bounce. + +## Standard exit codes + +:::info +The list of standard exit codes contains all universal TVM exit codes defined for TON Blockchain. Alternative exit codes should be sought in the source code of corresponded contract. +::: + +| Exit Code | TVM Phase | Description | +|-----------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `0` | Compute Phase | Standard successful execution exit code. | +| `1` | Compute Phase | Alternative successful execution exit code. | +| `2` | Compute Phase | Stack underflow. Last op-code consumed more elements than there are on the stacks. 1 | +| `3` | Compute Phase | Stack overflow. More values have been stored on a stack than allowed by this version of TVM. | +| `4` | Compute Phase | Integer overflow. Integer does not fit into −2256 ≤ x < 2256 or a division by zero has occurred. | +| `5` | Compute Phase | Integer out of expected range. | +| `6` | Compute Phase | Invalid opcode. Instruction is unknown in the current TVM version. | +| `7` | Compute Phase | Type check error. An argument to a primitive is of an incorrect value type. 1 | +| `8` | Compute Phase | Cell overflow. Writing to builder is not possible since after operation there would be more than 1023 bits or 4 references. | +| `9` | Compute Phase | Cell underflow. Read from slice primitive tried to read more bits or references than there are. | +| `10` | Compute Phase | Dictionary error. Error during manipulation with dictionary (hashmaps). | +| `11` | Compute Phase | Most often caused by trying to call get-method whose id wasn't found in the code (missing `method_id` modifier or wrong get-method name specified when trying to call it). In [TVM docs](https://ton.org/tvm.pdf) its described as "Unknown error, may be thrown by user programs". | +| `12` | Compute Phase | Thrown by TVM in situations deemed impossible. | +| `13` | Compute Phase | Out of gas error. Thrown by TVM when the remaining gas becomes negative. | +| `-14` | Compute Phase | It means out of gas error, same as `13`. Negative, because it [cannot be faked](https://github.com/ton-blockchain/ton/blob/20758d6bdd0c1327091287e8a620f660d1a9f4da/crypto/vm/vm.cpp#L492) | +| `32` | Action Phase | Action list is invalid. Set during action phase if c5 register after execution contains unparsable object. | +| `-32` | Action Phase | (the same as prev 32) - Method ID not found. Returned by TonLib during an attempt to execute non-existent get method. | +| `33` | Action Phase | Action list is too long. | +| `34` | Action Phase | Action is invalid or not supported. Set during action phase if current action cannot be applied. | +| `35` | Action Phase | Invalid Source address in outbound message. | +| `36` | Action Phase | Invalid Destination address in outbound message. | +| `37` | Action Phase | Not enough TON. Message sends too much TON (or there is not enough TON after deducting fees). | +| `38` | Action Phase | Not enough extra-currencies. | +| `40` | Action Phase | Not enough funds to process a message. This error is thrown when there is only enough gas to cover part of the message, but does not cover it completely. | +| `43` | Action Phase | The maximum number of cells in the library is exceeded or the maximum depth of the Merkle tree is exceeded. | + +1 If you encounter such exception in a func contract it probably means a type error in asm declarations. + +:::info +Often you can see the exit code `0xffff` (65535 in decimal form). This usually means that the received opcode is unknown to the contract. When writing contracts, this code is set by the developer himself. +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-initialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-initialization.md new file mode 100644 index 0000000000..3983b2468b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-initialization.md @@ -0,0 +1,188 @@ +# TVM Initialization + +:::info +To maximize your comprehension of this page, familiarizing yourself with the [TL-B language](/develop/data-formats/cell-boc) is highly recommended. +::: + +TVM is invoked during the computing phase of ordinary and/or other transactions. + +## Initial state + +A new instance of TVM is initialized prior to the execution of a smart contract as follows: + +- The original **cc** (current continuation) is initialized using the cell slice created from the `code` section of the smart contract. In case of a frozen or uninitialized state of the account, the code must be supplied in the `init` field of the incoming message. + +- The **cp** (current TVM codepage) is set to the default value, which is 0. If the smart contract wants to use another TVM codepage _x_, then it must switch to it by using `SETCODEPAGE` _x_ as the first instruction of its code. + +- The **gas** values (_gas limits_) are initialized in accordance to Credit phase results. + +- The **libraries** (_library context_) computation is [described below](#library-context). + +- The **stack** initialization process depends on the event which caused the transaction, and its contents are [described below](#stack). + +- Control register **c0** (return continuation) is initialized by extraordinary continuation `ec_quit` with parameter 0. When executed, this continuation leads to a termination of TVM with exit code 0. + +- Control register **c1** (alternative return continuation) is initialized by extraordinary continuation `ec_quit` with parameter 1. When invoked, it leads to a termination of TVM with exit code 1. Notice, that both exit codes 0 and 1 are considered a successful termination of TVM. + +- Control register **c2** (exception handler) is initialized by extraordinary continuation `ec_quit_exc`. When invoked, it takes the top integer from the stack (equal to the exception number) and terminates TVM with exit code equal to that integer. This way, by default all exceptions terminate the smart contract execution with exit code equal to the exception number. + +- Control register **c3** (code dictionary) is initialized by the cell with the smart contract code like **cc** (current continuation) described above. + +- Control register **c4** (root of persistent data) is initialized by the persistent data of the smart contract, stored in its `data` section. In case of a frozen or uninitialized state of the account, the data must be supplied in the `init` field of the incoming message. Notice, that the persistent data of the smart contract does not need to be loaded in its entirely for this to occur. The root is loaded instead, and TVM may load other cells by their references from the root only when they are accessed, thus providing a form of virtual memory. + +- Control register **c5** (root of actions) is initialized by an empty cell. The "output action" primitives of TVM, such as `SENDMSG`, accumulate _output actions_ (e.g., outbound messages) in this register, to be performed upon successful termination of the smart contract. The TL-B scheme for its serialization is [described below](#control-register-c5) + +- Control register **c7** (root of temporary data) is initialized as a tuple and its structure is [described below](#control-register-c7) + +## Library context + +The _library context_ (library environment) of a smart contract is a hashmap mapping 256-bit cell (representation) hashes into the corresponding cells themselves. When an external cell reference is accessed during the execution of the smart contract, the cell referred to is looked up in the library environment and the external cell reference is transparently replaced by the cell found. + +The library environment for an invocation of a smart contract is computed as follows: +1. The global library environment for the current workchain is taken from the current state of the masterchain. +2. Then, it is augmented by the local library environment of the smart contract, stored in the `library` field of the smart contract's state. Only 256-bit keys equal to the hashes of the corresponding value cells are taken into account. If a key is present in both the global and local library environments, the local environment takes precedence in the merge. +3. Finally, it is augmented by the `library` field of the `init` field of the incoming message (if any). Notice, that if the account is frozen or uninitialized, the `library` field of the message would be used over the local library environment from the previous step. The message library has lower precedence than both the local and the global library environments. + +Most common way of creating shared libraries for TVM is to publish a reference to the root cell of the library in the masterchain. + +## Stack + +Initialization of the TVM stack comes after the formation of the initial state of the TVM, and it depends on the event which caused the transaction: +- internal message +- external message +- tick-tock +- split prepare +- merge install + +The last item pushed to the stack is always the _function selector_, which is an _Integer_ that identifies the event that caused the transaction. + +### Internal message + +In case of internal message, the stack is initialized by pushing the arguments to the `main()` function of the smart contract as follows: +- The balance _b_ of the smart contract (after crediting the value of the inbound message) is passed as an _Integer_ amount of nanotons. +- The balance _b_m of inbound message _m_ is passed as an _Integer_ amount of nanotons. +- The inbound message _m_ is passed as a cell, which contains a serialized value of type _Message X_, where _X_ is the type of the message body. +- The body _m_b of the inbound message, equal to the value of field body _m_ and passed as a cell slice. +- The function selector _s_, normally equal to 0. + +After that, the code of the smart contract, equal to its initial value of **c3**, is executed. It selects the correct function according to _s_, which is expected to process the remaining arguments to the function and terminate afterwards. + +### External message + +An inbound external message is processed similarly to the [internal message described above](#internal-message), with the following modifications: +- The function selector _s_ is set to -1. +- The balance _b_m of inbound message is always 0. +- The initial current gas limit _g_l is always 0. However, the initial gas credit _g_c > 0. + +The smart contract must terminate with _g_c = 0 or _g_r ≥ _g_c; otherwise, the transaction and the block containing it are invalid. Validators or collators suggesting a block candidate must never include transactions processing inbound external messages that are invalid. + +### Tick and tock + +In case of tick and tock transactions, the stack is initialized by pushing the arguments to the `main()` function of the smart contract as follows: +- The balance _b_ of the current account is passed as an _Integer_ amount of nanotons. +- The 256-bit address of the current account inside the masterchain as an unsigned _Integer_. +- An integer equal to 0 for tick transactions and to -1 for tock transactions. +- The function selector _s_, equal to -2. + +### Split prepare + +In case of split prepare transaction, the stack is initialized by pushing the arguments to the `main()` function of the smart contract as follows: +- The balance _b_ of the current account is passed as an _Integer_ amount of nanotons. +- A _Slice_ containing _SplitMergeInfo_. +- The 256-bit address of the current account. +- The 256-bit address of the sibling account. +- An integer 0 ≤ _d_ ≤ 63, equal to the position of the only bit in which addresses of the current and sibling account differ. +- The function selector _s_, equal to -3. + +### Merge install + +In case of merge install transaction, the stack is initialized by pushing the arguments to the `main()` function of the smart contract as follows: +- The balance _b_ of the current account (already combined with the nanoton balance of the sibling account) is passed as an _Integer_ amount of nanotons. +- The balance _b'_ of the sibling account, taken from the inbound message _m_ is passed as an _Integer_ amount of nanotons. +- The message _m_ from the sibling account, automatically generated by a merge prepare transaction. Its `init` field contains the final state of the sibling account. The message is passed as a cell, which contains a serialized value of type _Message X_, where _X_ is the type of the message body. +- The state of the sibling account, represented by a _StateInit_. +- A _Slice_ containing _SplitMergeInfo_. +- The 256-bit address of the current account. +- The 256-bit address of the sibling account. +- An integer 0 ≤ _d_ ≤ 63, equal to the position of the only bit in which addresses of the current and sibling account differ. +- The function selector _s_, equal to -4. + +## Control register c5 + +The _output actions_ of a smart contract are accumulated in cell stored in control register **c5**: the cell itself contains the last action in the list and a reference to the previous one, thus forming a linked list. + +The list can also be serialized as a value of type _OutList n_, where _n_ is the length of the list: + +```tlb +out_list_empty$_ = OutList 0; + +out_list$_ {n:#} + prev:^(OutList n) + action:OutAction + = OutList (n + 1); + +out_list_node$_ + prev:^Cell + action:OutAction = OutListNode; +``` + +The list of possible actions thereby consists of: +- `action_send_msg` — for sending an outbound message +- `action_set_code` — for setting an opcode +- `action_reserve_currency` — for storing a currency collection +- `action_change_library` — for changing the library + +As described in the corresponding TL-B scheme: + +```tlb +action_send_msg#0ec3c86d + mode:(## 8) + out_msg:^(MessageRelaxed Any) = OutAction; + +action_set_code#ad4de08e + new_code:^Cell = OutAction; + +action_reserve_currency#36e6b809 + mode:(## 8) + currency:CurrencyCollection = OutAction; + +libref_hash$0 + lib_hash:bits256 = LibRef; +libref_ref$1 + library:^Cell = LibRef; +action_change_library#26fa1dd4 + mode:(## 7) { mode <= 2 } + libref:LibRef = OutAction; +``` + +## Control register c7 + +Control register **c7** contains the root of temporary data as a Tuple, formed by a _SmartContractInfo_ type, containing some basic blockchain context data, such as time, global config, etc. It is described by the following TL-B scheme: + +```tlb +smc_info#076ef1ea + actions:uint16 msgs_sent:uint16 + unixtime:uint32 block_lt:uint64 trans_lt:uint64 + rand_seed:bits256 balance_remaining:CurrencyCollection + myself:MsgAddressInt global_config:(Maybe Cell) = SmartContractInfo; +``` + +First component of this tuple is an _Integer_ value, which is always equal to 0x076ef1ea, after which 9 named fields follow: + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `actions` | uint16 | Originally initialized by 0, but incremented by one whenever an output action is installed by a non-RAW output action primitive | +| `msgs_sent` | uint16 | Number of messages sent | +| `unixtime` | uint32 | Unix timestamp in seconds | +| `block_lt` | uint64 | Represents _logical time_ of the previous block of this account. [More about logical time](https://docs.ton.org/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time) | +| `trans_lt` | uint64 | Represents _logical time_ of the previous transaction of this account | +| `rand_seed` | bits256 | Initialized deterministically starting from `rand_seed` of the block, the account address, the hash of the incoming message being processed (if any), and the transaction logical time `trans_lt` | +| `balance_remaining` | [CurrencyCollection](/develop/data-formats/msg-tlb#currencycollection) | Remaining balance of the smart contract | +| `myself` | [MsgAddressInt](/develop/data-formats/msg-tlb#msgaddressint-tl-b) | Address of this smart contract | +| `global_config` | (Maybe Cell) | Contains information about the global config | + +Notice, that in the upcoming upgrade to the TVM, the **c7** tuple was extended from 10 to 14 elements. Read more about it [here](/learn/tvm-instructions/tvm-upgrade-2023-07). + +## See also + +- Original description of [TVM Initialization](https://docs.ton.org/tblkch.pdf#page=89&zoom=100) from the whitepaper diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-overview.mdx new file mode 100644 index 0000000000..a0280bc6cc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/tvm/tvm-overview.mdx @@ -0,0 +1,131 @@ +import Button from '@site/src/components/button' + +# TVM Overview + +All TON Smart Contracts are executed on their own TON Virtual Machine (TVM). TVM is built on the _stack principle_, which makes it efficient and easy to implement. + +This document provides a bird's-eye view of how TVM executes transactions. + +:::tip +* TVM Source — [**TVM C++ implementation**](https://github.com/ton-blockchain/ton/tree/master/crypto/vm) +::: + + +## TON Course: TVM + +:::tip +Before starting the course, make sure you have a good understanding of the basics of blockchain technology. If you have gaps in your knowledge, we recommend taking the [Blockchain Basics with TON](https://stepik.org/course/201294/promo) ([RU version](https://stepik.org/course/202221/), [CHN version](https://stepik.org/course/200976/)) course. +::: + +The [TON Blockchain Course](https://stepik.org/course/176754/) is a comprehensive guide to TON Blockchain development. + +Module 2 completely covers __TVM__, transactions, scalability and business cases. + + + + + + + + + + + +## Transactions and phases +When some event happens on the account in one of the TON chains, it causes a **transaction**. The most common event is the "arrival of some message", but generally speaking there could be `tick-tock`, `merge`, `split` and other events. + +Each transaction consists of up to 5 phases: +1. **Storage phase** - in this phase, storage fees accumulated by the contract due to the occupation of some space in the chain state are calculated. Read more in [Storage Fees](/develop/smart-contracts/fees#storage-fee). +2. **Credit phase** - in this phase, the balance of the contract with respect to a (possible) incoming message value and collected storage fee are calculated. +3. **Compute phase** - in this phase, TVM is executing the contract (see below) and the result of the contract execution is an aggregation of `exit_code`, `actions` (serialized list of actions), `gas_details`, `new_storage` and some others. +4. **Action phase** - if the compute phase was successful, in this phase, `actions` from the compute phase are processed. In particular, actions may include sending messages, updating the smart contract code, updating the libraries, etc. Note that some actions may fail during processing (for instance, if we try to send message with more TON than the contract has), in that case the whole transaction may revert or this action may be skipped (it depends on the mode of the actions, in other words, the contract may send a `send-or-revert` or `try-send-if-no-ignore` type of message). +5. **Bounce phase** - if the compute phase failed (it returned `exit_code >= 2`), in this phase, the _bounce message_ is formed for the transactions initiated by an incoming message. + +## Compute phase +In this phase, the TVM execution occurs. + +:::tip +* TVM 4.3.5 — [**TON Blockchain paper**](https://docs.ton.org/assets/files/tblkch-6aaf006b94ee2843a982ebf21d7c1247.pdf) +::: + +### Compute phase skipped + +The computing phase consists in invoking TVM with correct inputs. On some occasions, TVM cannot be invoked at all (e.g., if the account is absent, not initialized, or frozen, and the inbound message being processed has no code or data fields or these fields have an incorrect hash) + +This is reflected by corresponding [constructors](https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L314): + +- `cskip_no_state$00` - The [absence of a state](https://testnet.tonviewer.com/transaction/7e78394d082882375a5d21affa6397dec60fc5a3ecbea87f401b0e460fb5c80c) (i.e., smart-contract code and data) in both the account (non-existing, uninitialized, or frozen) and the message. + +- `cskip_bad_state$01` - An invalid state passed in the message (i.e., the state's hash differs from the expected value) to a frozen or uninitialized account. + +- `cskip_no_gas$10` - The [absence of funds](https://testnet.tonviewer.com/transaction/a1612cde7fd66139a7d04b30f38db192bdb743a8b12054feba3c16061a4cb9a6) to buy gas. (About < 0.00004 TON by [08.2024](https://testnet.tonviewer.com/transaction/9789306d7b29318c90477aa3df6599ee4a897031162ad41a24decb87db65402b)) + +### TVM state +At any given moment, the TVM state is fully determined by 6 properties: +* Stack (see below) +* Control registers - (see below) to put it simply, this means up to 16 variables which may be directly set and read during execution +* Current continuation - object which describes a currently executed sequence of instructions +* Current codepage - in simple terms, this means the version of TVM which is currently running +* Gas limits - a set of 4 integer values; the current gas limit gl, the maximal gas limit gm, the remaining gas gr and the gas credit gc +* Library context - the hashmap of libraries which can be called by TVM + +### TVM is a stack machine +TVM is a last-input-first-output stack machine. In total, there are 7 types of variables which may be stored in stack — three non-cell types: +* Integer - signed 257-bit integers +* Tuple - ordered collection of up to 255 elements having arbitrary value types, possibly distinct. +* Null + +And four distinct flavours of cells: +* Cell - basic (possibly nested) opaque structure used by TON Blockchain for storing all data +* Slice - a special object which allows you to read from a cell +* Builder - a special object which allows you to create new cells +* Continuation - a special object which allows you to use a cell as source of TVM instructions + +### Control registers +* `c0` — Contains the next continuation or return continuation (similar to the subroutine return address in conventional designs). This value must be a Continuation. +* `c1` — Contains the alternative (return) continuation; this value must be a Continuation. +* `c2` — Contains the exception handler. This value is a Continuation, invoked whenever an exception is triggered. +* `c3` — Supporting register, contains the current dictionary, essentially a hashmap containing the code of all functions used in the program. This value must be a Continuation. +* `c4` — Contains the root of persistent data, or simply the `data` section of the contract. This value is a Cell. +* `c5` — Contains the output actions. This value is a Cell. +* `c7` — Contains the root of temporary data. It is a Tuple. + +### Initialization of TVM + +TVM initializes when transaction execution gets to the Computation phase, and then executes commands (opcodes) from _Current continuation_ until there are no more commands to execute (and no continuation for return jumps). + +Detailed description of the initialization process can be found here: [TVM Initialization](/learn/tvm-instructions/tvm-initialization.md) + +## TVM instructions + +The list of TVM instructions can be found here: [TVM instructions](/learn/tvm-instructions/instructions). + +### Result of TVM execution +Besides exit_code and consumed gas data, TVM indirectly outputs the following data: +* c4 register - the cell which will be stored as new `data` of the smart-contract (if execution will not be reverted on this or later phases) +* c5 register - (list of output actions) the cell with last action in the list and reference to the cell with prev action (recursively) + +All other register values will be neglected. + +Note, that since there is a limit on max cell-depth `<1024`, and particularly the limit on c4 and c5 depth `<=512`, there will be a limit on the number of output actions in one tx `<=255`. If a contract needs to send more than that, it may send a message with the request `continue_sending` to itself and send all necessary messages in subsequent transactions. + + +## See Also + +- [TVM Instructions](/learn/tvm-instructions/instructions) +- [TON TVM](https://ton.org/tvm.pdf) TVM Concepts(may include outdated information) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/whitepapers/overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/whitepapers/overview.md new file mode 100644 index 0000000000..f4612bf69b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/documentation/whitepapers/overview.md @@ -0,0 +1,35 @@ +# 白皮书 + +本节介绍了由尼古拉伊·杜罗夫博士编写的原始文档,全面描述了TON的所有部分。 + +## 原始文档 + +:::info +请注意,此处及以后的代码、注释和/或文档可能包含“gram”,“nanogram”,等的参数、方法和定义,这是Telegram公司开发的原始TON代码的遗留。Gram加密货币从未发行。TON的货币是Toncoin,TON测试网络的货币是Test Toncoin。 +::: + +- [TON 白皮书](https://docs.ton.org/ton.pdf) + + TON白皮书 - 对TON(The Open Network)区块链的概括性描述。 + +- [TON虚拟机](https://docs.ton.org/tvm.pdf) + + 关于TON虚拟机的描述(可能包含关于OP代码的过时信息,实际在[TVM指令](https://docs.ton.org/learn/tvm-instructions/tvm-overview)这一部分)。 + +- [TON区块链](https://docs.ton.org/tblkch.pdf) + + 关于Telegram开放网络区块链描述(可能包含过时信息)。 + +- [Catchain共识协议](https://docs.ton.org/catchain.pdf) + + 描述了TON区块链在创建新区块时采用的拜占庭容错(BFT)共识协议。 + +- [Fift文档](https://docs.ton.org/fiftbase.pdf) + + 关于Fift语言的描述以及在TON中如何使用它的说明。 + +## 翻译 + +- **\[RU]** [korolyow/ton_docs_ru](https://github.com/Korolyow/TON_docs_ru) — 俄文版TON白皮书。 (*此版本由社区制作,TON基金会无法保证翻译质量*) +- **\[繁体中文]** [awesome-doge/the-open-network-whitepaper](https://github.com/awesome-doge/TON_Paper/blob/main/zh_ton.pdf) — 繁体中文版TON白皮书。 (*由社区制作,TON基金会无法保证翻译质量*) +- **\[简体中文]** [kojhliang/Ton_White_Paper_SC](https://github.com/kojhliang/Ton_White_Paper_SC/blob/main/Ton%E5%8C%BA%E5%9D%97%E9%93%BE%E7%99%BD%E7%9A%AE%E4%B9%A6_%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%E7%89%88.pdf) — 简体中文版TON白皮书。 (*由社区制作,TON基金会无法保证翻译质量*) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/README.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/README.mdx new file mode 100644 index 0000000000..21cf87b55d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/README.mdx @@ -0,0 +1,133 @@ +import Button from '@site/src/components/button' + +# 入门指南 + +在深入研究DApps之前,请确保您理解底层的区块链原理。我们的[The Open Network](/learn/introduction)和[Blockchain of Blockchains](/learn/overviews/ton-blockchain)文章可能会对您有所帮助。 + +与单纯的[智能合约](/develop/smart-contracts/)开发相比,DApps开发更为复杂。您需要了解[如何使用APIs](/develop/dapps/apis/),如何使用[TON Connect](/develop/dapps/ton-connect/overview)进行用户认证,以及如何使用[SDKs](/develop/dapps/apis/sdk)与区块链交互。 + +DApp 通常可以用任何具有 TON SDK 的编程语言编写。实际上,最常见的选择是网站,其次是 Telegram 小应用程序。 + + + + + +## TON 课程:DApps + +[TON Blockchain 课程](https://stepik.org/course/201638/)是TON Blockchain 开发的综合指南。 + +模块 5 和 6 完全涵盖 DApps 开发。您将学习如何创建 DApp、如何使用 TON Connect、如何使用 SDK 以及如何使用区块链。 + + + + + + + + + + + +## 基本工具和资源 + +以下是您在 DApp 开发过程中需要的一些关键资源: + +1. [开发者钱包](/participate/wallets/apps) +2. [区块链浏览器](/participate/explorers) +3. [API 文档](/develop/dapps/apis/) +4. [各种语言的 SDK](/develop/dapps/apis/sdk) +5. [使用测试网](/develop/smart-contracts/environment/testnet) +6. [TON解冻器](https://unfreezer.ton.org/) + +### 资产管理 + +使用资产?这些指南涵盖了基本要素: + +- [支付处理](/develop/dapps/asset-processing/) +- [代币(Jetton)处理](/develop/dapps/asset-processing/jettons) +- [处理NFTs](/develop/dapps/asset-processing/nft) +- [解析元数据](/develop/dapps/asset-processing/metadata) + +### DeFi 入门 + +对去中心化金融(DeFi)感兴趣?下面介绍如何处理不同类型的资产: + +- [理解Toncoin](/develop/dapps/defi/coins) +- [代币:Jettons 和 NFT](/develop/dapps/defi/tokens) +- [TON 支付](/develop/dapps/defi/ton-payments) +- [设置订阅](/develop/dapps/defi/subscriptions) + +## 教程 & 案例 + +### DeFi 基础知识 + +- 创建您的第一个代币:[铸造您的第一个Jetton](/develop/dapps/tutorials/jetton-minter) +- 逐步操作:[NFT系列铸造](/develop/dapps/tutorials/collection-minting) + +### JavaScript + +#### JavaScript + +- [付款流程](https://github.com/toncenter/examples) +- [TON Bridge](https://github.com/ton-blockchain/bridge) +- [网络钱包](https://github.com/toncenter/ton-wallet) +- [饺子销售机器人](/develop/dapps/tutorials/accept-payments-in-a-telegram-bot-js) + +#### Python + +- [项目示例](https://github.com/psylopunk/pytonlib/tree/main/examples) +- [商店前端机器人](/develop/dapps/tutorials/accept-payments-in-a-telegram-bot) + +#### GO + +- [示例](https://github.com/xssnick/tonutils-go/tree/master/example) + +### 高级主题 + +- [零知识证明](/develop/dapps/tutorials/simple-zk-on-ton) + +### 钱包实例 + +- [桌面标准钱包(C++ 和 Qt)](https://github.com/ton-blockchain/wallet-desktop) +- [Android标准钱包(Java)](https://github.com/ton-blockchain/wallet-android) +- [iOS标准钱包(Swift)](https://github.com/ton-blockchain/wallet-ios) +- [TonLib CLI(C++)](https://github.com/ton-blockchain/ton/blob/master/tonlib/tonlib/tonlib-cli.cpp) + +## 👨‍💻 贡献 + +缺少某些关键材料?您可以自己编写教程,或者为社区描述问题。 + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/api-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/api-types.md new file mode 100644 index 0000000000..a3719c0059 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/api-types.md @@ -0,0 +1,82 @@ +# API 类型 + +**高可用性区块链API是在TON上安全、便捷、快速开发有效应用程序的核心元素。** + +- [TON HTTP API](/develop/dapps/apis/toncenter) — 允许处理_索引化区块链信息_的API。 +- [TON ADNL API](/develop/dapps/apis/adnl) — 基于ADNL协议的与TON通信的安全API。 + +## Toncenter APIs + +- [TON Index](https://toncenter.com/api/v3/) - TON Index从全节点收集数据到PostgreSQL数据库,并提供方便的API来访问索引化的区块链。 +- [toncenter/v2](https://toncenter.com/) - 此API通过HTTP访问TON区块链 - 获取账户和钱包信息,查询区块和交易,向区块链发送消息,调用智能合约的get方法等。 + +## 第三方 APIs + +- [tonapi.io](https://docs.tonconsole.com/tonapi/api-v2) - 快速索引API,提供关于账户、交易、区块的基本数据,以及NFT、拍卖、Jettons、TON DNS、订阅等应用特定数据。它还提供交易链的注释数据。 +- [dton.io](https://dton.io/graphql/) - GraphQL API,可以提供关于账户、交易和区块的数据,以及关于NFT、拍卖、Jettons和TON DNS的应用特定数据。 +- [ton-api-v4](https://mainnet-v4.tonhubapi.com) - 另一个专注于通过CDN的积极缓存以提高速度的轻量级API。 +- [docs.nftscan.com](https://docs.nftscan.com/reference/ton/model/asset-model) - TON区块链的NFT API。 +- [evercloud.dev](https://ton-mainnet.evercloud.dev/graphql) - 用于TON的基本查询的GraphQL API。 +- [everspace.center](https://everspace.center/toncoin) - 用于访问TON区块链的简单RPC API。 + +## 其他 API + +### Toncoin 汇率 API + +- https://tonapi.io/v2/rates?tokens=ton¤cies=ton%2Cusd%2Crub +- https://coinmarketcap.com/api/documentation/v1/ +- https://apiguide.coingecko.com/getting-started + +### 地址转换 APIs + +:::info +最好通过本地算法转换地址,更多信息请阅读文档中的[地址](/learn/overviews/addresses)部分。 +::: + +#### 从友好形式到原始形式 + +/api/v2/unpackAddress + +Curl + +```curl +curl -X 'GET' \ +'https://toncenter.com/api/v2/unpackAddress?address=EQApAj3rEnJJSxEjEHVKrH3QZgto_MQMOmk8l72azaXlY1zB' \ +-H 'accept: application/json' +``` + +响应正文 + +```curl +{ +"ok": true, +"result": "0:29023deb1272494b112310754aac7dd0660b68fcc40c3a693c97bd9acda5e563" +} +``` + +#### 从友好形式到原始形式 + +/api/v2/packAddress + +Curl + +```curl +curl -X 'GET' \ +'https://toncenter.com/api/v2/packAddress?address=0%3A29023deb1272494b112310754aac7dd0660b68fcc40c3a693c97bd9acda5e563' \ +-H 'accept: application/json' +``` + +响应正文 + +```json +{ + "ok": true, + "result": "EQApAj3rEnJJSxEjEHVKrH3QZgto/MQMOmk8l72azaXlY1zB" +} +``` + +## 参阅 + +- [TON HTTP API](/develop/dapps/apis/toncenter) +- [SDK列表](/develop/dapps/apis/sdk) +- [TON 开发手册](/develop/dapps/cookbook) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/sdk.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/sdk.mdx new file mode 100644 index 0000000000..aa89463e6d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/sdk.mdx @@ -0,0 +1,77 @@ +# SDKs + +通过右侧边栏即时导航首选语言。 + +## 概览 + +有不同的方式连接到区块链: + +1. RPC 数据提供程序或其他应用程序接口:在大多数情况下,您必须\*依赖于其稳定性和安全性。 +2. ADNL 连接:您正在连接一个 [liteserver](/participate/run-nodes/liteserver)。它们可能无法访问,但通过一定程度的验证(在库中实现),无法作恶。 +3. Tonlib 二进制:您也在连接 liteserver,因此所有优点和缺点都适用,但您的应用程序还包含一个外部编译的动态加载库。 +4. 仅链下。此类 SDK 可以创建单元格并将其序列化,然后发送给 API。 + +### TypeScript / JavaScript + +| 库 | 区块链连接 | 说明 | +| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| [ton](https://github.com/ton-org/ton) | 通过 RPC ([Orbs](https://www.orbs.com/ton-access/) / [Toncenter](https://toncenter.com/api/v2/) / 等) | 方便的客户端库,带有钱包包装器,用于在 TON 区块链上开发 dApp。 | +| [tonweb](https://github.com/toncenter/tonweb) | 通过 RPC ([Orbs](https://www.orbs.com/ton-access/) / [Toncenter](https://toncenter.com/api/v2/) / 等) | 旧式 TON JS SDK,外部依赖性极低,已在生产中进行过广泛测试。 | +| [tonkite/adnl](https://github.com/tonkite/adnl) | [ADNL](/develop/network/adnl-tcp) 本地/通过 WebSocket | ADNL TypeScript 实现。 | +| [tonutils](https://github.com/thekiba/tonutils) | 本地 [ADNL](/develop/network/adnl-tcp) | 基于 TypeScript 的界面,用于构建 TON 生态系统中的应用程序并与之交互。由于依赖于本地 ADNL,因此不能用于浏览器中的区块链交互。 | + +### Java + +| 库 | 区块链连接 | 说明 | +| ------------------------------------------ | ---------- | -------------------------------------- | +| [ton4j](https://github.com/neodix42/ton4j) | Tonlib 二进制 | 开放网络 (TON) Java SDK | + +### Python + + + +| 库 | 区块链连接 | 说明 | +| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| [pytoniq](https://github.com/yungwine/pytoniq) | 本地 ADNL | 带有本地 LiteClient 和其他基于 ADNL 协议实现的 Python SDK。 | +| [pytoniq-core](https://github.com/yungwine/pytoniq-core) | _仅链下_ | Python 强大的免传输 SDK | +| [pytonlib](https://github.com/toncenter/pytonlib) | Tonlib 二进制 | 这是一个基于 libtonlibjson 的独立 Python 库,是 TON monorepo 的二进制依赖库。 | +| [mytonlib](https://github.com/igroman787/mytonlib) | 本地 ADNL | 用于使用开放网络的本地 Python SDK 库 | +| [TonTools](https://github.com/yungwine/TonTools) | 通过 RPC ([Orbs](https://www.orbs.com/ton-access/) / [Toncenter](https://toncenter.com/api/v2/) / 等) | TonTools 是 Python 的高级 OOP 库,可用于与 TON 区块链交互。 | +| [tonpy](https://github.com/disintar/tonpy) | 本地 ADNL | Python 软件包提供与 TON 区块链交互的数据结构和应用程序接口。 | +| [tvm_valuetypes](https://github.com/toncenter/tvm_valuetypes) | _仅链下_ | 库是处理 TVM 类型的工具集合。 | +| [pytvm](https://github.com/yungwine/pytvm) | _链下_ | 使用与 C++ 标准模拟器绑定的 Python TVM 模拟器 | + +### C# + +| 库 | 区块链连接 | 说明 | +| ----------------------------------------------------------------- | ------------- | ------------------------------------------------------------- | +| [TonSdk.NET](https://github.com/continuation-team/TonSdk.NET) | 本地 ADNL 或 RPC | 开放网络的本地 C# SDK。 | +| [justdmitry/TonLib.NET](https://github.com/justdmitry/TonLib.NET) | Tonlib 二进制 | 开放网络的 .NET SDK,通过 libtonlibjson 作为 TON monorepo 的二进制依赖关系进行连接。 | + +### Rust + +| 库 | 区块链连接 | 说明 | +| ------------------------------------------------------------- | ---------- | ------------------------------------------------------------ | +| [tonlib-rs](https://github.com/ston-fi/tonlib-rs) | Tonlib 二进制 | 开放网络的 Rust SDK,带来 TON monorepo 的二进制依赖性。 | +| [getgems-io/ton-grpc](https://github.com/getgems-io/ton-grpc) | Tonlib 二进制 | tonlibjson 的 Rust 绑定(因此取决于 TON monorepo 中的二进制文件)以及在其基础上构建的服务 | + +### Go + +| 库 | 区块链连接 | 说明 | +| -------------------------------------------------------- | ---------- | ----------------------- | +| [tonutils-go](https://github.com/xssnick/tonutils-go) | 本地 ADNL | 用于与 TON 区块链交互的 Golang 库 | +| [tongo](https://github.com/tonkeeper/tongo) | 本地 ADNL | TON 区块链库的 Go 实现 | +| [tonlib-go](https://github.com/ton-blockchain/tonlib-go) | Tonlib 二进制 | libtonlibjson 的官方绑定 | + +### 其他语言的 SDK + +| 库 | 语言 | 区块链连接 | 说明 | | +| --------------------------------------------------------------------------- | ------ | ----------- | -------------------------------------------- | - | +| [ton-kotlin](https://github.com/ton-community/ton-kotlin) | Kotlin | 本地 ADNL | 开放网络的 Kotlin/多平台 SDK。 | | +| [tonlib-java](https://github.com/ton-blockchain/tonlib-java) | Java | Tonlib bin | TonLib 的 JVM 封装器,可与 Java/Scala/Kotlin 等一起使用。 | | +| [ayrat555/ton](https://github.com/ayrat555/ton) | Elixir | _仅链下_ | 用于 Elixir 的 TON SDK | | +| [C++ Tonlib](https://github.com/ton-blockchain/ton/tree/master/example/cpp) | C++ | Tonlib 二进制 | TON monorepo 中智能合约交互的正式示例 | . | +| [Java Tonlib](https://github.com/ton-blockchain/tonlib-java) | Java | Tonlib 二进制 | TON monorepo 中智能合约交互的官方示例。 | | +| [labraburn/SwiftyTON](https://github.com/labraburn/SwiftyTON) | Swift | Tonlib 二进制 | 使用 async/await 对 tonlib 进行本地 Swift 封装。 | | +| [tonlib-xcframework](https://github.com/labraburn/tonlib-xcframework) | Swift | Tonlib 二进制 | 适用于 iOS 所有架构的 Tonlib 构建助手。 | | +| [labraburn/node-tonlib](https://github.com/labraburn/node-tonlib) | NodeJS | Tonlib 二进制 | 用于 NodeJS 的 C++ 附加组件,可与 tonlibjson 协同工作。 | | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-adnl-apis.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-adnl-apis.md new file mode 100644 index 0000000000..2b87508a02 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-adnl-apis.md @@ -0,0 +1,47 @@ +# TON ADNL API + +:::tip + +有不同的方式连接到区块链: + +1. RPC 数据提供商或另一个 API: 在大多数情况下,您必须依赖它的稳定性和安全性。 +2. **ADNL连接**:您正在连接一个[liteserver](/participate/run-nodes/liteserver)。它们可能无法访问,但通过一定程度的验证(在库中实现),它们无法作恶。 +3. Tonlib 二进制: 您也在连接到liteserver,因此所有的好处和不足都适用,但您的应用程序还包含了一个在外部编译的动态加载库。 +4. 链下解决。这种SDK允许创建和序列化单元格,然后您可以发送到 API。 + +::: + +客户端使用二进制协议直接连接到 Liteservers(节点)。 + +客户端下载密钥块、帐户的当前状态以及他们的 **Merkle 证明**,保证收到数据的有效性。 + +读取操作 (如get-methods 调用) 是通过启动本地TVM 并下载和验证状态进行的。 值得注意的是,无需下载区块链的完整状态, 客户端只下载操作所需的内容。 + +您可以从全局配置([Mainnet](https://ton.org/global-config.json) 或 [Testnet](https://ton.org/testnet-global.config.json) )连接到公共 Liteservers,也可以运行自己的 [Liteserver](/participate/nodes/node-types) 并使用 [ADNL SDKs](/develop/dapps/apis/sdk#adnl-based-sdks) 进行处理。 + +阅读更多关于 [Merkle 证明](/develop/data-formuls/proofs)的信息[TON白皮书](https://ton.org/ton.pdf) 2.3.10, 2.3.11。 + +公共 liteservers(来自全局配置)的存在是为了让你快速开始使用 TON。它可用于学习 TON 编程,或用于不需要 100% 正常运行时间的应用程序和脚本。 + +建设生产基础设施――建议使用准备完善的基础设施: + +- [设置自己的 liteserver](https://docs.ton.org/participate/run-nodes/fullnode#enable-liteserver-mode), +- 使用 Liteserver 高级提供商 [@liteserver_bot](https://t.me/liteserver_bot) + +## 优缺点 + +- ✅ 可靠。使用带有Merkle证明哈希的API来验证传入的二进制数据。 + +- ✅ 安全。由于它检查Merkle证明,即使使用不受信任的轻节点也可以。 + +- ✅ 快速。直接连接到TON区块链节点,而不是使用HTTP中间件。 + +- ❌ 重复。需要更多时间才能找出问题。 + +- ❌ 后端优先。与 web 前端不兼容(为非 HTTP 协议构建),或需要 HTTP-ADNL 代理。 + +## API 参考 + +请求和对服务器的响应在 [TL](/develop/data-forms/tl) schema 中描述,它允许您为某个编程语言生成一个输入的接口。 + +[TonLib TL Schema](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-http-apis.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-http-apis.md new file mode 100644 index 0000000000..03e7cad671 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/apis-sdks/ton-http-apis.md @@ -0,0 +1,52 @@ +# 基于 TON HTTP 的APIs + +:::tip + +有不同的方式连接到区块链: + +1. **RPC 数据提供商或其他方 API**:在大多数情况下,您不得不*依赖*其稳定性和安全性。 +2. ADNL 连接:您需要连接到一个 [轻服务器](/participate/run-nodes/liteserver)。它们可能有些难懂,但其中的内容经过了一定程度的验证 (已在库实现),可以保证其真实性。 +3. Tonlib 库: 同样是连接到轻服务器,因此所有优点和缺点都存在,此外您的应用程序还包含一个外部编译的动态加载库。 +4. 仅链下。此类 SDK 可以创建cells并将其序列化,然后发送给 API。 + +::: + +## 优点和缺点 + +- ✅ 习惯性且适合快速入门,这对于每个想要尝试TON的新手来说是完美的。 + +- ✅ 面向Web。非常适合与TON交易、智能合约进行Web交互。 + +- ❌ 简化。无法接收需要索引TON API的信息。 + +- ❌ HTTP中间件。您不能完全信任服务器响应,因为它们不包含_Merkle证明_来验证您的数据是真实的。 + +## RPC 节点 + +- [GetBlock节点](https://getblock.io/nodes/ton/) — 使用GetBlocks节点连接和测试您的dApps。 +- [TON Access](https://www.orbs.com/ton-access/) - 开放网络(TON)的 HTTP API。 +- [Toncenter](https://toncenter.com/api/v2/) - 由社区主办的关于API的快速启动项目(获得一个API密钥 [@tonapibot](https://t.me/tonapibot))。 +- [ton-node-docker](https://github.com/fmira21/ton-node-docker) - 使用了Docker全节点和Toncenter API。 +- [toncenter/ton-http-api](https://github.com/toncenter/ton-http-api) — 运行您自己的RPC节点。 +- [nownodes.io](https://nownodes.io/nodes) — 通过API使用NOWNodes全节点和blockbook Explorers。 +- [Chainbase](https://chainbase.com/chainNetwork/TON) — 为TON设计开发了对应的节点API和数据基础设施。 + +## Indexer + +### Toncenter TON Index + +索引器允许列出jetton钱包、NFT、某些过滤器的交易,而不仅仅是检索特定的交易。 + +- 使用公共TON Index: 用其进行开发和测试完全免费,[高级版](https://t.me/tonapibot)可用于生产环境 - [toncenter.com/api/v3/](https://toncenter.com/api/v3/)。 +- 使用[Worker](https://github.com/toncenter/ton-index-worker/tree/36134e7376986c5517ee65e6a1ddd54b1c76cdba)和[TON Index API wrapper](https://github.com/toncenter/ton-indexer)运行您自己的TON Index。 + +### GraphQL Nodes + +GraphQL 节点也可充当索引器。 + +- [tvmlabs.io](https://ton-testnet.tvmlabs.dev/graphql)(适用于 TON,本文撰写时仅适用于 testnet)--拥有各种交易/区块数据、过滤方法等。 +- [dton.io](https://dton.io/graphql) - 不仅为合约数据提供了一系列诸如"is jetton"、"is NFT"的标记参数,还可以模拟交易和对接收执行进行追踪。 + +## 其他APIs + +- [TonAPI](https://docs.tonconsole.com/tonapi/api-v2)--旨在为用户提供简化体验的应用程序接口,无需担心智能合约的低层级细节。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/README.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/README.mdx new file mode 100644 index 0000000000..fde5eee05c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/README.mdx @@ -0,0 +1,206 @@ +import Button from '@site/src/components/button' + +# 支付处理 + +本页面包含了关于在TON区块链上处理(发送和接收)数字资产的概览和具体细节。 + +关于Toncoin处理的最佳实践和评论: + +- [创建密钥对、钱包并获取钱包地址](https://github.com/toncenter/examples/blob/main/common.js) + +- [JS代码接受Toncoin存款](https://github.com/toncenter/examples/blob/main/deposits.js) + +- [JS代码从钱包中提取(发送)Toncoins](https://github.com/toncenter/examples/blob/main/withdrawals.js) + +- [详细信息](https://docs.ton.org/develop/dapps/asset-processing#global-overview) + +关于处理jettons的最佳实践: + +- [JS代码接受jettons存款](https://github.com/toncenter/examples/blob/main/deposits-jettons.js) + +- [JS代码从钱包中提取(发送)jettons](https://github.com/toncenter/examples/blob/main/withdrawals-jettons.js) + +- [详细信息](https://docs.ton.org/develop/dapps/asset-processing/jettons) + +## 其他示例 + +### 自托管服务 + +#### 社区制作 + +[Gobicycle](https://github.com/gobicycle/bicycle) 服务专注于补充用户余额和向区块链账户发送支付。支持TONs和Jettons。该服务考虑了开发人员可能遇到的许多陷阱(所有jettons的检查、正确的操作状态检查、消息重发、区块链被分片时的高负载性能)。提供简单的HTTP API、rabbit和webhook通知新支付。 + +### JavaScript + +#### 社区制作 + +使用TON社区支持的ton.js SDK: + +- [创建钱包,获取余额,进行转账](https://github.com/ton-community/ton#usage) + +### Python + +#### 社区制作 + +使用psylopunk/pytonlib(The Open Network的简单Python客户端): + +- [发送交易](https://github.com/psylopunk/pytonlib/blob/main/examples/transactions.py) + +使用tonsdk库(类似于tonweb): + +- [初始化钱包,创建外部消息部署钱包](https://github.com/tonfactory/tonsdk#create-mnemonic-init-wallet-class-create-external-message-to-deploy-the-wallet) + +### Golang + +#### 社区制作 + +- [查看完整示例列表](https://github.com/xssnick/tonutils-go#how-to-use) + +## 全局概览 +TON区块链采用完全异步的方法,涉及一些与传统区块链不同的概念。特别是,任何参与者与区块链的每次互动都包括在智能合约和/或外部世界之间异步传输消息。任何互动的常见路径始于向`钱包`智能合约发送外部消息,该合约使用公钥密码学认证消息发送者,负责支付费用,并发送内部区块链消息。因此,在TON网络上的交易不等同于用户与区块链的互动,而仅是消息图的节点:智能合约接受和处理消息的结果,可能会或可能不会产生新消息。互动可能包括任意数量的消息和交易,并持续一段较长的时间。技术上,带有消息队列的交易被聚合到由验证者处理的区块中。TON区块链的异步性质**不允许在发送消息阶段预测交易的哈希和lt(逻辑时间)**。被接受到区块中的交易是最终的,且不能被修改。 + +**每个内部区块链消息都是从一个智能合约到另一个智能合约的消息,携带一定数量的数字资产以及任意部分数据。** + + + +智能合约指南建议将以32个二进制零开头的数据负载视为可读文本消息。大多数软件,如钱包和库,支持此规范,并允许在Toncoin中发送文本评论以及显示其他消息中的评论。 + +智能合约**支付交易费用**(通常来自输入消息的余额)以及**存储合约存储的代码和数据的存储费用**。费用取决于workchain配置,`masterchain`上的最大费用明显低于`basechain`。 + +## TON 上的数字资产 +TON拥有三种类型的数字资产。 +- Toncoin,网络的主要代币。它用于区块链上的所有基本操作,例如支付gas费或用于验证的质押。 +- 本地代币,这是可以附加到网络上任何消息的特殊类型资产。由于发行新本地代币的功能已关闭,这些资产目前未被使用。 +- 合约资产,如代币和NFT,类似于ERC-20/ERC-721标准,由任意合约管理,因此可能需要自定义处理规则。你可以在[处理NFTs](/develop/dapps/asset-processing/nfts)和[处理Jettons](/develop/dapps/asset-processing/jettons)文章中找到更多信息。 + +### 简单的 Toncoin 转账 +要发送Toncoin,用户需要通过外部消息发送请求,即从外部世界到区块链的消息,到一个特殊的`钱包`智能合约(见下文)。接收到此请求后,`钱包`将发送带有所需资产量和可选数据负载的内部消息,例如文本评论。 + +## 钱包智能合约 +钱包智能合约是TON网络上的合约,其任务是允许区块链外的参与者与区块链实体互动。通常,它解决三个挑战: +* 认证所有者:拒绝处理和支付非所有者请求的费用。 +* 重放保护:禁止重复执行一个请求,例如向某个智能合约发送资产。 +* 启动与其他智能合约的任意互动。 + +解决第一个挑战的标准解决方案是公钥密码学:`钱包`存储公钥并检查传入消息是否由相应的私钥签名,而该私钥仅由所有者知晓。第三个挑战的解决方案也很常见;通常,请求包含`钱包`向网络发送的完整内部消息。然而,对于重放保护,有几种不同的方法。 + +### 基于 Seqno 的钱包 +基于Seqno的钱包采用最简单的消息排序方法。每条消息都有一个特殊的`seqno`整数,必须与`钱包`智能合约中存储的计数器相符。`钱包`在每个请求上更新其计数器,从而确保一个请求不会被重复处理。有几个`钱包`版本在公开可用方法方面有所不同:限制请求的过期时间的能力,以及拥有相同公钥的多个钱包的能力。然而,这种方法的固有要求是逐一发送请求,因为`seqno`序列中的任何间隙都将导致无法处理所有后续请求。 + +### 高负载钱包 +这种类型的`钱包`采用基于存储智能合约存储中非过期处理请求的标识符的方法。在这种方法中,任何请求都会被检查是否是已处理请求的重复,如果检测到重放,则丢弃。由于过期,合约可能不会永远存储所有请求,但它会删除由于过期限制而无法处理的请求。向此`钱包`发送请求可以并行进行,彼此不干扰;然而,这种方法需要更复杂的请求处理监控。 + +## 与区块链的互动 +可以通过TonLib在TON区块链上进行基本操作。TonLib是一个共享库,可以与TON节点一起编译,并通过所谓的lite服务器(轻客户端服务器)公开API以与区块链互动。TonLib通过检查所有传入数据的证明采取无信任方法;因此,不需要可信数据提供者。TonLib的可用方法列在[TL方案中](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L234)。它们可以通过像[pyTON](https://github.com/EmelyanenkoK/pyTON)或[tonlib-go](https://github.com/mercuryoio/tonlib-go/tree/master/v2)(技术上这些是`tonlibjson`的包装器)这样的包装器或通过`tonlib-cli`使用共享库。 + +## 钱包部署 +要通过TonLib部署钱包,需要: +1. 通过[createNewKey](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L213)或其包装函数生成私钥/公钥对(例如在[tonlib-go](https://github.com/mercuryoio/tonlib-go/tree/master/v2#create-new-private-key)中)。注意,私钥是在本地生成的,不会离开主机。 +2. 形成对应于已启用`钱包`之一的[InitialAccountWallet](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L60)结构。目前可用的`wallet.v3`、`wallet.highload.v1`、`wallet.highload.v2`。 +3. 通过[getAccountAddress](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L249)方法计算新`钱包`智能合约的地址。我们建议使用默认修订版`0`,并且还在`basechain` `workchain=0`中部署钱包,以降低处理和存储费用。 +4. 向计算出的地址发送一些Toncoin。注意,您需要以`non-bounce`模式发送它们,因为该地址尚无代码,因此无法处理传入消息。`non-bounce`标志表示,即使处理失败,资金也不应通过反弹消息返回。我们不建议对其他交易使用`non-bounce`标志,尤其是在处理大笔资金时,因为反弹机制提供了一定程度的防错保护。 +5. 形成所需的[action](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L148),例如仅用于部署的`actionNoop`。然后使用[createQuery](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L255)和[sendQuery](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L260)启动与区块链的互动。 +6. 几秒钟后使用[getAccountState](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L254)方法检查合约。 + +:::tip +在[钱包教程](/develop/smart-contracts/tutorials/wallet#-deploying-a-wallet)中阅读更多 +::: + +## 接收消息价值 +要计算消息带给合约的接收值,需要解析交易。这发生在消息触及合约时。可以使用[getTransactions](https://github.com/ton-blockchain/ton/blob/master + +/tl/generate/scheme/tonlib_api.tl#L236)获得交易。对于传入钱包的交易,正确的数据包括一个传入消息和零个传出消息。否则,要么是外部消息发送到钱包,在这种情况下,所有者会花费Toncoin,要么钱包未部署,传入交易会反弹回去。 + +无论如何,一般来说,消息带给合约的金额可以计算为传入消息的价值减去传出消息的价值总和减去费用:`value_{in_msg} - SUM(value_{out_msg}) - fee`。技术上,交易表示包含三个不同的带有`费用`名称的字段:`费用`、`存储费用`和`其他费用`,即总费用、与存储成本相关的费用部分和与交易处理相关的费用部分。只应使用第一个。 + +## 检查合约的交易 +可以使用[getTransactions](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L236)获取合约的交易。此方法允许从某个`transactionId`和更早的时间获取10笔交易。要处理所有传入交易,应遵循以下步骤: +1. 最新的`last_transaction_id`可以使用[getAccountState](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L235)获得 +2. 应通过`getTransactions`方法加载10笔交易。 +3. 应处理此列表中未见过的交易。 +4. 传入支付是传入消息具有来源地址的交易;传出支付是传入消息没有来源地址并且还存在传出消息的交易。这些交易应相应处理。 +5. 如果这10笔交易都是未见过的,应加载接下来的10笔交易,重复步骤2、3、4、5。 + +## 接受支付 +接受支付有几种方法,它们在区分用户的方法上有所不同。 + +### 基于发票的方法 +要基于附加评论接受支付,服务应: +1. 部署`钱包`合约。 +2. 为每个用户生成唯一的`发票`。uuid32的字符串表示形式就足够了。 +3. 用户应被指示向服务的`钱包`合约发送Toncoin,并附加`发票`作为评论。 +4. 服务应定期轮询`钱包`合约的getTransactions方法。 +5. 对于新交易,应提取传入消息,将`评论`与数据库匹配,并将值(见**接收消息价值**段落)存入用户账户。 + +## 发票 + +### 带有ton://链接的发票 + +如果您需要为简单用户流程进行简便集成,使用ton://链接是合适的。 +最适合一次性支付和发票。 + +```bash +ton://transfer/? + [nft=&] + [fee-amount=&] + [forward-amount=] +``` + +- ✅ 简单集成 +- ✅ 无需连接钱包 + +- ❌ 用户需要为每次支付扫描新的二维码 +- ❌ 无法追踪用户是否已签署交易 +- ❌ 关于用户地址的信息 +- ❌ 在某些平台不可点击此类链接(例如Telegram桌面客户端的机器人消息)时需要变通方法 + + + + + +### 带有 TON Connect 的发票 + +最适合需要在会话中签署多个支付/交易的dApps,或需要一段时间保持与钱包的连接。 + +- ✅ 与钱包有永久通信 + +渠道,关于用户地址的信息 +- ✅ 用户只需扫描一次二维码 +- ✅ 可以了解用户在钱包中是否确认了交易,通过返回的BOC追踪交易 +- ✅ 不同平台的现成SDK和UI工具包 + +- ❌ 如果您只需要发送一次支付,用户需要进行两个操作:连接钱包和确认交易 +- ❌ 集成比ton://链接更复杂 + + + + +## 发送支付 + +1. 服务应部署`钱包`并保持其资金,以防止合约因存储费用而被销毁。注意,存储费通常少于每年1 Toncoin。 +2. 服务应从用户获取`destination_address`和可选的`comment`。注意,目前我们建议要么禁止未完成的同一(`destination_address`、`value`、`comment`)集合的传出支付,要么适当安排这些支付;这样,下一个支付只有在前一个确认后才启动。 +3. 用`comment`作为文本形成[msg.dataText](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L98)。 +4. 形成包含`destination_address`、空`public_key`、`amount`和`msg.dataText`的[msg.message](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L108)。 +5. 形成包含一组传出消息的[Action](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L149)。 +6. 使用[createQuery](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L255)和[sendQuery](https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl#L260)查询发送传出支付。 +7. 服务应定期轮询`钱包`合约的getTransactions方法。通过(`destination_address`、`value`、`comment`)匹配确认的交易与传出支付,可以将支付标记为完成;检测并向用户显示相应的交易哈希和lt(逻辑时间)。 +8. 对`v3`或`high-load`钱包的请求默认有60秒的过期时间。在此时间后,未处理的请求可以安全地重新发送到网络(见步骤3-6)。 + +## 浏览器 + +区块链浏览器是https://tonscan.org。 + +要在浏览器中生成交易链接,服务需要获取lt(逻辑时间)、交易哈希和账户地址(通过getTransactions方法检索到的用于lt和txhash的账户地址)。然后https://tonscan.org和https://explorer.toncoin.org/可以以以下格式显示该tx的页面: + +`https://tonscan.org/tx/{lt as int}:{txhash as base64url}:{account address}` + +`https://explorer.toncoin.org/transaction?account={account address}<={lt as int}&hash={txhash as base64url}` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/jettons.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/jettons.mdx new file mode 100644 index 0000000000..ba6ec151d1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/jettons.mdx @@ -0,0 +1,605 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Button from '@site/src/components/button'; + +# 处理 TON Jetton + +处理 jettons 的最佳实践及注释: + +- [接受 jettons 存款的 JS 算法](https://github.com/toncenter/examples/blob/main/deposits-jettons.js) + +- [jettons 提款的 JS 算法](https://github.com/toncenter/examples/blob/main/withdrawals-jettons.js) + +在大多数情况下,这应该足够你使用,如果不够,你可以在下面找到详细信息。 + +## 内容列表 + +本文档依次描述了以下内容: +1. 概览 +2. 架构 +3. Jetton 主合约(代币铸造) +4. Jetton 钱包合约(用户钱包) +5. 消息布局 +6. Jetton 处理(链下) +7. Jetton 处理(链上) +8. 钱包处理 +9. 最佳实践 + +## 概览 + +:::info +为了清晰理解,读者应该熟悉在[我们的文档的这一部分](/develop/dapps/asset-processing/)描述的资产处理的基本原理。特别重要的是要熟悉[合约](/learn/overviews/addresses#everything-is-a-smart-contract)、[钱包](/develop/smart-contracts/tutorials/wallet)、[消息](/develop/smart-contracts/guidelines/message-delivery-guarantees)和部署过程。 +::: + +快速跳转到 jetton 处理的核心描述: + + + + + + + + +



+ + +TON 区块链及其底层生态系统将可替代代币(FTs)分类为 jettons。因为 TON 区块链应用了分片,我们对可替代代币的实现在与类似的区块链模型相比时是独特的。 + +在这项分析中,我们深入探讨了详细规定 jetton [行为](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md)和[元数据](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md)的正式标准。 +我们还在我们的[分片聚焦的 jetton 架构概述博客文章](https://blog.ton.org/how-to-shard-your-ton-smart-contract-and-why-studying-the-anatomy-of-tons-jettons)中提供了关于 jetton 架构的非正式详情。 +我们还讨论了我们的第三方开源 TON 支付处理程序([bicycle](https://github.com/gobicycle/bicycle))的特定详情,该处理程序允许用户使用单独的存款地址存取 Toncoin 和 jettons,无需使用文本备注。 + + +## Jetton 架构 + +TON 上的标准化代币使用一组智能合约来实现,包括: +* [Jetton 主智能合约](https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-minter.fc) +* [Jetton 钱包智能合约](https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-wallet.fc) + +

+
+ contracts scheme +
+

+ +## Jetton 主智能合约 +Jetton 主智能合约存储了有关 jetton 的常见信息(包括总供应量、元数据链接或元数据本身)。 + +任何用户都可能创建一个伪造的、与原始 jetton 几乎相同的有价值的 jetton 克隆(使用任意名称、票证、图像等)。幸运的是,伪造的 jettons 可以通过它们的地址轻松识别。 + +为了消除 TON 用户的欺诈可能性,请查找特定 jetton 类型的原始 jetton 地址(Jetton 主合约),或关注项目的官方社交媒体频道或网站以找到正确信息。检查资产以消除 [Tonkeeper ton-assets list](https://github.com/tonkeeper/ton-assets)的欺诈可能性。 + +### 检索 Jetton 数据 + +要检索更具体的 Jetton 数据,使用 `get_jetton_data()` 获取方法。 + +此方法返回以下数据: + +| 名称 | 类型 | 描述 | +|--------------------|-------|-------------------- | +| `total_supply` | `int` | 以不可分割的单位衡量的发行的 jettons 总数。 | +| `mintable` | `int` | 详情是否可以铸造新的 jettons。此值为 -1(可以铸造)或 0(不能铸造)。 | +| `admin_address` | `slice` | | +| `jetton_content` | `cell` | 根据 [TEP-64](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) 的数据。 | +| `jetton_wallet_code` | `cell` | | + + + +也可以使用 [Toncenter API](https://toncenter.com/api/v3/#/default/get_jetton_masters_api_v3_jetton_masters_get) 中的方法 `/jetton/masters` 来检索已解码的 Jetton 数据和元数据。我们还为 (js) [tonweb](https://github.com/toncenter/tonweb/blob/master/src/contract/token/ft/JettonMinter.js#L85) 和 (js) [ton-core/ton](https://github.com/ton-core/ton/blob/master/src/jetton/JettonMaster.ts#L28),(go) [tongo](https://github.com/tonkeeper/tongo/blob/master/liteapi/jetton.go#L48) 和 (go) [tonutils-go](https://github.com/xssnick/tonutils-go/blob/33fd62d754d3a01329ed5c904db542ab4a11017b/ton/jetton/jetton.go#L79),(python) [pytonlib](https://github.com/toncenter/pytonlib/blob/d96276ec8a46546638cb939dea23612876a62881/pytonlib/client.py#L742) 以及许多其他 SDK 开发了方法。 + +使用 [Tonweb](https://github.com/toncenter/tonweb) 运行 get 方法并获取链下元数据的 url 的示例: + +```js +import TonWeb from "tonweb"; +const tonweb = new TonWeb(); +const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: ""}); +const data = await jettonMinter.getJettonData(); +console.log('Total supply:', data.totalSupply.toString()); +console.log('URI to off-chain metadata:', data.jettonContentUri); +``` + +#### Jetton 元数据 +[这里](/develop/dapps/asset-processing/metadata)提供了有关解析元数据的更多信息。 + +## Jetton 钱包智能合约 +Jetton 钱包合约用于发送、接收和销毁 jettons。每个 _jetton 钱包合约_ 存储特定用户的钱包余额信息。 +在特定情况下,jetton 钱包用于每种 jetton 类型的个别 jetton 持有者。 + +Jetton 钱包不应与仅用于区块链交互和只存储 Toncoin 资产(例如,v3R2 钱包、高负载钱包等)的钱包混淆,它负责支持和管理只有特定 jetton 类型的。 + +Jetton 钱包使用智能合约,并通过所有者钱包和 jetton 钱包之间的内部消息进行管理。例如,如果 Alice 管理着一个内有 jettons 的钱包,方案如下:Alice 拥有一个专门用于 jetton 使用的钱包(例如钱包版本 v3r2)。当 Alice 启动在她管理的钱包中发送 jettons 时,她向她的钱包发送外部消息,因此,_她的钱包_ 向 _她的 jetton 钱包_ 发送内部消息,然后 jetton 钱包实际执行代币转移。 + +### 检索给定用户的 Jetton 钱包地址 +要使用所有者地址(TON 钱包地址)检索 jetton 钱包地址, +Jetton 主合约提供了 get 方法 `get_wallet_address(slice owner_address)`。 + +#### 使用 API 检索 +应用程序使用 [Toncenter API](https://toncenter.com/api/v3/#/default/run_get_method_api_v3_runGetMethod_post) 的 `/runGetMethod` 方法,通过将所有者的地址序列化到 cell 中。 + +#### 使用 SDK 检索 +也可以通过使用我们各种 SDK 中的现成方法启动此过程,例如,使用 Tonweb SDK,可以通过输入以下字符串启动此过程: + +```js +import TonWeb from "tonweb"; +const tonweb = new TonWeb(); +const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: ""}); +const address = await jettonMinter.getJettonWalletAddress(new TonWeb.utils.Address("")); +// 检查钱包是否真的属于所需的 Jetton 主要非常重要: +const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider, { + address: jettonWalletAddress +}); +const jettonData = await jettonWallet.getData(); +if (jettonData.jettonMinterAddress.toString(false) !== new TonWeb.utils.Address(info.address).toString(false)) { + throw new Error('jetton minter address from jetton wallet doesnt match config'); +} + +console.log('Jetton 钱包地址:', address.toString(true, true, true)); +``` +:::tip +要了解更多示例,请阅读 [TON 开发手册](/develop/dapps/cookbook#how-to-calculate-users-jetton-wallet-address)。 +::: + +### 检索特定 Jetton 钱包的数据 + +要检索钱包的账户余额、所有者识别信息以及与特定 jetton 钱包合约相关的其他信息,jetton 钱包合约内使用 `get_wallet_data()` get 方法。 + + +此方法返回以下数据: + +| 名称 | 类型 | +|--------------------|-------| +| balance | int | +| owner | slice | +| jetton | slice | +| jetton_wallet_code | cell | + +也可以使用 [Toncenter API](https://toncenter.com/api/v3/#/default/get_jetton_wallets_api_v3_jetton_wallets_get) 的 `/jetton/wallets` get 方法来检索先前解码的 jetton 钱包数据(或 SDK 中的方法)。例如,使用 Tonweb: + +```js +import TonWeb from "tonweb"; +const tonweb = new TonWeb(); +const walletAddress = "EQBYc3DSi36qur7-DLDYd-AmRRb4-zk6VkzX0etv5Pa-Bq4Y"; +const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider,{address: walletAddress}); +const data = await jettonWallet.getData(); +console.log('Jetton 余额:', data.balance.toString()); +console.log('Jetton 所有者地址:', data.ownerAddress.toString(true, true, true)); +// 检查 Jetton 主是否真的识别钱包非常重要 +const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: data.jettonMinterAddress.toString(false)}); +const expectedJettonWalletAddress = await jettonMinter.getJettonWalletAddress(data.ownerAddress.toString(false)); +if (expectedJettonWalletAddress.toString(false) !== new TonWeb.utils.Address(walletAddress).toString(false)) { + throw new Error('jetton minter does not recognize the wallet'); +} + +console.log('Jetton 主地址:', data.jettonMinterAddress.toString(true, true, true)); +``` + +### Jetton 钱包部署 +在钱包之间转移 jettons 时,交易(消息)需要一定量的 TON作为网络gas费和根据 Jetton 钱包合约代码执行操作的支付。这意味着接收者在接收 jettons 之前不需要部署 jetton 钱包。只要发送方的钱包中有足够的 TON支付所需的gas费,接收者的 jetton 钱包将自动部署。 + +## 消息布局 + +:::tip 消息 +阅读更多关于消息的信息[这里](/develop/smart-contracts/guidelines/message-delivery-guarantees)。 +::: + +Jetton 钱包和 TON 钱包之间的通信是通过以下通信序列进行的: + +![](/img/docs/asset-processing/jetton_transfer.svg) + + +`发件人 -> 发件人' jetton 钱包` 意味着 _转移_ 消息体包含以下数据: + + +| 名称 | 类型 | +|----------------------|---------| +| `query_id ` | uint64 | +| `amount ` | coins | +| `destination ` | address | +| `response_destination` | address | +| `custom_payload ` | cell | +| `forward_ton_amount` | coins | +| `forward_payload` | cell | + +`收款人' jetton 钱包 -> 收款人` 意味着消息通知体包含以下数据: + + +| 名称 | 类型 | +|-----------------|---------| +| query_id ` | uint64 | +| amount ` | coins | +| sender ` | address | +| forward_payload` | cell | + +`收款人' jetton 钱包 -> 发件人` 意味着剩余消息体包含以下数据: + + +| 名称 | 类型 | +|----------------------|----------------| +| `query_id` | uint64 | + +有关 jetton 钱包合约字段的详细说明可以在 [TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) Jetton 标准接口描述中找到。 + +使用 `Transfer notification` 和 `Excesses` 参数的消息是可选的,取决于附着在 `Transfer` 消息上的 TON 的数量以及 `forward_ton_amount` 字段的值。 + +`query_id` 标识符允许应用程序将三种消息类型 `Transfer`、 `Transfer notification` 和 `Excesses` 相互关联。为了正确执行此过程,建议始终使用唯一的查询 id。 + +### 如何发送附带评论和通知的 Jetton 转账 + +为了进行附带通知的转账(随后在钱包内用于通知目的),必须通过设置非零的 `forward_ton_amount` 值附加足够数量的TON到发送的消息中,并且如有必要,将文本评论附加到 `forward_payload`。文本评论的编码方式与发送Toncoin时的文本评论类似。 + +[发送Jettons的费用](https://docs.ton.org/develop/smart-contracts/fees#fees-for-sending-jettons) + +然而,佣金取决于几个因素,包括Jetton代码详情和为接收者部署新的Jetton钱包的需要。因此,建议附加多一些Toncoin,并且然后将地址设置为 `response_destination` 以检索 `Excesses` 消息。例如,可以在将 `forward_ton_amount` 值设置为0.01 TON的同时,向消息附加0.05 TON(这个TON的数量将被附加到 `Transfer notification` 消息中)。 + +[使用Tonweb SDK的Jetton带评论转账示例](https://github.com/toncenter/tonweb/blob/b550969d960235314974008d2c04d3d4e5d1f546/src/test-jetton.js#L128): + +```js +// 前4个字节是文本评论的标签 +const comment = new Uint8Array([... new Uint8Array(4), ... new TextEncoder().encode('text comment')]); + +await wallet.methods.transfer({ + secretKey: keyPair.secretKey, + toAddress: JETTON_WALLET_ADDRESS, // Jetton发送者的Jetton钱包地址 + amount: TonWeb.utils.toNano('0.05'), // 附加到转账消息的TON总量 + seqno: seqno, + payload: await jettonWallet.createTransferBody({ + jettonAmount: TonWeb.utils.toNano('500'), // Jetton数量(以最基本的不可分割单位计) + toAddress: new TonWeb.utils.Address(WALLET2_ADDRESS), // 接收用户的钱包地址(非Jetton钱包) + forwardAmount: TonWeb.utils.toNano('0.01'), // 用于触发Transfer notification消息的一些TONs数量 + forwardPayload: comment, // Transfer notification消息的文本评论 + responseAddress: walletAddress // 扣除手续费后将TON退回给发件人的钱包地址 + }), + sendMode: 3, +}).send() +``` + +:::tip +要获得更多示例,请阅读 [TON 开发手册](/develop/dapps/cookbook#how-to-construct-a-message-for-a-jetton-transfer-with-a-comment)。 +::: + + +## Jetton 链下处理 + +:::info 交易确认 +TON交易在仅一次确认后就不可逆转。为了最佳用户体验,建议在TON区块链上的交易一旦完成后,不要等待额外的区块。在 [Catchain.pdf](https://docs.ton.org/catchain.pdf#page=3) 中阅读更多信息。 +::: + +可以有几种允许用户接收Jettons的场景。Jettons可以在一个中心化的热钱包内被接受;同样,它们也可以通过为每个独立用户设置分离地址的钱包来接受。 + +为了处理Jettons,与处理个体化的TON不同,需要一个热钱包(一个v3R2,高负载钱包)以及一个或多个Jetton钱包。Jetton热钱包的部署在我们的文档[钱包部署](/develop/dapps/asset-processing/#wallet-deployment)中有描述。就是说,不需要根据 [Jetton钱包部署](#jetton-wallet-deployment) 标准部署Jetton钱包。然而,当接收到Jettons时,会自动部署Jetton钱包,这意味着当Jettons被提取时,假定它们已经在用户的资产中。 + +出于安全原因,最好拥有对不同Jettons持有分开的热钱包(每种资产类型的多个钱包)。 + +在处理资金时,也建议提供一个冷钱包用于存储不参与自动存款和提款过程的额外资金。 + +### 添加新的 Jettons 进行资产处理和初步验证 + +1. 要找到正确的智能合约代币主地址,请参见以下来源:[如何找到正确的Jetton master合约](#jetton-master-smart-contract) +2. 此外,要检索特定Jetton的元数据,请参见以下来源:[如何接收Jetton元数据](#retrieving-jetton-data)。 + 为了正确向用户展示新的Jettons,需要正确的 `decimals` 和 `symbol`。 + +为了确保所有用户的安全,至关重要的是避免可能被伪造(假冒)的Jettons。例如,`symbol`==`TON` 的Jettons或那些包含系统通知消息的Jettons,例如:`ERROR`、`SYSTEM` 等。务必确保jettons以这样的方式在你的界面中显示,以便它们不能与TON转账、系统通知等混淆。有时,即使`symbol`、`name`和`image`被设计得几乎与原始的一模一样,也是只是希望误导用户的。 + +### 在收到转账通知消息时识别未知的 Jetton + +1. 如果在你的钱包内收到了关于未知Jetton的转账通知消息,那么你的钱包就被创建为持有特定Jetton的钱包。接下来,进行几个验证过程很重要。 +2. 包含 `Transfer notification` 体的内部消息的发送地址是新的Jetton钱包的地址。不要与 `Transfer notification` 体内的 `sender` 字段混淆,Jetton钱包的地址是消息来源的地址。 +3. 检索新的Jetton钱包的Jetton master地址:[如何检索Jetton钱包的数据](#retrieving-jetton-data)。 + 要执行此过程,需要 `jetton` 参数,即Jetton master合约的地址。 +4. 使用Jetton master合约检索你的钱包地址(作为拥有者)的Jetton钱包地址:[如何检索特定用户的Jetton钱包地址](#retrieving-jetton-wallet-addresses-for-a-given-user) +5. 将master合约返回的地址与钱包代币的实际地址进行比较。如果它们匹配,那是理想的。如果不匹配,那么你可能收到了一个假冒的欺诈代币。 +6. 检索Jetton元数据:[如何接收Jetton元数据](#retrieving-jetton-data)。 +7. 检查 `symbol` 和 `name` 字段是否为欺诈的迹象。如有必要,警告用户。[为处理和初步检查添加新的Jettons](#adding-new-jettons-for-asset-processing-and-initial-verification)。 + + +### 通过中心化钱包接收用户的 Jettons + +在这种情况下,支付服务为每个发送者创建一个唯一的备忘录标识符,公开中心化钱包的地址和发送的金额。发送者将代币发送到指定的中心化地址,并且必须在评论中包含备忘录。 + +**这种方法的优点:**这种方法非常简单,因为接受代币时没有额外的费用,并且它们直接检索到热钱包中。 + +**这种方法的缺点:**这种方法要求所有用户在转账时附加评论,这可能导致更多的存款错误(遗忘备忘录、备忘录错误等),意味着更多的支持工作量。 + +Tonweb示例: + +1. [使用评论(备忘录)接受到个人热钱包的Jetton存款](https://github.com/toncenter/examples/blob/main/deposits-jettons-single-wallet.js) +2. [Jettons提款示例](https://github.com/toncenter/examples/blob/main/jettons-withdrawals.js) + +#### 准备 + +1. 准备一个接受的Jettons列表:[添加新的Jettons以进行处理和初步验证](#adding-new-jettons-for-asset-processing-and-initial-verification)。 +2. 热钱包部署(如果预期没有Jetton提款,则使用v3R2;如果预期有Jetton提款,则使用高负载v2)[钱包部署](/develop/dapps/asset-processing/#wallet-deployment)。 +3. 使用热钱包地址执行测试Jetton转账以初始化钱包。 + +#### 处理收到的 Jettons +1. 加载接受的Jettons列表 +2. 检索你部署的热钱包的Jetton钱包地址:[如何检索特定用户的Jetton钱包地址](#retrieving-jetton-wallet-addresses-for-a-given-user) +3. 为每个Jetton钱包检索Jetton master地址:[如何检索特定Jetton钱包的信息](#retrieving-data-for-a-specific-jetton-wallet)。 + 要执行此过程,需要 `jetton` 参数(实际上是Jetton master合约的地址)。 +4. 比较步骤1和步骤3(直接上方)中的Jetton master合约地址。如果地址不匹配,则必须报告Jetton地址验证错误。 +5. 使用热钱包帐户检索最近未处理的交易列表,并对其进行迭代(逐一排序每个交易)。参见:[检查合约的交易](https://docs.ton.org/develop/dapps/asset-processing/#checking-contracts-transactions), +或使用 [Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-single-wallet.js#L43) +或使用Toncenter API `/getTransactions` 方法。 +6. 检查交易中的输入消息(in_msg)并从输入消息中检索源地址。[Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-jettons-single-wallet.js#L84) +7. 如果源地址与Jetton钱包内的地址匹配,则需要继续处理交易。如果不是,则跳过处理该交易并检查下一个交易。 +8. 确保消息体不为空,并且消息的前32位与 `transfer notification` op码 `0x7362d09c` 匹配。[Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-jettons-single-wallet.js#L91) + 如果消息体为空或op码无效 - 跳过交易。 +9. 读取消息体的其他数据,包括 `query_id`、`amount`、`sender`、`forward_payload`。[Jetton合约消息布局](#jetton-contract-message-layouts),[Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-jettons-single-wallet.js#L105) +10. 试图从 `forward_payload` 数据中检索文本评论。前32位必须与文本评论op码 `0x00000000` 匹配,剩余部分 - UTF-8编码文本。[Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-jettons-single-wallet.js#L110) +11. 如果 `forward_payload` 数据为空或op码无效 - 跳过交易。 +12. 将接收到的评论与保存的备忘录比较。如果有匹配(始终可以识别用户)- 存入转账。 +13. 从步骤5重新开始并重复该过程,直到遍历完所有交易的整个列表。 + +### 通过用户存款地址接收 Jettons + +要从用户存款地址接收Jettons,支付服务需要为发送资金的每位参与者创建其自己的个人地址(存款)。在这种情况下提供的服务涉及执行几个并行过程,包括创建新的存款、扫描区块中的交易、将资金从存款中提到热钱包,等等。 + +因为一个热钱包可以使用一个Jetton钱包为每种Jetton类型,因此需要为初始化存款创建多个钱包。为了创建大量的钱包,同时用一个种子短语(或私钥)管理它们,创建钱包时需要指定不同的 `subwallet_id`。TON上创建子钱包的功能由v3钱包及更高版本支持。 + + +#### 在 Tonweb 中创建子钱包 + +```Tonweb +const WalletClass = tonweb.wallet.all['v3R2']; +const wallet = new WalletClass(tonweb.provider, { + publicKey: keyPair.publicKey, + wc: 0, + walletId: , +}); +``` + +#### 准备 + +1. 准备一个被接受的Jettons列表:[为处理和初步检查添加新的Jettons](#adding-new-jettons-for-asset-processing-and-initial-verification) +2. 热钱包 [钱包部署](/develop/dapps/asset-processing/#wallet-deployment) + +#### 创建存款 + +1. 接受为用户创建新存款的请求。 +2. 根据热钱包种子生成一个新的子钱包(v3R2)地址。[在Tonweb中创建子钱包](#creating-a-subwallet-in-tonweb) +3. 接收地址可以给用户当做用于Jetton存款的地址(这是存款Jetton钱包的所有者地址)。钱包初始化不是必需的,这可以在从存款中提取Jettons时完成。 +4. 对于此地址,需要通过Jetton master合约计算Jetton钱包的地址。[如何检索特定用户的Jetton钱包地址](#retrieving-jetton-wallet-addresses-for-a-given-user)。 +5. 将Jetton钱包地址添加到交易监控的地址池中,并保存子钱包地址。 + +#### 处理交易 + +:::info 交易确认 +TON交易在仅一次确认后就不可逆转。为了最佳用户体验,建议在TON区块链上的交易一旦完成后,不要等待额外的区块。在 [Catchain.pdf](https://docs.ton.org/catchain.pdf#page=3) 中阅读更多信息。 +::: + +并不总是能够从消息中确定收到的Jettons的确切数量,因为Jetton钱包可能不会发送 `transfer notification`、`excesses`,而且`internal transfer`消息不是标准化的。这意味着不能保证可以解码 `internal transfer` 消息。 + +因此,要确定钱包中接收到的金额,需要使用get方法请求余额。在为特定区块链上的账户状态请求余额时,使用区块来检索关键数据。[使用Tonweb准备接收区块](https://github.com/toncenter/tonweb/blob/master/src/test-block-subscribe.js)。 + +该过程如下进行: + +1. 准备接受区块(通过准备系统接受新区块)。 +2. 检索新区块并保存前一个区块ID。 +3. 从区块中接收交易。 +4. 筛选仅使用来自存款Jetton钱包pool地址的交易。 +5. 使用`transfer notification`正文解码消息,以接收更多详细数据,包括 + `sender`地址,Jetton `amount` 和评论。 (参见:[处理传入Jettons](#处理传入-jettons)) +6. 如果至少有一个交易包含无法解码的外部消息(消息体不包含用于 + `transfer notification`的操作码和用于`excesses`的操作码)或帐户中没有外部消息,则必须使用当前区块的get方法请求Jetton余额,同时使用前一个区块来计算余额差异。现在由于区块内进行的交易,存款总余额变动被揭示出来。 +7. 作为未识别Jetton转账的标识符(没有`transfer notification`),如果有这样一个交易或区块数据存在(如果一个区块内有几个存在),则可以使用交易数据。 +8. 现在需要检查以确保存款余额是正确的。如果存款余额足够发起热钱包和现有Jetton钱包之间的转账,则需要提取Jettons以确保钱包余额减少。 +9. 从第2步重新开始并重复整个过程。 + +#### 从存款中提款 + +不应从每次存款充值时都将存款转至热钱包,因为转账操作会收取TON手续费(以网络gas费支付)。 +重要的是确定一定数量的Jettons,这些Jettons是必需的,才能使转账变得划算(从而存入)。 + +默认情况下,Jetton存款钱包的所有者不会初始化。这是因为没有预定的必须支付存储费。在发送带有 +`transfer`正文的消息时,可以部署Jetton存款钱包,然后立即销毁它。为此,工程师必须使用发送消息的特殊机制:128 + 32。 + + +1. 检索标记为要提取到热钱包的存款列表 +2. 为每个存款检索保存的所有者地址 +3. 然后将消息发送到每个所有者地址(通过将几条这样的消息组合成一批),从高负载钱包附加TON Jetton数量。这是通过添加用于v3R2钱包初始化的费用+发送带有`transfer`正文的消息的费用+任意TON数量的`forward_ton_amount` +(如有必要)。附加的TON数量是通过添加用于v3R2钱包初始化的费用(值)+ 发送带有`transfer`正文的消息的费用(值)+ 任意TON数量的 +`forward_ton_amount`(值)(如果需要)来确定的。 +4. 当地址上的余额变为非零时,帐户状态发生改变。等待几秒钟,然后检查帐户状态,它很快会从`nonexists`状态变为`uninit`。 +5. 对于每个所有者地址(处于`uninit`状态),需要发送一条带有v3R2钱包 + init和带有`transfer`消息的正文进行存入Jetton钱包的外部消息= 128 + 32。对于`transfer`, + 用户必须将热钱包地址指定为`destination`和`response destination`。 + 可以添加文字评论以简化转账识别。 +6. 可以使用存款地址到热钱包地址的Jetton传送进行验证,通过考虑 + [这里找到的处理传入Jettons信息](#处理传入-jettons)。 + +### Jetton 提款 + +要提取Jettons,钱包发送带有`transfer`正文的消息到其对应的Jetton钱包。 +然后Jetton钱包将Jettons发送给收件人。本着诚信,重要的是要附上一些TON +作为`forward_ton_amount`(并选择性附上评论到`forward_payload`)以触发`transfer notification`。 +参见:[Jetton合约消息布局](#jetton-合约消息布局) + +#### 准备 + +1. 准备用于提款的Jettons列表:[为处理和初步验证添加新的Jettons](#为资产处理和初始验证添加新的-jettons) +2. 启动热钱包部署。推荐使用Highload v2。[钱包部署](/develop/dapps/asset-processing/#wallet-deployment) +3. 使用热钱包地址进行Jetton转账,以初始化Jetton钱包并补充其余额。 + +#### 处理提款 + +1. 加载已处理的Jettons列表 +2. 检索部署的热钱包的Jetton钱包地址:[如何为给定用户检索Jetton钱包地址](#为给定用户检索-jetton-钱包地址) +3. 检索每个Jetton钱包的Jetton主地址:[如何检索Jetton钱包的数据](#检索特定-jetton-钱包的数据)。 + 需要`jetton`参数(实际上是Jetton主合约的地址)。 +4. 比较第1步和第3步中来自Jetton主合约的地址。如果地址不匹配,则应报告Jetton地址验证错误。 +5. 收到提款请求,实际上指明了Jetton的类型,转移的金额,以及收件人钱包地址。 +6. 检查Jetton钱包的余额,以确保有足够的资金进行提款。 +7. 使用Jetton `transfer`正文生成消息,并填写所需字段,包括:query_id,发出的金额, + 目的地(收件人的非Jetton钱包地址),response_destination(建议指定用户的热钱包), + forward_ton_amount(建议将其设置为至少0.05 TON以调用`transfer notification`),`forward_payload` + (可选,如果需要发送评论)。[Jetton合约消息布局](#jetton-合约消息布局), + [Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/jettons-withdrawals.js#L69) + 为了检查交易的成功验证,每条消息的`query_id`必须分配一个唯一值。 +8. 当使用高负载钱包时,建议收集一批消息,并一次性发送一批以优化费用。 +9. 保存外部发出消息的过期时间(这是钱包成功 + 处理消息的时间,在此之后,钱包将不再接受该消息) +10. 发送单条消息或多条消息(批量消息)。 +11. 检索热钱包帐户中最新未处理的交易列表,并迭代它。 + 了解更多:[检查合约的交易](/develop/dapps/asset-processing/#checking-contracts-transactions), + [Tonweb示例](https://github.com/toncenter/examples/blob/9f20f7104411771793dfbbdf07f0ca4860f12de2/deposits-single-wallet.js#L43) 或 + 使用Toncenter API `/getTransactions`方法。 +12. 查看帐户中的外部消息。 +13. 如果存在带有`transfer`操作码的消息,则应解码以检索`query_id`值。 + 检索到的`query_id`需要标记为成功发送。 +14. 如果扫描到当前交易的处理时间大于 + 过期时间,并且未找到带有给定`query_id`的外部消息, + 则请求应(可选)标记为过期,并且应安全地重新发送。 +15. 查找帐户中的传入消息。 +16. 如果存在使用`excesses`操作码的消息,应解码该消息并检索`query_id` + 值。找到的`query_id`必须标记为成功传送。 +17. 转到第5步。未成功发送的过期请求应重新加入提款列表。 + +## 在链上处理 Jetton + +:::info 交易确认 +TON交易在仅一次确认后即不可逆转。为了最佳用户体验,建议一旦交易在TON区块链上最终确定后就不再等待其他区块。在[catchain.pdf](https://docs.ton.org/catchain.pdf#page=3)中阅读更多。 +::: + +通常,接受和处理jettons时,一个负责内部消息的消息处理程序使用`op=0x7362d09c`操作码。 + +以下是在进行链上jetton处理时必须考虑的一些建议: + +1. 使用它们的钱包类型而不是Jetton主合约来识别传入的jettons。换句话说,您的合约应该与特定的jetton钱包进行交互(与使用特定Jetton主合约的某个未知钱包交互)。 +2. 当链接Jetton钱包和Jetton主合约时,检查这种连接是否是双向的,即钱包识别主合约,反之亦然。例如,如果您的合约系统从jetton钱包(将其MySuperJetton视为其主合约)收到通知,其转账信息必须向用户显示,然后显示MySuperJetton合约的`symbol`、`name`和`image` +之前,请检查MySuperJetton钱包是否使用正确的合约系统。反过来,如果您的合约系统由于某种原因需要使用MySuperJetton或MySuperJetton主合约发送jettons,请验证钱包X是否是使用相同合约参数的钱包。 +另外,在向X发送`transfer`请求之前,请确保它将MySuperJetton识别为其主合约。 +3. 去中心化金融(DeFi)的真正力量基于能够像乐高积木一样将协议叠加在彼此之上的能力。举例来说,假设jetton A被换成jetton B,然后又在借贷协议中用作杠杆(当用户提供流动性时),然后用来购买NFT....以此类推。因此,请考虑合约如何能够服务于非链上用户以及链上实体,方法是通过附加token化的价值到转账通知中,添加可以与转账通知一起发送的自定义有效负载。 +4. 请注意,并非所有合约都遵循相同的标准。不幸的是,一些jettons可能是敌对的(使用基于攻击的向量)并且仅为攻击毫无戒心的用户而创建。出于安全考虑,如果协议涉及多个合约,请不要创建大量相同类型的jetton钱包。特别是,不要在协议内部的存款合约、保管库合约或用户帐户合约等之间发送jettons。攻击者可能会通过伪造转账通知、jetton数量或有效负载参数来故意干扰合约逻辑。通过在系统中只使用每个jetton的一个钱包(用于所有存款和提款),来降低潜在的攻击风险。 +5. 同样,为了减少地址欺骗的机会(例如,使用为jetton A设计的合约发送给jetton B的转账消息),为每个个别jetton创建子合约通常是个好主意。 +6. 强烈建议在合约级别使用不可分割的jetton单位。十进制相关逻辑通常用于增强用户界面(UI)的显示,并与链上数值记录无关。 +7. 欲了解更多关于[在FunC中安全智能合约编程 by CertiK](https://blog.ton.org/secure-smart-contract-programming-in-func),请随时阅读此资源。建议开发者处理所有智能合约异常,以便在应用开发期间永远不会被忽略。 + +## Jetton 钱包处理 +通常,用于链下jetton处理的所有验证程序都适用于钱包。对于Jetton钱包处理,我们最重要的建议如下: + +1. 当钱包从未知的jetton钱包收到转账通知时,信任jetton钱包及其主地址至关重要,因为它可能是恶意伪造的。为了保护自己,请检查Jetton主(主合约)使用其提供的地址,以确保您的验证过程将jetton钱包视为合法的。在您信任钱包并验证其为合法后,您可以允许它访问您的帐户余额和其他钱包内数据。如果Jetton主不识别此钱包,建议不启动或公开您的jetton转账,只显示传入的TON转账(即附加到转账通知的Toncoin)。 +2. 实际操作中,如果用户想与Jetton而不是jetton钱包互动。换句话说,用户发送wTON/oUSDT/jUSDT, jUSDC, jDAI而不是`EQAjN...`/`EQBLE...` + 等。通常这意味着,当用户启动jetton转账时,钱包会询问相应的jetton主,哪个由用户拥有的jetton钱包应启动转账请求。重要的是永远不要盲目信任来自主合约(主合约)的这些数据。在将转账请求发送至jetton钱包之前,请始终确保jetton钱包确实属于它声称所属的Jetton主。 +3. 请注意,敌对的Jetton Masters/jetton钱包可能会随时间更改其钱包/Masters。因此,对与用户进行交互的任何钱包进行尽职调查和验证至关重要,请在每次使用前检查。 +4. 始终确保您以不会与TON转账、系统通知等混淆的方式在界面中显示jettons。即使是`symbol`,`name`和`image` + 参数也可以设计用来误导用户,留下受骗的潜在受害者。有几个实例,恶意jettons被用来冒充TON转账、通知错误、奖励收入或资产冻结公告。 +5. 始终警惕可能创建假冒jettons的恶意行为者,提供给用户消除主界面中不需要的jettons的功能总是个好主意。 + + +由[kosrk](https://github.com/kosrk)、[krigga](https://github.com/krigga)、[EmelyanenkoK](https://github.com/EmelyanenkoK/) 和 [tolya-yanot](https://github.com/tolya-yanot/)编写。 + + +## 最佳实践 +在此我们提供了一些由TON社区成员创建的jetton代码处理的示例: + + + + +```js +const transfer = await wallet.methods.transfer({ + secretKey: keyPair.secretKey, + toAddress: jettonWalletAddress, + amount: 0, + seqno: seqno, + sendMode: 128 + 32, // 模式128用于携带所有剩余余额的消息;模式32表示如果当前账户的结果余额为零,则必须销毁该账户; + payload: await jettonWallet.createTransferBody({ + queryId: seqno, // 任意数字 + jettonAmount: jettonBalance, // jetton数量(单位) + toAddress: new TonWeb.utils.Address(MY_HOT_WALLET_ADDRESS), + responseAddress: new TonWeb.utils.Address(MY_HOT_WALLET_ADDRESS), + }), +}); +await transfer.send(); +``` + + + + +```go +client := liteclient.NewConnectionPool() + +// 连接到测试网lite服务器 +err := client.AddConnectionsFromConfigUrl(context.Background(), "https://ton.org/global.config.json") +if err != nil { + panic(err) +} + +ctx := client.StickyContext(context.Background()) + +// 初始化ton api lite连接包装器 +api := ton.NewAPIClient(client) + +// 账户的种子词,你可以使用任何钱包或使用wallet.NewSeed()方法生成它们 +words := strings.Split("birth pattern then forest walnut then phrase walnut fan pumpkin pattern then cluster blossom verify then forest velvet pond fiction pattern collect then then", " ") + +w, err := wallet.FromSeed(api, words, wallet.V3R2) +if err != nil { + log.Fatalln("FromSeed err:", err.Error()) + return +} + +token := jetton.NewJettonMasterClient(api, address.MustParseAddr("EQD0vdSA_NedR9uvbgN9EikRX-suesDxGeFg69XQMavfLqIw")) + +// 寻找我们的jetton钱包 +tokenWallet, err := token.GetJettonWallet(ctx, w.WalletAddress()) +if err != nil { + log.Fatal(err) +} + +amountTokens := tlb.MustFromDecimal("0.1", 9) + +comment, err := wallet.CreateCommentCell("Hello from tonutils-go!") +if err != nil { + log.Fatal(err) +} + +// 接收者钱包的地址(非token钱包,只是常规钱包) +to := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N") +transferPayload, err := tokenWallet.BuildTransferPayload(to, amountTokens, tlb.ZeroCoins, comment) +if err != nil { + log.Fatal(err) +} + +// 你的TON余额必须大于0.05才能发送 +msg := wallet.SimpleMessage(tokenWallet.Address(), tlb.MustFromTON("0.05"), transferPayload) + +log.Println("发送交易...") +tx, _, err := w.SendWaitTransaction(ctx, msg) +if err != nil { + panic(err) +} +log.Println("交易确认,哈希:", base64.StdEncoding.EncodeToString(tx.Hash)) +``` + + + + +```py +my_wallet = Wallet(provider=client, mnemonics=my_wallet_mnemonics, version='v4r2') + +# 对于TonCenterClient和LsClient +await my_wallet.transfer_jetton(destination_address='address', jetton_master_address=jetton.address, jettons_amount=1000, fee=0.15) + +# 对于所有客户端 +await my_wallet.transfer_jetton_by_jetton_wallet(destination_address='address', jetton_wallet='your jetton wallet address', jettons_amount=1000, fee=0.1) +``` + + + + + + +## 参阅 + +* [支付处理](/develop/dapps/asset-processing/) +* [TON上的NFT处理](/develop/dapps/asset-processing/nfts) +* [TON上的元数据解析](/develop/dapps/asset-processing/metadata) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/metadata-parsing.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/metadata-parsing.md new file mode 100644 index 0000000000..27b19d0631 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/metadata-parsing.md @@ -0,0 +1,220 @@ +# TON 元数据解析 + +元数据标准涵盖了 NFT、NFT 集合和 Jettons,在 TON 增强提案 64 [TEP-64](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) 中有所描述。 + +在 TON 上,实体可以有三种类型的元数据:链上、半链上和链下。 + +- **链上元数据:** 存储在区块链内部,包括名称、属性和图像。 +- **链下元数据:** 使用链接存储到链外托管的元数据文件。 +- **半链上元数据:** 两者之间的混合体,允许在区块链上存储小字段,如名称或属性,而将图像托管在链外,并仅存储指向它的链接。 + +## 蛇形数据编码 + +蛇形编码格式允许部分数据存储在标准cell内,而剩余部分存储在子cell内(以递归方式)。蛇形编码格式必须使用 0x00 字节作为前缀。TL-B 方案: + +``` +tail#_ {bn:#} b:(bits bn) = SnakeData ~0; +cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1); +``` + +当单个cell无法存储的数据超过最大大小时,使用 蛇形格式存储额外数据。这是通过在根cell中存储部分数据,其余部分存储在第一个子cell中,并继续递归进行,直到所有数据都被存储。 + +以下是 TypeScript 中 蛇形格式编码和解码的示例: + +```typescript +export function makeSnakeCell(data: Buffer): Cell { + const chunks = bufferToChunks(data, 127) + + if (chunks.length === 0) { + return beginCell().endCell() + } + + if (chunks.length === 1) { + return beginCell().storeBuffer(chunks[0]).endCell() + } + + let curCell = beginCell() + + for (let i = chunks.length - 1; i >= 0; i--) { + const chunk = chunks[i] + + curCell.storeBuffer(chunk) + + if (i - 1 >= 0) { + const nextCell = beginCell() + nextCell.storeRef(curCell) + curCell = nextCell + } + } + + return curCell.endCell() +} + +export function flattenSnakeCell(cell: Cell): Buffer { + let c: Cell | null = cell; + + const bitResult = new BitBuilder(); + while (c) { + const cs = c.beginParse(); + if (cs.remainingBits === 0) { + break; + } + + const data = cs.loadBits(cs.remainingBits); + bitResult.writeBits(data); + c = c.refs && c.refs[0]; + } + + const endBits = bitResult.build(); + const reader = new BitReader(endBits); + + return reader.loadBuffer(reader.remaining / 8); +} +``` + +应该注意,使用 蛇形格式时在根cell中并不总是需要 `0x00` 字节前缀,就像链下 NFT 内容的情况一样。此外,cell中以字节而非位填充,以简化解析。为了避免在其父cell已经写入后再向下一个子cell添加引用的问题,snake cell是以反向顺序构造的。 + +## 分块编码 + +分块编码格式使用字典数据结构存储数据,从 chunk_index 到 chunk。分块编码必须使用 `0x01` 字节作为前缀。TL-B 方案: + +``` +chunked_data#_ data:(HashMapE 32 ^(SnakeData ~0)) = ChunkedData; +``` + +以下是使用 TypeScript 解码分块数据的示例: + +```typescript +interface ChunkDictValue { + content: Buffer; +} +export const ChunkDictValueSerializer = { + serialize(src: ChunkDictValue, builder: Builder) {}, + parse(src: Slice): ChunkDictValue { + const snake = flattenSnakeCell(src.loadRef()); + return { content: snake }; + }, +}; + +export function ParseChunkDict(cell: Slice): Buffer { + const dict = cell.loadDict( + Dictionary.Keys.Uint(32), + ChunkDictValueSerializer + ); + + let buf = Buffer.alloc(0); + for (const [_, v] of dict) { + buf = Buffer.concat([buf, v.content]); + } + return buf; +} +``` + +## NFT 元数据属性 + +| 属性 | 类型 | 要求 | 描述 | +| ------------- | --------- | -- | ------------------------------------- | +| `uri` | ASCII 字符串 | 可选 | 指向由 "半链上内容布局" 使用的带有元数据的 JSON 文档的 URI。 | +| `name` | UTF8 字符串 | 可选 | 标识资产。 | +| `description` | UTF8 字符串 | 可选 | 描述资产。 | +| `image` | ASCII 字符串 | 可选 | 指向带有图像 mime 类型的资源的 URI。 | +| `image_data` | 二进制\* | 可选 | 链上布局的图像的二进制表示,或链下布局的 base64。 | + +## Jetton 元数据属性 + +1. `uri` - 可选。由 "半链上内容布局" 使用。ASCII 字符串。指向带有元数据的 JSON 文档的 URI。 +2. `name` - 可选。UTF8 字符串。标识资产。 +3. `description` - 可选。UTF8 字符串。描述资产。 +4. `image` - 可选。ASCII 字符串。指向带有图像 mime 类型的资源的 URI。 +5. `image_data` - 可选。链上布局的图像的二进制表示,或链下布局的 base64。 +6. `symbol` - 可选。UTF8 字符串。代币的符号,例如 "XMPL"。使用格式 "你收到了 99 XMPL"。 +7. `decimals` - 可选。如未指定,默认使用 9。从 0 到 255 的数字的 UTF8 编码字符串。代币使用的小数位数,例如 8,意味着将代币数量除以 100000000 以获得其用户表示。 +8. `amount_style` - 可选。需要由外部应用程序来理解显示jettons数量的格式。 + +- "n" - jettons数量(默认值)。如果用户拥有 0 小数的 100 个代币,则显示用户拥有 100 个代币 +- "n-of-total" - 总发行jettons数量中的jettons数量。例如,totalSupply Jetton = 1000。用户在jettons钱包中有 100 个jettons。例如,必须在用户的钱包中显示为 100 of 1000 或以任何其他文本或图形方式来表现从整体中的特定部分。 +- "%" - 从总发行jettons数量中的百分比。例如,totalSupply Jetton = 1000。用户在jettons钱包中有 100 个jettons。例如,应该在用户的钱包中显示为 10%。 + +9. `render_type` - 可选。需要由外部应用程序来理解jettons属于哪个组,以及如何显示它。 + +- "currency" - 作为代币显示(默认值)。 +- "game" - 游戏显示。应该显示为 NFT,但同时显示jettons的数量,考虑到 `amount_style`。 + +| 属性 | 类型 | 要求 | 描述 | +| -------------- | --------- | -- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `uri` | ASCII 字符串 | 可选 | 指向用于“半链上内容布局”的元数据的 JSON 文档的 URI。 | +| `name` | UTF8 字符串 | 可选 | 标识资产。 | +| `description` | UTF8 字符串 | 可选 | 描述资产。 | +| `image` | ASCII 字符串 | 可选 | 指向资源的 URI,该资源具有图像的 mime 类型。 | +| `image_data` | 二进制\* | 可选 | 链上布局的图像的二进制表示,或链外布局的 base64。 | +| `symbol` | UTF8 字符串 | 可选 | 代币的符号 - 例如 "XMPL",使用形式 "您收到了 99 XMPL"。 | +| `decimals` | UTF8 字符串 | 可选 | 代币使用的小数位数。如果未指定,默认使用 9。UTF8 编码的字符串,数字在 0 到 255 之间。 - 例如,8,意味着代币数量必须除以 100000000 来获取其用户表示形式。 | +| `amount_style` | | 可选 | 外部应用需要了解显示什么样的 jettons 格式。定义为 *n*,*n-of-total*,*%*。 | +| `render_type` | | 可选 | 外部应用需要了解 jetton 属于哪个组并如何显示它。"currency" - 作为货币显示(默认值)。“game” - 用于游戏的显示,显示为 NFT,但同时显示 jettons 的数量并考虑 amount_style 的值。 | + +> `amount_style` 参数: + +- *n* - jettons 的数量(默认值)。如果用户拥有 0 小数的 100 个代币,则显示用户拥有 100 个代币。 +- *n-of-total* - 用户拥有的 jettons 数量与发行的总 jettons 数量之比。例如,如果 Jettons 的 totalSupply 是 1000,用户的钱包中有 100 jettons,那么它必须以用户钱包中的 100/1000 或以其他文字或图形方式显示,以展示用户代币与可用代币总量的比例。 +- *%* - 从发行的总 jettons 数量中 jettons 的百分比。例如,如果 Jettons 的 totalSupply 是 1000,用户持有 100 jettons,百分比应在用户钱包余额显示 10%(100 ÷ 1000 = 0.1 或 10%)。 + +> `render_type` 参数: + +- *currency* - 作为货币显示(默认值)。 +- *game* - 用于游戏的显示,显示为 NFT,但同时显示 jettons 的数量并考虑 `amount_style` 值。 + +## 解析元数据 + +要解析元数据,首先必须从区块链获取 NFT 数据。为了更好地理解这个过程,请考虑阅读我们的 TON 资产处理文档章节中的[获取 NFT 数据](/develop/dapps/asset-processing/nfts#getting-nft-data)部分。 + +在获得链上 NFT 数据后,必须对其进行解析。要执行此过程,必须通过读取构成 NFT 内部结构的第一个字节来确定 NFT 的内容类型。 + +### 链下 + +如果元数据字节字符串以 `0x01` 开始,它表示链外 NFT 内容类型。NFT 内容的其余部分使用Snake编码格式解码为 ASCII 字符串。在正确的 NFT URL 被实现,并且检索到 NFT 标识数据后,过程即完成。以下是使用链下 NFT 内容元数据解析的 URL 示例: +`https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/95/meta.json` + +URL 内容(从上面直接引用): + +```json +{ + "name": "TON Smart Challenge #2 Winners Trophy", + "description": "TON Smart Challenge #2 Winners Trophy 1 place out of 181", + "image": "https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/images/943e994f91227c3fdbccbc6d8635bfaab256fbb4", + "content_url": "https://s.getgems.io/nft/b/c/62fba50217c3fe3cbaad9e7f/content/84f7f698b337de3bfd1bc4a8118cdfd8226bbadf", + "attributes": [] +} +``` + +### 链上和半链上 + +如果元数据字节字符串以 `0x00` 开始,它表示 NFT 使用链上或半链上格式。 + +我们的 NFT 元数据存储在一个字典中,其中键是属性名称的 SHA256 哈希,值是以Snake或分块格式存储的数据。 + +为了确定正在使用哪种类型的 NFT,开发者需要读取已知的 NFT 属性,例如 `uri`、`name`、`image`、`description` 和 `image_data`。如果元数据中存在 `uri` 字段,则表示半链上布局。在这种情况下,应该下载 uri 字段中指定的链外内容,并与字典值合并。 + +链上 NFT 的示例:[EQBq5z4N_GeJyBdvNh4tPjMpSkA08p8vWyiAX6LNbr3aLjI0](https://getgems.io/collection/EQAVGhk_3rUA3ypZAZ1SkVGZIaDt7UdvwA4jsSGRKRo-MRDN/EQBq5z4N_GeJyBdvNh4tPjMpSkA08p8vWyiAX6LNbr3aLjI0) + +半链上 NFT 的示例: [EQB2NJFK0H5OxJTgyQbej0fy5zuicZAXk2vFZEDrqbQ_n5YW](https://getgems.io/nft/EQB2NJFK0H5OxJTgyQbej0fy5zuicZAXk2vFZEDrqbQ_n5YW) + +链上 Jetton Master 的示例:[EQA4pCk0yK-JCwFD4Nl5ZE4pmlg4DkK-1Ou4HAUQ6RObZNMi](https://tonscan.org/jetton/EQA4pCk0yK-JCwFD4Nl5ZE4pmlg4DkK-1Ou4HAUQ6RObZNMi) + +链上 NFT 解析器的示例:[stackblitz/ton-onchain-nft-parser](https://stackblitz.com/edit/ton-onchain-nft-parser?file=src%2Fmain.ts) + +## NFT 元数据的重要说明 + +1. 对于 NFT 元数据,`name`、`description` 和 `image`(或 `image_data`)字段是显示 NFT 所必需的。 +2. 对于 Jetton 元数据,`name`、`symbol`、`decimals` 和 `image`(或 `image_data`)是主要的。 +3. 重要的是要意识到,任何人都可以使用任何 `name`、`description` 或 `image` 创建 NFT 或 Jetton。为避免混淆和潜在的骗局,用户应始终以一种清晰区分于其应用其他部分的方式显示他们的 NFT。恶意 NFT 和 Jettons 可以带有误导性或虚假信息被发送到用户的钱包。 +4. 一些项目可能有一个 `video` 字段,链接到与 NFT 或 Jetton 相关联的视频内容。 + +## 参考文献 + +- [TON 增强提案 64(TEP-64)](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) + +## 参阅 + +- [TON NFT 处理](/develop/dapps/asset-processing/nfts) +- [TON Jetton 处理](/develop/dapps/asset-processing/jettons) +- [首次打造你的 Jetton](/develop/dapps/tutorials/jetton-minter) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/nfts.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/nfts.md new file mode 100644 index 0000000000..9769edfc5d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/asset-processing/nft-processing/nfts.md @@ -0,0 +1,271 @@ +# TON NFT 处理 + +## 概述 + +在本文档部分中,我们将向读者提供对 NFT 的更深刻理解。这将教导读者如何与 NFT 交互,并如何通过在 TON 区块链上发送的交易接收 NFT。 + +下面提供的信息假定读者已经深入了解了我们之前的[有关 Toncoin 支付处理的部分](/develop/dapps/asset-processing),同时也假设他们具备通过编程与钱包智能合约交互的基本知识。 + +## 理解 NFT 的基础 + +在 TON 区块链上运行的 NFT 由 [TEP-62](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) 和 [TEP-64](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) 标准表示。 + +Open Network (TON) 区块链设计考虑了高性能,并包括了一个功能,该功能基于 TON 上的合约地址使用自动分片(用于帮助配置特定 NFT 设计)。为了实现最佳性能,单个 NFT 必须使用自己的智能合约。这使得可以创建任意大小(数量大或小)的 NFT 集合,同时也降低了开发成本和性能问题。然而,这种方法也为 NFT 集合的开发引入了新的考虑因素。 + +因为每个 NFT 都使用自己的智能合约,所以使用单个合约无法获取 NFT 集合中每个个体化 NFT 的信息。为了检索整个集合以及集合中每个 NFT 的信息,需要分别查询集合合约和每个个体 NFT 合约。出于同样的原因,要跟踪 NFT 转移,需要跟踪特定集合中每个个体化 NFT 的所有交易。 + +### NFT 集合 + +NFT 集合是一个用于索引和存储 NFT 内容的合约,并应包含以下接口: + +#### 获取方法 `get_collection_data` + +``` +(int next_item_index, cell collection_content, slice owner_address) get_collection_data() +``` + +获取关于集合的一般信息,表示如下: + +1. `next_item_index` - 如果集合是有序的,此分类指示集合中 NFT 的总数,以及用于铸造的下一个索引。对于无序的集合,`next_item_index` 的值是 -1,意味着集合使用独特机制来跟踪 NFT(例如,TON DNS 域的哈希)。 +2. `collection_content` - 一个以 TEP-64 兼容格式表示集合内容的 cell。 +3. `owner_address` - 包含集合所有者地址的 slice(此值也可以为空)。 + +#### 获取方法 `get_nft_address_by_index` + +``` +(slice nft_address) get_nft_address_by_index(int index) +``` + +此方法可用于验证 NFT 的真实性,并确认它是否确实属于特定集合。它还使用户能够通过提供其在集合中的索引来检索 NFT 地址。该方法应返回包含与提供的索引对应的 NFT 地址的 slice。 + +#### 获取方法 `get_nft_content` + +``` +(cell full_content) get_nft_content(int index, cell individual_content) +``` + +由于集合充当 NFT 的公共数据存储,因此需要此方法来完善 NFT 内容。要使用此方法,首先需要通过调用相应的 `get_nft_data()` 方法获取 NFT 的 `individual_content`。获取 `individual_content` 后,可以使用 NFT 索引和 `individual_content` cell 调用 `get_nft_content()` 方法。该方法应返回一个包含 NFT 全部内容的 TEP-64 cell。 + +### NFT 项 + +基本 NFT 应实现: + +#### 获取方法 `get_nft_data()` + +``` +(int init?, int index, slice collection_address, slice owner_address, cell individual_content) get_nft_data() +``` + +#### 内联消息处理器 `transfer` + +``` +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 +``` + +让我们看一下您需要在消息中填充的每个参数: + +1. `OP` - `0x5fcc3d14` - 由 TEP-62 标准在转移消息中定义的常量。 +2. `queryId` - `uint64` - 用于跟踪消息的 uint64 数字。 +3. `newOwnerAddress` - `MsgAddress` - 用于将 NFT 转移至的合约地址。 +4. `responseAddress` - `MsgAddress` - 用于转移余额的地址。通常,额外的 TON 数量(例如,1 TON)被发送到 NFT 合约以确保它有足够的资金支付交易费用并创建新的转移(如果需要)。交易中的所有未使用资金都发送到 `responseAddress`。 +5. `forwardAmount` - `Coins` - 与转发消息一起使用的 TON 金额(通常设置为 0.01 TON)。由于 TON 使用异步架构,新所有者在成功接收交易后不会立即收到通知。为了通知新所有者,一个内部消息从 NFT 智能合约发送到 `newOwnerAddress`,使用 `forwardAmount` 表示的值。转发消息将以 `ownership_assigned` OP(`0x05138d91`)开始,紧随其后的是之前所有者的地址和 `forwardPayload`(如果存在)。 +6. `forwardPayload` - `Slice | Cell` - 作为 `ownership_assigned` 通知消息的一部分发送。 + +如上所述,这个消息是与 NFT 交互的主要方式,用于在收到上述消息的通知后改变所有权。 + +例如,这种消息类型通常用于将 NFT Item 智能合约从 Wallet 智能合约发送。当 NFT 智能合约接收到此消息并执行它时,NFT 合约的存储(内部合约数据)将随着所有者 ID 的更新而更新。通过这种方式,NFT Item(合约)正确地更换所有者。此过程详细说明了标准 NFT 转移 + +在这种情况下,转发金额应设置为适当的值(对于常规钱包为 0.01 TON 或在您希望通过转移 NFT 来执行合约时更多),以确保新所有者接收到关于所有权转移的通知。除了以上述方式通知新所有者,如果不采取这一步骤,新所有者将不会知道他们已收到 NFT。 + +## 检索 NFT 数据 + +大多数 SDK 使用现成的处理器来检索 NFT 数据,包括:[tonweb(js)](https://github.com/toncenter/tonweb/blob/b550969d960235314974008d2c04d3d4e5d1f546/src/contract/token/nft/NftItem.js#L38)、[tonutils-go](https://github.com/xssnick/tonutils-go/blob/fb9b3fa7fcd734eee73e1a73ab0b76d2fb69bf04/ton/nft/item.go#L132)、[pytonlib](https://github.com/toncenter/pytonlib/blob/d96276ec8a46546638cb939dea23612876a62881/pytonlib/client.py#L771)等。 + +要接收 NFT 数据,需要使用 `get_nft_data()` 检索机制。例如,我们必须验证以下 NFT 项地址 `EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e`(也称为 [foundation.ton](https://tonscan.org/address/EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e) 域)。 + +首先需要按照如下方式使用 toncenter.com API 执行 get 方法。: + +``` +curl -X 'POST' \ + 'https://toncenter.com/api/v2/runGetMethod' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "address": "EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e", + "method": "get_nft_data", + "stack": [] +}' +``` + +响应通常类似于如下内容: + +```json +{ + "ok": true, + "result": { + "@type": "smc.runResult", + "gas_used": 1581, + "stack": [ + // init + [ "num", "-0x1" ], + // index + [ "num", "0x9c7d56cc115e7cf6c25e126bea77cbc3cb15d55106f2e397562591059963faa3" ], + // collection_address + [ "cell", { "bytes": "te6cckEBAQEAJAAAQ4AW7psr1kCofjDYDWbjVxFa4J78SsJhlfLDEm0U+hltmfDtDcL7" } ], + // owner_address + [ "cell", { "bytes": "te6cckEBAQEAJAAAQ4ATtS415xpeB1e+YRq/IsbVL8tFYPTNhzrjr5dcdgZdu5BlgvLe" } ], + // content + [ "cell", { "bytes": "te6cckEBCQEA7AABAwDAAQIBIAIDAUO/5NEvz/d9f/ZWh+aYYobkmY5/xar2cp73sULgTwvzeuvABAIBbgUGAER0c3/qevIyXwpbaQiTnJ1y+S20wMpSzKjOLEi7Jwi/GIVBAUG/I1EBQhz26hlqnwXCrTM5k2Qg5o03P1s9x0U4CBUQ7G4HAUG/LrgQbAsQe0P2KTvsDm8eA3Wr0ofDEIPQlYa5wXdpD/oIAEmf04AQe/qqXMblNo5fl5kYi9eYzSLgSrFtHY6k/DdIB0HmNQAQAEatAVFmGM9svpAE9og+dCyaLjylPtAuPjb0zvYqmO4eRJF0AIDBvlU=" } ] + ], + "exit_code": 0, + "@extra": "1679535187.3836682:8:0.06118075068995321" + } +} +``` + +返回参数: + +- `init` - `boolean` - -1 表示 NFT 已初始化并可使用 +- `index` - `uint256` - 集合中 NFT 的索引。可以是顺序的或以其他方式派生。例如,这可以表示使用 TON DNS 合约的 NFT 域哈希,而集合应该只在给定索引内拥有唯一的 NFT。 +- `collection_address` - `Cell` - 包含 NFT 集合地址的 cell(可以为空)。 +- `owner_address` - `Cell` - 包含当前所有者 NFT 地址的 cell(可以为空)。 +- `content` - `Cell` - 包含 NFT 项内容的 cell(如果需要解析,需要参考 TEP-64 标准)。 + +## 检索集合内的所有 NFT + +检索集合内所有 NFT 的过程取决于集合是否有序。我们在下面概述了两种过程。 + +### 有序集合 + +检索有序集合中的所有 NFT 相对简单,因为已经知道了检索所需的 NFT 数量,且可以轻松获取它们的地址。为了完成这一过程,应按顺序执行以下步骤: + +1. 使用 TonCenter API 调用集合合约中的 `get_collection_data` 方法,并从响应中检索 `next_item_index` 值。 +2. 使用 `get_nft_address_by_index` 方法,传入索引值 `i`(最初设置为 0),以检索集合中第一个 NFT 的地址。 +3. 使用上一步获得的地址检索 NFT 项数据。接下来,验证初始 NFT Collection 智能合约是否与 NFT 项本身报告的 NFT Collection 智能合约一致(以确保集合没有挪用其他用户的 NFT 智能合约)。 +4. 使用来自上一步的 `i` 和 `individual_content` 调用 `get_nft_content` 方法。 +5. `i` 增加 1 并重复步骤 2-5,直到 `i` 等于 `next_item_index`。 +6. 此时,您将拥有来自集合及其各个项目所需的信息。 + +### 无序集合 + +检索无序集合中的 NFT 列表更为困难,因为没有固有的方式来获取属于集合的 NFT 的地址。因此,需要解析集合合约中的所有交易并检查所有发出的消息以识别属于集合的 NFT 对应的消息。 + +为此,必须检索 NFT 数据,并在集合中使用 NFT 返回的 ID 调用 `get_nft_address_by_index` 方法。如果 NFT 合约地址与 `get_nft_address_by_index` 方法返回的地址匹配,这表明该 NFT 属于当前集合。但是,解析集合到所有消息可能是一个漫长的过程,并且可能需要归档节点。 + +## 在 TON 之外的 NFT 处理 + +### 发送 NFT + +要转移 NFT 所有权,需要从 NFT 所有者的钱包向 NFT 合约发送一条包含转移消息的 cell。这可以通过使用特定语言的库(例如 [tonweb(js)](https://github.com/toncenter/tonweb/blob/b550969d960235314974008d2c04d3d4e5d1f546/src/contract/token/nft/NftItem.js#L65)、[ton(js)](https://github.com/getgems-io/nft-contracts/blob/debcd8516b91320fa9b23bff6636002d639e3f26/packages/contracts/nft-item/NftItem.data.ts#L102)、[tonutils-go(go)](https://github.com/xssnick/tonutils-go/blob/fb9b3fa7fcd734eee73e1a73ab0b76d2fb69bf04/ton/nft/item.go#L132))来完成。 + +一旦创建了转移消息,就必须从所有者的钱包合约地址发送到 NFT 项合约地址,并附带足够的 TON 以支付关联的交易费用。 + +要将 NFT 从另一个用户转移到您自己,需要使用 TON Connect 2.0 或包含 ton:// 链接的简单二维码。例如: +`ton://transfer/{nft_address}?amount={message_value}&bin={base_64_url(transfer_message)}` + +### 接收 NFTs + +跟踪发送到某个智能合约地址(即用户的钱包)的 NFT 的过程类似于跟踪支付的机制。这是通过监听钱包中的所有新交易并解析它们来完成的。 + +下一步可能会根据具体情况而有所不同。让我们下面看几个不同的场景。 + +#### 等待已知 NFT 地址转移的服务: + +- 验证从 NFT 项目智能合约地址发送的新交易。 +- 读取消息体的前 32 位作为使用 `uint` 类型,并验证它是否等于 `op::ownership_assigned()`(`0x05138d91`) +- 从消息体中读取接下来的 64 位作为 `query_id`。 +- 将消息体中的地址读作 `prev_owner_address`。 +- 现在可以管理您的新 NFT 了。 + +#### 监听所有类型的 NFT 转移的服务: + +- 检查所有新交易,忽略那些消息体长度小于 363 位(OP - 32,QueryID - 64,地址 - 267)的交易。 +- 重复上面列表中详细介绍的步骤。 +- 如果流程工作正常,则需要通过解析 NFT 及其所属的集合来验证 NFT 的真实性。接下来,需要确保 NFT 属于指定的集合。有关此过程的更多信息可以在 `获取所有集合 NFTs` 部分找到。可以通过使用 NFT 或集合的白名单来简化此过程。 +- 现在可以管理您的新 NFT 了。 + +#### 将 NFT 转移绑定到内部交易: + +当接收到这种类型的交易时,需要重复前面列表中的步骤。完成此过程后,可以通过在读取 `prev_owner_address` 值之后从消息体中读取一个 uint32 来检索 `RANDOM_ID` 参数。 + +#### 未发送通知信息的 NFT 发送: + +以上概述的所有策略都依赖于服务在 NFT 转移时正确创建转发消息。如果他们不这样做,我们不会知道他们是否已经将 NFT 转移到我们这边。但是,有一些可能的解决方法: + +以上概述的所有策略都依赖于服务正确在 NFT 转移中创建转发消息。如果不执行此过程,就无法清楚 NFT 是否已转移到正确的一方。但是,在这种情况下有几种可能的解决方案: + +- 如果预计 NFT 数量较少,可以定期解析它们并验证所有者是否已更改为相应的合约类型。 +- 如果预计 NFT 数量较多,可以解析所有新块并验证是否有任何调用发送到使用 `op::transfer` 方法的 NFT 目的地。如果启动了这样的交易,可以验证 NFT 的所有者并接收转移。 +- 如果在转移过程中无法解析新块,用户可以自行触发 NFT 所有权验证过程。这样,在转移没有通知的 NFT 之后,就可以触发 NFT 所有权验证过程。 + +## 从智能合约与 NFTs 交互 + +既然我们已经涵盖了发送和接收 NFTs 的基础,现在让我们探讨如何使用 [NFT Sale](https://github.com/ton-blockchain/token-contract/blob/1ad314a98d20b41241d5329e1786fc894ad811de/nft/nft-sale.fc) 合约示例从智能合约接收和转移 NFTs。 + +### 发送 NFTs + +在这个例子中,NFT 转移信息位于 [第 67 行](https://www.google.com/url?q=https://github.com/ton-blockchain/token-contract/blob/1ad314a98d20b41241d5329e1786fc894ad811de/nft/nft-sale.fc%23L67\&sa=D\&source=docs\&ust=1685436161341866\&usg=AOvVaw1yuoIzcbEuvqMS4xQMqfXE): + +``` +var nft_msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(nft_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page) + .store_uint(op::transfer(), 32) + .store_uint(query_id, 64) + .store_slice(sender_address) ;; new_owner_address + .store_slice(sender_address) ;; response_address + .store_int(0, 1) ;; empty custom_payload + .store_coins(0) ;; forward amount to new_owner_address + .store_int(0, 1); ;; empty forward_payload + + +send_raw_message(nft_msg.end_cell(), 128 + 32); +``` + +让我们仔细检查每行代码: + +- `store_uint(0x18, 6)` - 存储消息标志。 +- `store_slice(nft_address)` - 存储消息目标(NFT 地址)。 +- `store_coins(0)` - 发送随消息发送的 TON 数量设置为 0,因为使用 `128` [消息模式](/develop/smart-contracts/messages#message-modes) 以其余余额发送消息。要发送非用户全部余额的金额,必须更改此数字。请注意,它应足够大以支付gas费以及任何转发金额。 +- `store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)` - 剩余构成消息头的部分被留空。 +- `store_uint(op::transfer(), 32)` - 这是 msg_body 的开始。在这里,我们首先使用 transfer OP 代码,以便接收者理解其转移所有权消息。 +- `store_uint(query_id, 64)` - 存储查询 ID。 +- `store_slice(sender_address) ;; new_owner_address` - 第一个存储的地址是用于转移 NFTs 和发送通知的地址。 +- `store_slice(sender_address) ;; response_address` - 第二个存储的地址是响应地址。 +- `store_int(0, 1)` - 自定义有效载荷标志设置为 0,表示不需要自定义有效载荷。 +- `store_coins(0)` - 随消息转发的 TON 数量。在这个例子中设置为 0,但是,建议将此值设置为更高的金额(如至少 0.01 TON),以便创建转发消息并通知新所有者他们已经收到了 NFT。金额应足以覆盖任何相关费用和成本。 +- `.store_int(0, 1)` - 自定义有效载荷标志。如果您的服务应该作为 ref 传递有效载荷,则必须将其设置为 `1`。 + +### 接收 NFTs + +一旦我们发送了 NFT,就至关重要的是确定新所有者何时收到了它。一个好的例子可以在同一个 NFT 销售智能合约中找到: + +``` +slice cs = in_msg_full.begin_parse(); +int flags = cs~load_uint(4); + +if (flags & 1) { ;; ignore all bounced messages + return (); +} +slice sender_address = cs~load_msg_addr(); +throw_unless(500, equal_slices(sender_address, nft_address)); +int op = in_msg_body~load_uint(32); +throw_unless(501, op == op::ownership_assigned()); +int query_id = in_msg_body~load_uint(64); +slice prev_owner_address = in_msg_body~load_msg_addr(); +``` + +让我们再次检查每行代码: + +- `slice cs = in_msg_full.begin_parse();` - 用于解析传入消息。 +- `int flags = cs~load_uint(4);` - 用于从消息的前 4 位加载标志。 +- `if (flags & 1) { return (); } ;; 忽略所有弹回消息` - 用于验证消息是否没有被弹回。对于所有您的传入消息,如果没有理由反之,就很重要进行此过程。弹回的消息是那些在尝试接收交易时遇到错误并被退回给发件人的消息。 +- `slice sender_address = cs~load_msg_addr();` - 接下来加载消息发送者。在这种特殊情况下,通过使用 NFT 地址完成。 +- `throw_unless(500, equal_slices(sender_address, nft_address));` - 用于验证发送者确实是应该通过合约转移的 NFT。从智能合约解析 NFT 数据相当困难,因此在大多数情况下,NFT 地址在合约创建时预定义。 +- `int op = in_msg_body~load_uint(32);` - 加载消息 OP 代码。 +- `throw_unless(501, op == op::ownership_assigned());` - 确保接收的 OP 代码与所有权分配的常量值匹配。 +- `slice prev_owner_address = in_msg_body~load_msg_addr();` - 从传入消息体中提取的前所有者地址,并加载到 `prev_owner_address` 切片变量中。如果前所有者选择取消合约并将 NFT 归还给他们,这一点可能很有用。 + +现在我们已经成功地解析并验证了通知消息,我们可以继续我们的业务逻辑,这用于启动销售智能合约(它旨在处理 NFT 物品业务销售过程,例如 NFT 拍卖,如 getgems.io) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/cookbook.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/cookbook.md new file mode 100644 index 0000000000..3d0016556a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/cookbook.md @@ -0,0 +1,1582 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# TON 开发手册 + +在产品开发过程中,关于如何与不同的合约进行交互,常常会出现各种问题。 + +此文档旨在收集所有开发者的最佳实践,并与大家分享。 + +### 如何转换(用户友好型 \<-> 原始格式)、组装和从字符串提取地址? + +在 TON 上,根据服务的不同,地址可以以两种格式出现:`用户友好型` 和 `原始格式`。 + +```bash +用户友好型: EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +原始格式: 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e +``` + +用户友好型地址采用 base64 编码,而原始格式地址采用 hex 编码。在原始格式中,地址所在的工作链单独写在“:”字符之前,字符的大小写不重要。 + +要从字符串中获取地址,可以使用以下代码: + + + + +```js +import { Address } from "@ton/core"; + + +const address1 = Address.parse('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF'); +const address2 = Address.parse('0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e'); + +// toStrings 参数:urlSafe, bounceable, testOnly +// 默认值:true, true, false + +console.log(address1.toString()); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +console.log(address1.toRawString()); // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e + +console.log(address2.toString()); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +console.log(address2.toRawString()); // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e +``` + + + + +```js +const TonWeb = require('tonweb'); + +const address1 = new TonWeb.utils.Address('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF'); +const address2 = new TonWeb.utils.Address('0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e'); + +// toString 参数:isUserFriendly, isUrlSafe, isBounceable, isTestOnly + +console.log(address1.toString(true, true, true)); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +console.log(address1.toString(isUserFriendly = false)); // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e + +console.log(address1.toString(true, true, true)); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +console.log(address2.toString(isUserFriendly = false)); // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e +``` + + + + +```go +package main + +import ( + "fmt" + "github.com/xssnick/tonutils-go/address" +) + +// 在这里,我们需要手动实现对原始地址的处理,因为库不支持。 + +func main() { + address1 := address.MustParseAddr("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF") + address2 := mustParseRawAddr("0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e", true, false) + + fmt.Println(address1.String()) // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF + fmt.Println(printRawAddr(address1)) // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e + + fmt.Println(address2.String()) // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF + fmt.Println(printRawAddr(address2)) // 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e +} + +func mustParseRawAddr(s string, bounceable bool, testnet bool) *address.Address { + addr, err := parseRawAddr(s, bounceable, testnet) + if err != nil { + panic(err) + } + return addr +} + +func parseRawAddr(s string, bounceable bool, testnet bool) (*address.Address, error) { + var ( + workchain int32 + data []byte + ) + _, err := fmt.Sscanf(s, "%d:%x", &workchain, &data) + if err != nil { + return nil, err + } + if len(data) != 32 { + return nil, fmt.Errorf("地址长度必须为 32 字节") + } + + var flags byte = 0b00010001 + if !bounceable { + setBit(&flags, 6) + } + if testnet { + setBit(&flags, 7) + } + + return address.NewAddress(flags, byte(workchain), data), nil +} + +func printRawAddr(addr *address.Address) string { + return fmt.Sprintf("%v:%x", addr.Workchain, addr.Data()) +} + +func setBit(n *byte, pos uint) { + *n |= 1 << pos +} +``` + + + + +```py +from pytoniq_core import Address + +address1 = Address('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF') +address2 = Address('0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e') + +# to_str() 参数:is_user_friendly, is_url_safe, is_bounceable, is_test_only + +print(address1.to_str(is_user_friendly=True, is_bounceable=True, is_url_safe=True)) # EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +print(address1.to_str(is_user_friendly=False)) # 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e + +print(address2.to_str(is_user_friendly=True, is_bounceable=True, is_url_safe=True)) # EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +print(address2.to_str(is_user_friendly=False)) # 0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e +``` + + + + +### 如何获得不同类型的地址并确定地址类型? + +地址有三种格式:**可弹回的(bounceable)**、**不可弹回的(non-bounceable)**和**测试网络的(testnet)**。可以通过查看地址的第一个字母来轻松理解,因为它是第一个字节(8位)包含的标志根据 [TEP-2](https://github.com/ton-blockchain/TEPs/blob/master/text/0002-address.md#smart-contract-addresses): + +| 字母 | 二进制形式 | 可弹回 | 测试网络 | +|:----:|:---------:|:------:|:-------:| +| E | 00010001 | 是 | 否 | +| U | 01010001 | 否 | 否 | +| k | 10010001 | 是 | 是 | +| 0 | 11010001 | 否 | 是 | + +值得注意的是,在 base64 编码中,每个字符代表了 **6位** 的信息。正如你所观察到的,在所有情况下,最后 2 位保持不变,所以在这种情况下,我们可以关注第一个字母。如果它们改变了,会影响地址中的下一个字符。 + +此外,在某些库中,你可能会注意到一个称为“url safe”的字段。事实是,base64 格式不是 url 安全的,这意味着在链接中传输这个地址时可能会出现问题。当 urlSafe = true 时,所有的 `+` 符号被替换为 `-`,所有的 `/` 符号被替换为 `_`。您可以使用以下代码获得这些地址格式: + + + + +```js +import { Address } from "@ton/core"; + +const address = Address.parse('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF'); + +// toStrings 参数:urlSafe, bounceable, testOnly +// 默认值:true, true, false + +console.log(address.toString()); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHFэ +console.log(address.toString({urlSafe: false})) // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF +console.log(address.toString({bounceable: false})) // UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA +console.log(address.toString({testOnly: true})) // kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP +console.log(address.toString({bounceable: false, testOnly: true})) // 0QDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPleK +``` + + + + +```js +const TonWeb = require('tonweb'); + +const address = new TonWeb.utils.Address('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF'); + +// toString 参数:isUserFriendly, isUrlSafe, isBounceable, isTestOnly + +console.log(address.toString(true, true, true, false)); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +console.log(address.toString(true, false, true, false)); // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF +console.log(address.toString(true, true, false, false)); // UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA +console.log(address.toString(true, true, true, true)); // kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP +console.log(address.toString(true, true, false, true)); // 0QDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPleK +``` + + + + +```go +package main + +import ( + "fmt" + "github.com/xssnick/tonutils-go/address" +) + +func main() { + address := address.MustParseAddr("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF") + + fmt.Println(address.String()) // EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF + address.SetBounce(false) + fmt.Println(address.String()) // UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA + address.SetBounce(true) + address.SetTestnetOnly(true) // kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP + fmt.Println(address.String()) + address.SetBounce(false) // 0QDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPleK + fmt.Println(address.String()) +} +``` + + + + +```py +from pytoniq_core import Address + +address = Address('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF') + +# to_str() 参数:is_user_friendly, is_url_safe, is_bounceable, is_test_only + +print(address.to_str(is_user_friendly=True, is_bounceable=True, is_url_safe=True, is_test_only=False)) # EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF +print(address.to_str(is_user_friendly=True, is_bounceable=True, is_url_safe=False, is_test_only=False)) # EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff+W72r5gqPrHF +print(address.to_str(is_user_friendly=True, is_bounceable=False, is_url_safe=True, is_test_only=False)) # UQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPuwA +print(address.to_str(is_user_friendly=True, is_bounceable=True, is_url_safe=True, is_test_only=True)) # kQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPgpP +print(address.to_str(is_user_friendly=True, is_bounceable=False, is_url_safe=True, is_test_only=True)) # 0QDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPleK +``` + + + + +### 如何发送标准 TON 转账消息? + +要发送标准TON转账消息,首先需要打开您的钱包合约,之后获取您的钱包序列号(seqno)。只有完成这些步骤之后,您才能发送您的TON转账。请注意,如果您使用的是非V4版本的钱包,您需要将WalletContractV4重命名为WalletContract{您的钱包版本},例如,WalletContractV3R2。 + + + + +```js +import { TonClient, WalletContractV4, internal } from "@ton/ton"; +import { mnemonicNew, mnemonicToPrivateKey } from "@ton/crypto"; + +const client = new TonClient({ + endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC', +}); + +// 将助记词转换成私钥 +let mnemonics = "word1 word2 ...".split(" "); +let keyPair = await mnemonicToPrivateKey(mnemonics); + +// 创建钱包合约 +let workchain = 0; // 通常你需要一个workchain 0 +let wallet = WalletContractV4.create({ workchain, publicKey: keyPair.publicKey }); +let contract = client.open(wallet); + +// 创建转账 +let seqno: number = await contract.getSeqno(); +await contract.sendTransfer({ + seqno, + secretKey: keyPair.secretKey, + messages: [internal({ + value: '1', + to: 'EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N', + body: '转账示例内容', + })] +}); +``` + + + + + +```kotlin +// 设置liteClient +val context: CoroutineContext = Dispatchers.Default +val json = Json { ignoreUnknownKeys = true } +val config = json.decodeFromString( + URI("https://ton.org/global-config.json").toURL().readText() +) +val liteClient = LiteClient(context, config) + +val WALLET_MNEMONIC = "word1 word2 ...".split(" ") + +val pk = PrivateKeyEd25519(Mnemonic.toSeed(WALLET_MNEMONIC)) +val walletAddress = WalletV3R2Contract.address(pk, 0) +println(walletAddress.toString(userFriendly = true, bounceable = false)) + +val wallet = WalletV3R2Contract(liteClient, walletAddress) +runBlocking { + wallet.transfer(pk, WalletTransfer { + destination = AddrStd("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N") + bounceable = true + coins = Coins(100000000) // 1 ton 的 nanotons + messageData = org.ton.contract.wallet.MessageData.raw( + body = buildCell { + storeUInt(0, 32) + storeBytes("Comment".toByteArray()) + } + ) + sendMode = 0 + }) +} +``` + + + + +```py +from pytoniq import LiteBalancer, WalletV4R2 +import asyncio + +mnemonics = ["你的", "助记词", "在这里"] + +async def main(): + provider = LiteBalancer.from_mainnet_config(1) + await provider.start_up() + + wallet = await WalletV4R2.from_mnemonic(provider=provider, mnemonics=mnemonics) + DESTINATION_ADDRESS = "目的地址在这里" + + + await wallet.transfer(destination=DESTINATION_ADDRESS, amount=int(0.05*1e9), body="转账示例内容") + await client.close_all() + +asyncio.run(main()) +``` + + + + + +### 如何计算用户的 Jetton 钱包地址? + +为了计算用户的Jetton钱包地址,我们需要调用jetton主合约的"get_wallet_address" get方法,并实际传入用户地址。对于这项任务,我们可以轻松使用JettonMaster的getWalletAddress方法或者自行调用主合约。 + + + + +```js +const { Address, beginCell } = require("@ton/core") +const { TonClient, JettonMaster } = require("@ton/ton") + +const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', +}); + +const jettonMasterAddress = Address.parse('...') // 例如 EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE +const userAddress = Address.parse('...') + +const jettonMaster = client.open(JettonMaster.create(jettonMasterAddress)) +console.log(await jettonMaster.getWalletAddress(userAddress)) +``` + + + + +```js +const { Address, beginCell } = require("@ton/core") +const { TonClient } = require("@ton/ton") + +async function getUserWalletAddress(userAddress, jettonMasterAddress) { + const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + }); + const userAddressCell = beginCell().storeAddress(userAddress).endCell() + + const response = await client.runMethod(jettonMasterAddress, "get_wallet_address", [{type: "slice", cell: userAddressCell}]) + return response.stack.readAddress() +} +const jettonMasterAddress = Address.parse('...') // 例如 EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE +const userAddress = Address.parse('...') + +getUserWalletAddress(userAddress, jettonMasterAddress) + .then((userJettonWalletAddress) => { + console.log(userJettonWalletAddress) + } +) +``` + + + + +```kotlin +// 设置liteClient +val context: CoroutineContext = Dispatchers.Default +val json = Json { ignoreUnknownKeys = true } +val config = json.decodeFromString( + URI("https://ton.org/global-config.json").toURL().readText() +) +val liteClient = LiteClient(context, config) + +val USER_ADDR = AddrStd("钱包地址") +val JETTON_MASTER = AddrStd("Jetton主合约地址") // 例如 EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE + +// 我们需要以切片形式发送常规钱包地址 +val userAddressSlice = CellBuilder.beginCell() + .storeUInt(4, 3) + .storeInt(USER_ADDR.workchainId, 8) + .storeBits(USER_ADDR.address) + .endCell() + .beginParse() + +val response = runBlocking { + liteClient.runSmcMethod( + LiteServerAccountId(JETTON_MASTER.workchainId, JETTON_MASTER.address), + "get_wallet_address", + VmStackValue.of(userAddressSlice) + ) +} + +val stack = response.toMutableVmStack() +val jettonWalletAddress = stack.popSlice().loadTlb(MsgAddressInt) as AddrStd +println("计算的Jetton钱包:") +println(jettonWalletAddress.toString(userFriendly = true)) + +``` + + + + +```py +from pytoniq import LiteBalancer, begin_cell +import asyncio + +async def main(): + provider = LiteBalancer.from_mainnet_config(1) + await provider.start_up() + + JETTON_MASTER_ADDRESS = "EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE" + USER_ADDRESS = "EQAsl59qOy9C2XL5452lGbHU9bI3l4lhRaopeNZ82NRK8nlA" + + + result_stack = await provider.run_get_method(address=JETTON_MASTER_ADDRESS, method="get_wallet_address", + stack=[begin_cell().store_address(USER_ADDRESS).end_cell().begin_parse()]) + jetton_wallet = result_stack[0].load_address() + print(f"用户{USER_ADDRESS}的Jetton钱包地址: {jetton_wallet.to_str(1, 1, 1)}") + await provider.close_all() + +asyncio.run(main()) +``` + + + + +### 如何构建带有评论的 jetton 转账消息? + +为了理解如何构建 token 转账消息,我们使用 [TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#1-transfer),该标准描述了 token 标准。需要注意的是,每个 token 可以有自己的 `decimals`,默认值为 `9`。因此,在下面的示例中,我们将数量乘以 10^9。如果小数位数不同,您**需要乘以不同的值**。 + + + + +```js +import { Address, beginCell, internal, storeMessageRelaxed, toNano } from "@ton/core"; + +async function main() { + const jettonWalletAddress = Address.parse('put your jetton wallet address'); + const destinationAddress = Address.parse('put destination wallet address'); + + const forwardPayload = beginCell() + .storeUint(0, 32) // 0 opcode 意味着我们有一个评论 + .storeStringTail('Hello, TON!') + .endCell(); + + const messageBody = beginCell() + .storeUint(0x0f8a7ea5, 32) // jetton 转账的 opcode + .storeUint(0, 64) // query id + .storeCoins(toNano(5)) // jetton 数量,数量 * 10^9 + .storeAddress(destinationAddress) + .storeAddress(destinationAddress) // 响应目的地 + .storeBit(0) // 无自定义有效负载 + .storeCoins(toNano('0.02')) // 转发金额 + .storeBit(1) // 我们将 forwardPayload 作为引用存储 + .storeRef(forwardPayload) + .endCell(); + + const internalMessage = internal({ + to: jettonWalletAddress, + value: toNano('0.1'), + bounce: true, + body: messageBody + }); + const internalMessageCell = beginCell() + .store(storeMessageRelaxed(internalMessage)) + .endCell(); +} + +main().finally(() => console.log("Exiting...")); +``` + + + + +```js +const TonWeb = require("tonweb"); +const {mnemonicToKeyPair} = require("tonweb-mnemonic"); + +async function main() { + const tonweb = new TonWeb(new TonWeb.HttpProvider( + 'https://toncenter.com/api/v2/jsonRPC', { + apiKey: 'put your api key' + }) + ); + const destinationAddress = new TonWeb.Address('put destination wallet address'); + + const forwardPayload = new TonWeb.boc.Cell(); + forwardPayload.bits.writeUint(0, 32); // 0 opcode 意味着我们有一个评论 + forwardPayload.bits.writeString('Hello, TON!'); + + /* + Tonweb 有一个内置的用于与 jettons 互动的类(class),它有一个创建转账的方法。 + 然而,它有缺点,所以我们手动创建消息体。此外,这种方式让我们更好地理解了 + 存储的内容和它的功能是什么。 + */ + + const jettonTransferBody = new TonWeb.boc.Cell(); + jettonTransferBody.bits.writeUint(0xf8a7ea5, 32); // jetton 转账的 opcode + jettonTransferBody.bits.writeUint(0, 64); // query id + jettonTransferBody.bits.writeCoins(new TonWeb.utils.BN('5')); // jetton 数量,数量 * 10^9 + jettonTransferBody.bits.writeAddress(destinationAddress); + jettonTransferBody.bits.writeAddress(destinationAddress); // 响应目的地 + jettonTransferBody.bits.writeBit(false); // 无自定义有效载荷 + jettonTransferBody.bits.writeCoins(TonWeb.utils.toNano('0.02')); // 转发金额 + jettonTransferBody.bits.writeBit(true); // 我们将 forwardPayload 作为引用存储 + jettonTransferBody.refs.push(forwardPayload); + + const keyPair = await mnemonicToKeyPair('put your mnemonic'.split(' ')); + const jettonWallet = new TonWeb.token.ft.JettonWallet(tonweb.provider, { + address: 'put your jetton wallet address' + }); + + // 可用钱包类型:simpleR1, simpleR2, simpleR3, + // v2R1, v2R2, v3R1, v3R2, v4R1, v4R2 + const wallet = new tonweb.wallet.all['v4R2'](tonweb.provider, { + publicKey: keyPair.publicKey, + wc: 0 // 工作链 + }); + + await wallet.methods.transfer({ + secretKey: keyPair.secretKey, + toAddress: jettonWallet.address, + amount: tonweb.utils.toNano('0.1'), + seqno: await wallet.methods.seqno().call(), + payload: jettonTransferBody, + sendMode: 3 + }).send(); // 创建转账并发送 +} + +main().finally(() => console.log("Exiting...")); +``` + + + + + +```py +from pytoniq import LiteBalancer, WalletV4R2, begin_cell +import asyncio + +mnemonics = ["your", "mnemonics", "here"] + +async def main(): + provider = LiteBalancer.from_mainnet_config(1) + await provider.start_up() + + wallet = await WalletV4R2.from_mnemonic(provider=provider, mnemonics=mnemonics) + USER_ADDRESS = wallet.address + JETTON_MASTER_ADDRESS = "EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE" + DESTINATION_ADDRESS = "EQAsl59qOy9C2XL5452lGbHU9bI3l4lhRaopeNZ82NRK8nlA" + + USER_JETTON_WALLET = (await provider.run_get_method(address=JETTON_MASTER_ADDRESS, + method="get_wallet_address", + stack=[begin_cell().store_address(USER_ADDRESS).end_cell().begin_parse()]))[0] + forward_payload = (begin_cell() + .store_uint(0, 32) # 文本评论 op-code + .store_snake_string("Comment") + .end_cell()) + transfer_cell = (begin_cell() + .store_uint(0xf8a7ea5, 32) # Jetton 转账 op-code + .store_uint(0, 64) # query_id + .store_coins(1) # 要转账的 Jetton 数量,以 nanojetton 计 + .store_address(DESTINATION_ADDRESS) # 目标地址 + .store_address(USER_ADDRESS) # 响应地址 + .store_bit(0) # 自定义有效负载为空 + .store_coins(1) # TON 转发金额,以 nanoton 计 + .store_bit(1) # 将 forward_payload 作为引用存储 + .store_ref(forward_payload) # 转发有效负载 + .end_cell()) + + await wallet.transfer(destination=USER_JETTON_WALLET, amount=int(0.05*1e9), body=transfer_cell) + await client.close_all() + +asyncio.run(main()) +``` + + + + + + +为了表示我们想要包含一个评论,我们指定了 32 个零位,然后写下我们的评论。我们还指定了`响应目的地`,这意味着关于成功转账的响应将发送到这个地址。如果我们不想要响应,我们可以指定 2 个零位而不是一个地址。 + +### 如何向 DEX(DeDust)发送交换(swap)信息? + +DEX使用不同的协议来进行交易。在这个例子中,我们将与**DeDust**交互。 +* [DeDust文档](https://docs.dedust.io/)。 + +DeDust有两种交换路径:jetton <-> jetton 或 toncoin <-> jetton。每种都有不同的方案。要进行交换,您需要将jettons(或toncoin)发送到特定的**vault**并提供特殊的有效负载。以下是将jetton交换为jetton或jetton交换为toncoin的方案: + +```tlb +swap#e3a0d482 _:SwapStep swap_params:^SwapParams = ForwardPayload; + step#_ pool_addr:MsgAddressInt params:SwapStepParams = SwapStep; + step_params#_ kind:SwapKind limit:Coins next:(Maybe ^SwapStep) = SwapStepParams; + swap_params#_ deadline:Timestamp recipient_addr:MsgAddressInt referral_addr:MsgAddress + fulfill_payload:(Maybe ^Cell) reject_payload:(Maybe ^Cell) = SwapParams; +``` +此方案显示了您的jettons转账消息(`transfer#0f8a7ea5`)的`forward_payload`中应包含的内容。 + +以及toncoin到jetton交换的方案: +```tlb +swap#ea06185d query_id:uint64 amount:Coins _:SwapStep swap_params:^SwapParams = InMsgBody; + step#_ pool_addr:MsgAddressInt params:SwapStepParams = SwapStep; + step_params#_ kind:SwapKind limit:Coins next:(Maybe ^SwapStep) = SwapStepParams; + swap_params#_ deadline:Timestamp recipient_addr:MsgAddressInt referral_addr:MsgAddress + fulfill_payload:(Maybe ^Cell) reject_payload:(Maybe ^Cell) = SwapParams; +``` +这是向toncoin **vault**转账的方案。 + +首先,您需要知道您要交换的jettons的**vault**地址或toncoin **vault**地址。可以通过合约[**Factory**](https://docs.dedust.io/reference/factory)的`get_vault_address`方法来完成。您需要按照下面的方案传递一个切片: +```tlb +native$0000 = Asset; // 用于ton +jetton$0001 workchain_id:int8 address:uint256 = Asset; // 用于jetton +``` +此外,对于交换本身,我们需要**pool**地址 - 可以通过get方法`get_pool_address`获得。至于参数 - 根据上述方案的资产切片。作为响应,这两个方法都将返回所请求的**vault** / **pool**地址的切片。 + +这足以构建消息。 + + + + +DEX使用不同的协议来执行它们的工作,我们需要了解关键概念和一些重要组件,还需要知道涉及我们正确执行交换过程的TL-B模式。在这个教程中,我们处理DeDust,TON中已构建完成的著名DEX之一。 +在DeDust中,我们有一个抽象的Asset概念,它包括任何可交换的资产类型。对资产类型的抽象化简化了交换过程,因为资产类型无关紧要,即使是来自其他链的额外代币或资产,在这种方法中也能轻松覆盖。 + + + +以下是DeDust为Asset概念引入的TL-B模式。 + +```tlb +native$0000 = Asset; // 用于ton + +jetton$0001 workchain_id:int8 address:uint256 = Asset; // 用于任何jetton,地址指的是jetton主地址 + +// 即将推出,尚未实现。 +extra_currency$0010 currency_id:int32 = Asset; +``` + +接下来,DeDust引入了三个组件,Vault,Pool和Factory。这些组件是合约或合约组,并且负责j交换过程的部分。Factory充当寻找其他组件地址(如vault和pool)的角色,并且还构建其他组件。 +Vault负责接收转账消息,持有资产,只是通知相应的Pool,"用户A想要将100 X换成Y"。 + + +另一方面,Pool负责根据预定义公式计算交换金额,通知负责资产Y的其他Vault,并告诉它支付给用户计算出的金额。 +交换金额的计算基于数学公式,这意味着到目前为止我们有两种不同的pool,一种被称为Volatile,它基于常用的“恒定产品”公式运作:x * y = k,另一种被称为Stable-Swap - 为近等值资产(例如USDT / USDC,TON / stTON)优化。它使用公式:x3 * y + y3 * x = k。 +所以对于每次交换,我们需要相应的Vault,它只需要实现一个为与特定资产类型交互而定制的特定API。DeDust有三种Vault的实现,Native Vault - 处理原生代币(Toncoin)。Jetton Vault - 管理jettons和Extra-Currency Vault(即将推出)- 为TON额外代币设计。 + + +DeDust提供了一个特殊的SDk来处理合约、组件和API,它是用typescript编写的。 +足够的理论,让我们设置环境以交换一个jetton和TON。 + +```bash +npm install --save @ton/core @ton/ton @ton/crypt + +``` + +我们还需要引入DeDust SDK。 + +```bash +npm install --save @dedust/sdk +``` + +现在我们需要初始化一些对象。 + +```typescript +import { Factory, MAINNET_FACTORY_ADDR } from "@dedust/sdk"; +import { Address, TonClient4 } from "@ton/ton"; + +const tonClient = new TonClient4({ + endpoint: "https://mainnet-v4.tonhubapi.com", +}); +const factory = tonClient.open(Factory.createFromAddress(MAINNET_FACTORY_ADDR)); +//Factory合约用于定位其他合约。 +``` + +交换过程有一些步骤,例如,要用Jetton交换一些TON,我们首先需要找到相应的Vault和Pool +然后确保它们已部署。对于我们的示例TON和SCALE,代码如下: + +```typescript +import { Asset, VaultNative } from "@dedust/sdk"; + +//Native vault是用于TON的 +const tonVault = tonClient.open(await factory.getNativeVault()); +//我们使用factory来找到我们的原生代币(Toncoin)Vault。 +``` + +下一步是找到相应的Pool,这里是(TON和SCALE) + +```typescript +import { PoolType } from "@dedust/sdk"; + +const SCALE_ADDRESS = Address.parse( + "EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE", +); +// SCALE jetton的主地址 +const TON = Asset.native(); +const SCALE = Asset.jetton(SCALE_ADDRESS); + +const pool = tonClient.open( + await factory.getPool(PoolType.VOLATILE, [TON, SCALE]), +); +``` + +现在我们应该确保这些合约存在,因为向一个未激活的合约发送资金可能导致无法找回的损失。 + +```typescript +import { ReadinessStatus } from "@dedust/sdk"; + +// 检查pool是否存在: +if ((await pool.getReadinessStatus()) !== ReadinessStatus.READY) { + throw new Error("Pool (TON, SCALE) 不存在。"); +} + +// 检查vault是否存在: +if ((await tonVault.getReadinessStatus()) !== ReadinessStatus.READY) { + throw new Error("Vault (TON) 不存在。"); +} +``` + +之后,我们可以发送带有TON数量的转账消息 + +```typescript +import { toNano } from "@ton/core"; +import { mnemonicToPrivateKey } from "@ton/crypto"; + + if (!process.env.MNEMONIC) { + throw new Error("需要环境变量MNEMONIC。"); + } + + const mnemonic = process.env.MNEMONIC.split(" "); + + const keys = await mnemonicToPrivateKey(mnemonic); + const wallet = tonClient.open( + WalletContractV3R2.create({ + workchain: 0, + publicKey: keys.publicKey, + }), + ); + +const sender = wallet.sender(keys.secretKey); + +const amountIn = toNano("5"); // 5 TON + +await tonVault.sendSwap(sender, { + poolAddress: pool.address, + amount: amountIn, + gasAmount: toNano("0.25"), +}); +``` + +要用Y交换Token X,流程相同,例如,我们向Vault X发送X token的数量,Vault X接收我们的资产,持有它,并通知(X,Y)pool这个地址请求交换,然后Pool根据计算通知另一个Vault,这里Vault Y向请求交换的用户释放等价的Y。 + +资产之间的差异只是关于转账方法的问题,例如,对于jettons,我们使用转账消息将它们转入Vault,并附加特定的forward_payload,但对于原生代币,我们发送交换消息到Vault,附加相应数量的TON。 + +这是TON和jetton的模式: + +```tlb +swap#ea06185d query_id:uint64 amount:Coins _:SwapStep swap_params:^SwapParams = InMsgBody; +``` + +因此,每个vault和相应的Pool都针对特定的交换设计,并具有为特定资产量身定做的特殊API。 + +这是使用jetton SCALE交换TON的过程。jetton与jetton交换的过程是相同的,唯一的区别是我们应提供TL-B模式中描述的有效负载。 + +```TL-B +swap#e3a0d482 _:SwapStep swap_params:^SwapParams = ForwardPayload; +``` + +```typescript +//寻找Vault +const scaleVault = tonClient.open(await factory.getJettonVault(SCALE_ADDRESS)); +``` + +```typescript +//寻找jetton地址 +import { JettonRoot, JettonWallet } from '@dedust/sdk'; + +const scaleRoot = tonClient.open(JettonRoot.createFromAddress(SCALE_ADDRESS)); +const scaleWallet = tonClient.open(await scaleRoot.getWallet(sender.address); + +// 将jettons转移到Vault(SCALE)并附上相应的有效负载 + +const amountIn = toNano('50'); // 50 SCALE + +await scaleWallet.sendTransfer(sender, toNano("0.3"), { + amount: amountIn, + destination: scaleVault.address, + responseAddress: sender.address, // 将gas返回给用户 + forwardAmount: toNano("0.25"), + forwardPayload: VaultJetton.createSwapPayload({ poolAddress }), +}); +``` + + + + +构建资源片段: +```kotlin +val assetASlice = buildCell { + storeUInt(1,4) + storeInt(JETTON_MASTER_A.workchainId, 8) + storeBits(JETTON_MASTER_A.address) +}.beginParse() +``` + +执行获取方法: +```kotlin +val responsePool = runBlocking { + liteClient.runSmcMethod( + LiteServerAccountId(DEDUST_FACTORY.workchainId, DEDUST_FACTORY.address), + "get_pool_address", + VmStackValue.of(0), + VmStackValue.of(assetASlice), + VmStackValue.of(assetBSlice) + ) +} +stack = responsePool.toMutableVmStack() +val poolAddress = stack.popSlice().loadTlb(MsgAddressInt) as AddrStd +``` + +构建并传输消息: +```kotlin +runBlocking { + wallet.transfer(pk, WalletTransfer { + destination = JETTON_WALLET_A // 你现有的jettons钱包 + bounceable = true + coins = Coins(300000000) // 0.3 ton 的 nanotons + messageData = MessageData.raw( + body = buildCell { + storeUInt(0xf8a7ea5, 32) // op 转账 + storeUInt(0, 64) // 查询_id + storeTlb(Coins, Coins(100000000)) // jettons的数量 + storeSlice(addrToSlice(jettonAVaultAddress)) // 目的地地址 + storeSlice(addrToSlice(walletAddress)) // 响应地址 + storeUInt(0, 1) // 自定义载荷 + storeTlb(Coins, Coins(250000000)) // forward_ton_amount // 0.25 ton 的 nanotons + storeUInt(1, 1) + // 前送载荷 + storeRef { + storeUInt(0xe3a0d482, 32) // op 交换 + storeSlice(addrToSlice(poolAddress)) // pool_addr + storeUInt(0, 1) // 类型 + storeTlb(Coins, Coins(0)) // 限制 + storeUInt(0, 1) // next (用于multihop) + storeRef { + storeUInt(System.currentTimeMillis() / 1000 + 60 * 5, 32) // 截止日期 + storeSlice(addrToSlice(walletAddress)) // 收件人地址 + storeSlice(buildCell { storeUInt(0, 2) }.beginParse()) // 转发人(空地址) + storeUInt(0, 1) + storeUInt(0, 1) + endCell() + } + } + } + ) + sendMode = 3 + }) +} +``` + + + + +此示例展示如何将Ton币兑换为Jetton。 + +```py +from pytoniq import Address, begin_cell, LiteBalancer, WalletV4R2 +import time +import asyncio + +DEDUST_FACTORY = "EQBfBWT7X2BHg9tXAxzhz2aKiNTU1tpt5NsiK0uSDW_YAJ67" +DEDUST_NATIVE_VAULT = "EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_" + +mnemonics = ["your", "mnemonics", "here"] + +async def main(): + provider = LiteBalancer.from_mainnet_config(1) + await provider.start_up() + + wallet = await WalletV4R2.from_mnemonic(provider=provider, mnemonics=mnemonics) + + JETTON_MASTER = Address("EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE") # 想要兑换的jettons地址 + TON_AMOUNT = 10**9 # 1 ton - 兑换数量 + GAS_AMOUNT = 10**9 // 4 # 0.25 ton作为gas + + pool_type = 0 # Volatile pool类型 + + asset_native = (begin_cell() + .store_uint(0, 4) # 资产类型是原生代币 + .end_cell().begin_parse()) + asset_jetton = (begin_cell() + .store_uint(1, 4) # 资产类型是jettons + .store_uint(JETTON_MASTER.wc, 8) + .store_bytes(JETTON_MASTER.hash_part) + .end_cell().begin_parse()) + + stack = await provider.run_get_method( + address=DEDUST_FACTORY, method="get_pool_address", + stack=[pool_type, asset_native, asset_jetton] + ) + pool_address = stack[0].load_address() + + swap_params = (begin_cell() + .store_uint(int(time.time() + 60 * 5), 32) # 截止时间 + .store_address(wallet.address) # 收件人地址 + .store_address(None) # 转发人地址 + .store_maybe_ref(None) # 完成载荷 + .store_maybe_ref(None) # 拒绝载荷 + .end_cell()) + swap_body = (begin_cell() + .store_uint(0xea06185d, 32) # 交换操作码 + .store_uint(0, 64) # 查询id + .store_coins(int(1*1e9)) # 交换数量 + .store_address(pool_address) + .store_uint(0, 1) # 交换类型 + .store_coins(0) # 交换限制 + .store_maybe_ref(None) # 下一步,multi-hop的交换 + .store_ref(swap_params) + .end_cell()) + + await wallet.transfer(destination=DEDUST_NATIVE_VAULT, + amount=TON_AMOUNT + GAS_AMOUNT, # 交换数量+gas + body=swap_body) + + await provider.close_all() + +asyncio.run(main()) + +``` + + + + + + +### 如何使用 NFT 批量部署? + +集合的智能合约允许在单个交易中部署多达250个NFT。但是,实际上,由于1ton的计算费用限制,这个最大数量在100到130个NFT之间。为此,我们需要在字典中存储有关新NFT的信息。 + + + + +```js +import { Address, Cell, Dictionary, beginCell, internal, storeMessageRelaxed, toNano } from "@ton/core"; +import { TonClient } from "@ton/ton"; + +async function main() { + const collectionAddress = Address.parse('put your collection address'); + const nftMinStorage = '0.05'; + const client = new TonClient({ + endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC' // 对于Testnet + }); + const ownersAddress = [ + Address.parse('EQBbQljOpEM4Z6Hvv8Dbothp9xp2yM-TFYVr01bSqDQskHbx'), + Address.parse('EQAUTbQiM522Y_XJ_T98QPhPhTmb4nV--VSPiha8kC6kRfPO'), + Address.parse('EQDWTH7VxFyk_34J1CM6wwEcjVeqRQceNwzPwGr30SsK43yo') + ]; + const nftsMeta = [ + '0/meta.json', + '1/meta.json', + '2/meta.json' + ]; + + const getMethodResult = await client.runMethod(collectionAddress, 'get_collection_data'); + let nextItemIndex = getMethodResult.stack.readNumber(); +``` + + + + +首先,我们假设每存储费用的TON最小金额为`0.05`。这意味着部署一个NFT后,集合的智能合约将向其余额发送这么多TON。接下来,我们获取新NFT所有者和内容的数组。之后,我们通过GET方法`get_collection_data`获取`next_item_index`。 + + + + +```js + let counter = 0; + const nftDict = Dictionary.empty(); + for (let index = 0; index < 3; index++) { + const metaCell = beginCell() + .storeStringTail(nftsMeta[index]) + .endCell(); + const nftContent = beginCell() + .storeAddress(ownersAddress[index]) + .storeRef(metaCell) + .endCell(); + nftDict.set(nextItemIndex, nftContent); + nextItemIndex++; + counter++; + } + + /* + 我们需要编写自定义的序列化和反序列化 + 函数来正确地在字典中存储数据,因为库中的 + 内置函数不适合我们的案例。 + */ + const messageBody = beginCell() + .storeUint(2, 32) + .storeUint(0, 64) + .storeDict(nftDict, Dictionary.Keys.Uint(64), { + serialize: (src, builder) => { + builder.storeCoins(toNano(nftMinStorage)); + builder.storeRef(src); + }, + parse: (src) => { + return beginCell() + .storeCoins(src.loadCoins()) + .storeRef(src.loadRef()) + .endCell(); + } + }) + .endCell(); + + const totalValue = String( + (counter * parseFloat(nftMinStorage) + 0.015 * counter).toFixed(6) + ); + + const internalMessage = internal({ + to: collectionAddress, + value: totalValue, + bounce: true, + body: messageBody + }); + const internalMessageCell = beginCell() + .store(storeMessageRelaxed(internalMessage)) + .endCell(); +} + +main().finally(() => console.log("Exiting...")); +``` + + + + +接下来,我们需要正确计算总交易费用。通过测试得知`0.015`值,但每个案例可能会有所不同。主要取决于NFT的内容,内容的增加导致更高的**转发费**(交付费)。 + +### 如何更改集合的智能合约所有者? + +更改集合的所有者非常简单。要做到这一点,你需要指定 **opcode = 3**,任何 query_id,以及新所有者的地址: + + + + +```js +import { Address, beginCell, internal, storeMessageRelaxed, toNano } from "@ton/core"; + +async function main() { + const collectionAddress = Address.parse('put your collection address'); + const newOwnerAddress = Address.parse('put new owner wallet address'); + + const messageBody = beginCell() + .storeUint(3, 32) // 改变所有者的opcode + .storeUint(0, 64) // query id + .storeAddress(newOwnerAddress) + .endCell(); + + const internalMessage = internal({ + to: collectionAddress, + value: toNano('0.05'), + bounce: true, + body: messageBody + }); + const internalMessageCell = beginCell() + .store(storeMessageRelaxed(internalMessage)) + .endCell(); +} + +main().finally(() => console.log("Exiting...")); +``` + + + + +```js +const TonWeb = require("tonweb"); +const {mnemonicToKeyPair} = require("tonweb-mnemonic"); + +async function main() { + const tonweb = new TonWeb(new TonWeb.HttpProvider( + 'https://toncenter.com/api/v2/jsonRPC', { + apiKey: 'put your api key' + }) + ); + const collectionAddress = new TonWeb.Address('put your collection address'); + const newOwnerAddress = new TonWeb.Address('put new owner wallet address'); + + const messageBody = new TonWeb.boc.Cell(); + messageBody.bits.writeUint(3, 32); // 改变所有者的opcode + messageBody.bits.writeUint(0, 64); // query id + messageBody.bits.writeAddress(newOwnerAddress); + + // 可选的钱包类型有: simpleR1, simpleR2, simpleR3, + // v2R1, v2R2, v3R1, v3R2, v4R1, v4R2 + const keyPair = await mnemonicToKeyPair('put your mnemonic'.split(' ')); + const wallet = new tonweb.wallet.all['v4R2'](tonweb.provider, { + publicKey: keyPair.publicKey, + wc: 0 // 工作链 + }); + + await wallet.methods.transfer({ + secretKey: keyPair.secretKey, + toAddress: collectionAddress, + amount: tonweb.utils.toNano('0.05'), + seqno: await wallet.methods.seqno().call(), + payload: messageBody, + sendMode: 3 + }).send(); // 创建并发送转账 +} + +main().finally(() => console.log("Exiting...")); +``` + + + + +### 如何更改集合智能合约中的内容? + +要更改智能合约集合的内容,我们需要了解它是如何存储的。集合将所有内容存储在一个单一的cell中,其中包含两个cell:**集合内容** 和 **NFT 通用内容**。第一个cell包含集合的元数据,而第二个cell包含NFT元数据的基本URL。 + +通常,集合的元数据存储格式类似于 `0.json` 并且继续递增,而这个文件之前的地址保持不变。正是这个地址应该存储在NFT通用内容中。 + + + + +```js +import { Address, beginCell, internal, storeMessageRelaxed, toNano } from "@ton/core"; + +async function main() { + const collectionAddress = Address.parse('put your collection address'); + const newCollectionMeta = 'put url fol collection meta'; + const newNftCommonMeta = 'put common url for nft meta'; + const royaltyAddress = Address.parse('put royalty address'); + + const collectionMetaCell = beginCell() + .storeUint(1, 8) // 我们拥有链下元数据 + .storeStringTail(newCollectionMeta) + .endCell(); + const nftCommonMetaCell = beginCell() + .storeUint(1, 8) // 我们拥有链下元数据 + .storeStringTail(newNftCommonMeta) + .endCell(); + + const contentCell = beginCell() + .storeRef(collectionMetaCell) + .storeRef(nftCommonMetaCell) + .endCell(); + + const royaltyCell = beginCell() + .storeUint(5, 16) // factor + .storeUint(100, 16) // base + .storeAddress(royaltyAddress) // 该地址将接收每次销售金额的5% + .endCell(); + + const messageBody = beginCell() + .storeUint(4, 32) // 更改内容的 opcode + .storeUint(0, 64) // query id + .storeRef(contentCell) + .storeRef(royaltyCell) + .endCell(); + + const internalMessage = internal({ + to: collectionAddress, + value: toNano('0.05'), + bounce: true, + body: messageBody + }); + + const internalMessageCell = beginCell() + .store(storeMessageRelaxed(internalMessage)) + .endCell(); +} + +main().finally(() => console.log("Exiting...")); +``` + + + + +```js +const TonWeb = require("tonweb"); +const {mnemonicToKeyPair} = require("tonweb-mnemonic"); + +async function main() { + const tonweb = new TonWeb(new TonWeb.HttpProvider( + 'https://testnet.toncenter.com/api/v2/jsonRPC', { + apiKey: 'put your api key' + }) + ); + const collectionAddress = new TonWeb.Address('put your collection address'); + const newCollectionMeta = 'put url fol collection meta'; + const newNftCommonMeta = 'put common url for nft meta'; + const royaltyAddress = new TonWeb.Address('put royalty address'); + + const collectionMetaCell = new TonWeb.boc.Cell(); + collectionMetaCell.bits.writeUint(1, 8); // 我们拥有链下元数据 + collectionMetaCell.bits.writeString(newCollectionMeta); + const nftCommonMetaCell = new TonWeb.boc.Cell(); + nftCommonMetaCell.bits.writeUint(1, 8); // 我们拥有链下元数据 + nftCommonMetaCell.bits.writeString(newNftCommonMeta); + + const contentCell = new TonWeb.boc.Cell(); + contentCell.refs.push(collectionMetaCell); + contentCell.refs.push(nftCommonMetaCell); + + const royaltyCell = new TonWeb.boc.Cell(); + royaltyCell.bits.writeUint(5, 16); // factor + royaltyCell.bits.writeUint(100, 16); // base + royaltyCell.bits.writeAddress(royaltyAddress); // 该地址将接收每次销售金额的5% + + const messageBody = new TonWeb.boc.Cell(); + messageBody.bits.writeUint(4, 32); + messageBody.bits.writeUint(0, 64); + messageBody.refs.push(contentCell); + messageBody.refs.push(royaltyCell); + + // 可选的钱包类型有: simpleR1, simpleR2, simpleR3, + // v2R1, v2R2, v3R1, v3R2, v4R1, v4R2 + const keyPair = await mnemonicToKeyPair('put your mnemonic'.split(' ')); + const wallet = new tonweb.wallet.all['v4R2'](tonweb.provider, { + publicKey: keyPair.publicKey, + wc: 0 // 工作链 + }); + + await wallet.methods.transfer({ + secretKey: keyPair.secretKey, + toAddress: collectionAddress, + amount: tonweb.utils.toNano('0.05'), + seqno: await wallet.methods.seqno().call(), + payload: messageBody, + sendMode: 3 + }).send(); // 创建并发送转账 +} + +main().finally(() => console.log("Exiting...")); +``` + + + +另外,我们需要在消息中包含版权信息,因为使用这个 opcode 时,它们也会改变。需要注意的是,不是一定要在所有地方指定新值。例如,如果只需要更改NFT通用内容,则所有其他值可以按照之前的指定。 + +### 处理蛇形Cells + +有时候,在cell最多可以包含 **1023位** 的情况下,需要存储长字符串(或其他大型信息)。这种情况下,我们可以使用 蛇形cells。蛇形cells 是包含对另一个cell的引用的cell,而该cell又包含对另一个cell的引用,依此类推。 + + + + +```js +const TonWeb = require("tonweb"); + +function writeStringTail(str, cell) { + const bytes = Math.floor(cell.bits.getFreeBits() / 8); // 1字符 = 8位 + if(bytes < str.length) { // 如果我们不能写下所有字符串 + cell.bits.writeString(str.substring(0, bytes)); // 写入字符串的一部分 + const newCell = writeStringTail(str.substring(bytes), new TonWeb.boc.Cell()); // 创建新cell + cell.refs.push(newCell); // 将新cell添加到当前cell的引用中 + } else { + cell.bits.writeString(str); // 写下所有字符串 + } + + return cell; +} + +function readStringTail(cell) { + const slice = cell.beginParse(); // 将cell转换为切片 + if(cell.refs.length > 0) { + const str = new TextDecoder('ascii').decode(slice.array); // 解码 uint8array 为字符串 + return str + readStringTail(cell.refs[0]); // 读取下一个cell + } else { + return new TextDecoder('ascii').decode(slice.array); + } +} + +let cell = new TonWeb.boc.Cell(); +const str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In euismod, ligula vel lobortis hendrerit, lectus sem efficitur enim, vel efficitur nibh dui a elit. Quisque augue nisi, vulputate vitae mauris sit amet, iaculis lobortis nisi. Aenean molestie ultrices massa eu fermentum. Cras rhoncus ipsum mauris, et egestas nibh interdum in. Maecenas ante ipsum, sodales eget suscipit at, placerat ut turpis. Nunc ac finibus dui. Donec sit amet leo id augue tempus aliquet. Vestibulum eu aliquam ex, sit amet suscipit odio. Vestibulum et arcu dui."; +cell = writeStringTail(str, cell); +const text = readStringTail(cell); +console.log(text); +``` + + + + +这个示例将帮助你了解如何使用递归来处理这类cell。 + +### 如何解析账户的交易记录(转账、Jettons、NFTs)? + +通过 `getTransactions` API方法可以获取到一个账户上的交易记录列表。它返回一个`Transaction`对象的数组,其中每个项都有很多属性。然而,最常用的字段有: + - 初始化这笔交易的消息的Sender, Body和Value + - 交易的哈希和逻辑时间(LT) + +_Sender_ 和 _Body_ 字段可用于确定消息的类型(常规转账、jetton转账、nft转账等等)。 + +以下是一个例子,展示了如何获取任何区块链账户上最近的5笔交易,根据类型解析它们,并在循环中打印出来。 + + + + +```js +import { Address, TonClient, beginCell, fromNano } from '@ton/ton'; + +async function main() { + const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + apiKey: '1b312c91c3b691255130350a49ac5a0742454725f910756aff94dfe44858388e', + }); + + const myAddress = Address.parse('EQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doc2lN'); // 你想要从中获取交易记录的地址 + + const transactions = await client.getTransactions(myAddress, { + limit: 5, + }); + + for (const tx of transactions) { + const inMsg = tx.inMessage; + + if (inMsg?.info.type == 'internal') { + // 我们在这里只处理内部消息,因为它们最常用 + // 对于外部消息,一些字段是空的,但主要结构是相似的 + const sender = inMsg?.info.src; + const value = inMsg?.info.value.coins; + + const originalBody = inMsg?.body.beginParse(); + let body = originalBody.clone(); + if (body.remainingBits < 32) { + // 如果正文没有操作码:这是一条没有评论的简单消息 + console.log(`Simple transfer from ${sender} with value ${fromNano(value)} TON`); + } else { + const op = body.loadUint(32); + if (op == 0) { + // 如果操作码是0:这是一条有评论的简单消息 + const comment = body.loadStringTail(); + console.log( + `Simple transfer from ${sender} with value ${fromNano(value)} TON and comment: "${comment}"` + ); + } else if (op == 0x7362d09c) { + // 如果操作码是0x7362d09c:这是一个Jetton转账通知 + + body.skip(64); // 跳过query_id + const jettonAmount = body.loadCoins(); + const jettonSender = body.loadAddressAny(); + const originalForwardPayload = body.loadBit() ? body.loadRef().beginParse() : body; + let forwardPayload = originalForwardPayload.clone(); + + // 重要:我们必须验证这条消息的来源,因为它可能被伪造 + const runStack = (await client.runMethod(sender, 'get_wallet_data')).stack; + runStack.skip(2); + const jettonMaster = runStack.readAddress(); + const jettonWallet = ( + await client.runMethod(jettonMaster, 'get_wallet_address', [ + { type: 'slice', cell: beginCell().storeAddress(myAddress).endCell() }, + ]) + ).stack.readAddress(); + if (!jettonWallet.equals(sender)) { + // 如果发送者不是我们真正的JettonWallet:这条消息被伪造了 + console.log(`FAKE Jetton transfer`); + continue; + } + + if (forwardPayload.remainingBits < 32) { + // 如果forward payload没有操作码:这是一个简单的Jetton转账 + console.log(`Jetton transfer from ${jettonSender} with value ${fromNano(jettonAmount)} Jetton`); + } else { + const forwardOp = forwardPayload.loadUint(32); + if (forwardOp == 0) { + // 如果forward payload的操作码是0:这是一次带有评论的简单Jetton转账 + const comment = forwardPayload.loadStringTail(); + console.log( + `Jetton transfer from ${jettonSender} with value ${fromNano( + jettonAmount + )} Jetton and comment: "${comment}"` + ); + } else { + // 如果forward payload的操作码是其他:这是一条具有任意结构的消息 + // 如果你知道其他操作码,你可以手动解析它,或者直接以十六进制形式打印 + console.log( + `Jetton transfer with unknown payload structure from ${jettonSender} with value ${fromNano( + jettonAmount + )} Jetton and payload: ${originalForwardPayload}` + ); + } + + console.log(`Jetton Master: ${jettonMaster}`); + } + } else if (op == 0x05138d91) { + // 如果操作码是0x05138d91:这是一个NFT转账通知 + + body.skip(64); // 跳过query_id + const prevOwner = body.loadAddress(); + const originalForwardPayload = body.loadBit() ? body.loadRef().beginParse() : body; + let forwardPayload = originalForwardPayload.clone(); + + // 重要:我们必须验证这条消息的来源,因为它可能被伪造 + const runStack = (await client.runMethod(sender, 'get_nft_data')).stack; + runStack.skip(1); + const index = runStack.readBigNumber(); + const collection = runStack.readAddress(); + const itemAddress = ( + await client.runMethod(collection, 'get_nft_address_by_index', [{ type: 'int', value: index }]) + ).stack.readAddress(); + + if (!itemAddress.equals(sender)) { + console.log(`FAKE NFT Transfer`); + continue; + } + + if (forwardPayload.remainingBits < 32) { + // 如果forward payload没有操作码:这是一个简单的NFT转账 + console.log(`NFT transfer from ${prevOwner}`); + } else { + const forwardOp = forwardPayload.loadUint(32); + if (forwardOp == 0) { + // 如果forward payload的操作码是0:这是一次带有评论的简单NFT转账 + const comment = forwardPayload.loadStringTail(); + console.log(`NFT transfer from ${prevOwner} with comment: "${comment}"`); + } else { + // 如果forward payload的操作码是其他:这是一条具有任意结构的消息 + // 如果你知道其他操作码,你可以手动解析它,或者直接以十六进制形式打印 + console.log( + `NFT transfer with unknown payload structure from ${prevOwner} and payload: ${originalForwardPayload}` + ); + } + } + + console.log(`NFT Item: ${itemAddress}`); + console.log(`NFT Collection: ${collection}`); + } else { + // 如果操作码是其他的:这是一条具有任意结构的消息 + // 如果你知道其他操作码,你可以手动解析它,或者直接以十六进制形式打印 + console.log( + `Message with unknown structure from ${sender} with value ${fromNano( + value + )} TON and body: ${originalBody}` + ); + } + } + } + console.log(`Transaction Hash: ${tx.hash().toString('hex')}`); + console.log(`Transaction LT: ${tx.lt}`); + console.log(); + } +} + +main().finally(() => console.log('Exiting...')); +``` + + + + + +```py +from pytoniq import LiteBalancer, begin_cell +import asyncio + +async def parse_transactions(transactions): + for transaction in transactions: + if not transaction.in_msg.is_internal: + continue + if transaction.in_msg.info.dest.to_str(1, 1, 1) != MY_WALLET_ADDRESS: + continue + + sender = transaction.in_msg.info.src.to_str(1, 1, 1) + value = transaction.in_msg.info.value_coins + if value != 0: + value = value / 1e9 + + if len(transaction.in_msg.body.bits) < 32: + print(f"TON transfer from {sender} with value {value} TON") + else: + body_slice = transaction.in_msg.body.begin_parse() + op_code = body_slice.load_uint(32) + + # TextComment + if op_code == 0: + print(f"TON transfer from {sender} with value {value} TON and comment: {body_slice.load_snake_string()}") + + # Jetton Transfer Notification + elif op_code == 0x7362d09c: + body_slice.load_bits(64) # skip query_id + jetton_amount = body_slice.load_coins() / 1e9 + jetton_sender = body_slice.load_address().to_str(1, 1, 1) + if body_slice.load_bit(): + forward_payload = body_slice.load_ref().begin_parse() + else: + forward_payload = body_slice + + jetton_master = (await provider.run_get_method(address=sender, method="get_wallet_data", stack=[]))[2].load_address() + jetton_wallet = (await provider.run_get_method(address=jetton_master, method="get_wallet_address", + stack=[ + begin_cell().store_address(MY_WALLET_ADDRESS).end_cell().begin_parse() + ]))[0].load_address().to_str(1, 1, 1) + + if jetton_wallet != sender: + print("FAKE Jetton Transfer") + continue + + if len(forward_payload.bits) < 32: + print(f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton") + else: + forward_payload_op_code = forward_payload.load_uint(32) + if forward_payload_op_code == 0: + print(f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton and comment: {forward_payload.load_snake_string()}") + else: + print(f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton and unknown payload: {forward_payload} ") + + # NFT 转移通知 + elif op_code == 0x05138d91: + body_slice.load_bits(64) # skip query_id + prev_owner = body_slice.load_address().to_str(1, 1, 1) + if body_slice.load_bit(): + forward_payload = body_slice.load_ref().begin_parse() + else: + forward_payload = body_slice + + stack = await provider.run_get_method(address=sender, method="get_nft_data", stack=[]) + index = stack[1] + collection = stack[2].load_address() + item_address = (await provider.run_get_method(address=collection, method="get_nft_address_by_index", + stack=[index]))[0].load_address().to_str(1, 1, 1) + + if item_address != sender: + print("FAKE NFT Transfer") + continue + + if len(forward_payload.bits) < 32: + print(f"NFT transfer from {prev_owner}") + else: + forward_payload_op_code = forward_payload.load_uint(32) + if forward_payload_op_code == 0: + print(f"NFT transfer from {prev_owner} with comment: {forward_payload.load_snake_string()}") + else: + print(f"NFT transfer from {prev_owner} with unknown payload: {forward_payload}") + + print(f"NFT Item: {item_address}") + print(f"NFT Collection: {collection}") + print(f"Transaction hash: {transaction.cell.hash.hex()}") + print(f"Transaction lt: {transaction.lt}") + +MY_WALLET_ADDRESS = "EQAsl59qOy9C2XL5452lGbHU9bI3l4lhRaopeNZ82NRK8nlA" +provider = LiteBalancer.from_mainnet_config(1) + +async def main(): + await provider.start_up() + transactions = await provider.get_transactions(address=MY_WALLET_ADDRESS, count=5) + await parse_transactions(transactions) + await provider.close_all() + +asyncio.run(main()) +``` + + + + +请注意,这个示例只涵盖了入站消息的最简单情况,其中只需在单个账户上获取交易记录即可。如果你想进一步深入并处理更复杂的交易和消息链,你应该考虑`tx.outMessages`字段。它包含了这笔交易所产生的输出消息的列表。为了更好地理解整个逻辑,你可以阅读这些文章: +* [消息概览](/develop/smart-contracts/guidelines/message-delivery-guarantees) +* [内部消息](/develop/smart-contracts/guidelines/internal-messages) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/README.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/README.mdx new file mode 100644 index 0000000000..703248ff14 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/README.mdx @@ -0,0 +1,91 @@ +--- +description: Telegram 小程序(或 TMA)是在 Telegram 信使中运行的 Web 应用。它们使用 Web 技术构建 —— HTML、CSS 和 JavaScript。 +--- +import Button from '@site/src/components/button' + +# 什么是小程序? + +
+ +
+ +Telegram 小程序(或 TMA)是在 Telegram 信使中运行的 Web 应用。它们使用 Web 技术构建 —— HTML、CSS 和 JavaScript。 + +解锁通往 **8 亿 Telegram 用户** 的大门。想象一下,仅通过一次点击就能将您的应用或服务提供给这庞大的用户基础。 + + + + + + + + +## 概览 + +Telegram 机器人可以完全替代任何网站。它们支持无缝授权、通过 20 个支付提供商(包括开箱即用的 Google Pay 和 Apple Pay)进行集成支付、向用户发送定制的推送通知,以及更多功能。 + +通过小程序,机器人获取了全新的维度。机器人开发者可以使用 JavaScript(世界上使用最广泛的编程语言)创建灵活多变的界面。 + +以下是有关 Telegram 小程序的一些关键点: + +- **在 Telegram 内集成**:Telegram 小程序旨在无缝集成到 Telegram 应用中,为用户提供一致的体验。它们可以从 Telegram 聊天或群组对话中访问。 +- **增强功能**:Telegram 小程序可以提供广泛的功能。它们可用于各种目的,如游戏、内容分享、生产力工具等。这些应用扩展了 Telegram 平台超出基本消息传递的能力。 +- **跨平台兼容性**:由于 Telegram 小程序基于 Web,它们可在 Android、iOS、PC、Mac 和 Linux 的 Telegram 应用上使用。用户可以一键访问它们,无需额外安装。 +- **机器人互动**:Telegram 小程序通常利用 Telegram 机器人提供交互式和自动化的体验。机器人可以响应用户输入、执行任务,并在小程序内促进互动。 +- **开发框架**:开发者可以使用 HTML、CSS 和 JavaScript 等 Web 开发技术构建 Telegram 小程序。此外,Telegram 提供了开发者工具和 API 用于创建这些应用并将其与 Telegram 平台集成。 +- **变现机会**:Telegram 小程序可以通过各种方式变现,如应用内购买、订阅模型或广告,使其对开发者和企业具有吸引力。 +- **Web3 准备就绪**:TON SDK;TON Connect 是钱包和 TON 中应用之间的通信协议;代币 +- **社区发展**:Telegram 拥有一个蓬勃发展的开发者社区,许多第三方开发者创建并与用户分享他们的 Telegram 小程序。这种社区驱动的方法促进了可用应用的创新和多样性。 + +总体而言,Telegram 小程序作为增强 Telegram 体验的手段,提供额外的功能和服务,同时为开发者提供在 Telegram 生态系统内创建和分发应用的机会。 + +## 入门 + +### TMA 文档 + +- [Telegram 小程序文档](https://docs.telegram-mini-apps.com) —— TWA 的社区驱动文档。 +- [Telegram 提供的 TMA 文档](https://core.telegram.org/bots/webapps) —— Telegram 网站上的完整描述。 + +### Telegram 开发者社区 + +加入专门的 Telegram 开发者聊天群,讨论小程序的开发并获得支持: + + + + + +### 小程序 SDK + +- [twa-dev/sdk](https://github.com/twa-dev/sdk) —— TMA SDK 的 NPM 包 +- [twa-dev/boilerplate](https://github.com/twa-dev/Boilerplate) —— TWA 的另一个样板。 +- [twa-dev/Mark42](https://github.com/twa-dev/Mark42) —— Mark42 是 TWA 的一个简单轻量级可抖动 UI 库。 +- [ton-defi-org/tonstarter-twa](https://github.com/ton-defi-org/tonstarter-twa) —— 与 TON 互动的新 TWA 模板。 + +## 与 TON Connect 集成 + +借助 TON Connect 协议与用户钱包连接。在此了解更多信息: + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/grants.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/grants.mdx new file mode 100644 index 0000000000..8ef3471250 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/grants.mdx @@ -0,0 +1,17 @@ +import Button from '@site/src/components/button' + +# 资助 + +## Telegram Web3 Grants + +为了进一步推动创新,[TON Foundation](https://ton.foundation/en) 推出了 [Telegram Web3 Grants](http://t.me/toncoin/991) 计划。这项倡议旨在激励更多的开发者创造新平台或将现有平台迁移到 TON 和 Telegram。 + +## 如何参与? + +无论您是成熟的企业、新创公司,还是个人开发者,现在都是参与的最佳时机。通过 [这个机器人](https://t.me/app_moderation_bot) 提交您的 Telegram 应用,并考虑参与 [Grants计划](https://t.me/trendingapps/33)。让我们一起开拓未来。 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/monetization.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/monetization.mdx new file mode 100644 index 0000000000..6d1e960a89 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/monetization.mdx @@ -0,0 +1,66 @@ +import Button from '@site/src/components/button' +import ThemedImage from '@theme/ThemedImage'; + +# 支付 + +## Wallet Pay + +

+Wallet Pay illustration +

+Wallet Pay 是 Telegram 小程序的主要支付系统,支持加密货币和法币交易。监控您的订单统计数据并轻松提款。 +Wallet Pay 嵌入于 Wallet 生态系统内,便于商家和客户之间的无缝金融交易。 + +相关链接 + +- [Wallet Pay 商业支持](https://t.me/WalletPay_supportbot) 是一个 Telegram 机器人,用于联系 Wallet Pay 支持团队。 +- [Demo Store Bot](https://t.me/PineAppleDemoWPStoreBot) 是一个 Telegram 机器人,用于介绍 Wallet Pay 功能。(注意:所有支付都是用真实资产进行的) +- [商户社区](https://t.me/+6TReWBEyZxI5Njli) 是一个 Telegram 群组,用于群组成员之间分享经验和解决方案。 + +## TON Connect + +

+ +

+ +TON Connect 是 **钱包** 与 **应用** 在 TON 上的通信协议。 + +**应用** 建立在 TON 上的能够提供丰富的功能和高性能,并旨在通过智能合约保护用户资金。因为应用程序采用了区块链等去中心化技术构建,通常被称为去中心化应用(dApps)。 + +**钱包** 提供批准交易的用户界面,并在用户的个人设备上安全地保存用户的加密密钥。这种关注点的分离使得创新迅速且安全性高:钱包无需自己构建封闭生态系统,而应用程序不需要承担持有终端用户帐户的风险。 + +TON Connect 旨在提供钱包与应用程序之间无缝的用户体验。 + + + +## 集成代币 + +您可以在 TON 区块链上创建自己的代币并将其集成到您的应用程序中。您也可以将现有的代币集成到您的应用程序中。 + + + +## TON 上的订阅 + +由于 TON 区块链上的交易快速且网络费用低廉,您可以通过智能合约处理链上的周期性付款。 + +例如,用户可以订阅数字内容(或其他任何东西)并被收取每月 1 TON 的费用。 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/publishing.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/publishing.mdx new file mode 100644 index 0000000000..2ad6f16355 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/publishing.mdx @@ -0,0 +1,114 @@ +import Button from '@site/src/components/button' + +# 发布小程序 + +作为开发者,了解我们所处的生态系统是非常重要的。Telegram 为小程序开发者提供了独特的机会,得益于其强大的平台和广泛的用户基础。本文将指导您通过 Telegram 可用的渠道发布您的小程序。 + +## tApps 中心 + +**什么是 tApps 中心?** TON 基金会引入了 Telegram 应用中心,以创建 Telegram 机器人和小程序 (TMAs) 的集中式库。这个平台旨在通过提供一个类似于您已经熟悉的知名应用商店的界面来增强用户体验。 + +**广泛的生态系统支持**。Telegram 应用中心不仅仅关注 TON 生态系统;它还欢迎来自其他区块链的应用。您甚至不需要 web3 集成就可以成为这个目录的一部分。这种包容性的做法旨在将 Telegram 建立成一个“一切皆应用”的超级应用,类似于微信等平台,用户可以在一个界面访问多种服务。 + + + +### 为什么在 tApps 中心发布? + +**更大的曝光度**。Telegram 应用中心为开发者提供了一个展示他们项目给广泛受众的绝佳机会,使得吸引用户和投资者变得更加容易。 + +**社区精神**。该平台采取了以社区为中心的方法,鼓励合作以及资源和知识的共享。 + + + +## 在 Telegram 内启动 + +Telegram 目前支持六种不同的方式来启动小程序:从 [键盘按钮](https://core.telegram.org/bots/webapps#keyboard-button-web-apps)、从 [内联按钮](https://core.telegram.org/bots/webapps#inline-button-web-apps)、从 [机器人菜单按钮](https://core.telegram.org/bots/webapps#launching-web-apps-from-the-menu-button)、通过 [内联模式](https://core.telegram.org/bots/webapps#inline-mode-web-apps)、从 [直接链接](https://core.telegram.org/bots/webapps#direct-link-web-apps) 启动 — 甚至从 [附件菜单](https://core.telegram.org/bots/webapps#launching-web-apps-from-the-attachment-menu) 中启动。 + +![](/img/docs/telegram-apps/publish-tg-1.jpeg) + +### 键盘按钮小程序 + +**简而言之:** 从 **web_app** 类型的 [键盘按钮](https://core.telegram.org/bots/api#keyboardbutton) 启动的小程序可以使用 [Telegram.WebApp.sendData](https://core.telegram.org/bots/webapps#initializing-web-apps) 将数据以 _服务消息_ 的形式发送回机器人。这使得机器人能够在不与任何外部服务器通信的情况下产生响应。 + +用户可以使用 [自定义键盘](https://core.telegram.org/bots#keyboards)、[机器人消息下的按钮](https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating),以及发送自由格式的 **文本消息** 或 Telegram 支持的任何 **附件类型** 与机器人互动:照片和视频、文件、地点、联系人和投票。为了更多的灵活性,机器人可以利用 **HTML5** 的全部功能来创建用户友好的输入接口。 + +您可以发送一个 **web_app** 类型的 [KeyboardButton](https://core.telegram.org/bots/api#keyboardbutton),以从指定的 URL 打开一个小程序。 + +要将数据从用户传回机器人,小程序可以调用 [Telegram.WebApp.sendData](https://core.telegram.org/bots/webapps#initializing-web-apps) 方法。数据将以字符串形式通过服务消息传输给机器人。机器人在接收到数据后可以继续与用户通信。 + +**适合:** + +- **自定义数据输入界面**(用于选择日期的个性化日历;带高级搜索选项的列表选择数据;让用户“转动轮盘”并从可用选项中选择一个的随机器等等。) +- **可复用组件**,不依赖于特定的机器人。 + +### 内联按钮小程序 + +**简而言之:** 对于像 [@DurgerKingBot](https://t.me/durgerkingbot) 这样更互动的小程序,使用 **web_app** 类型的 [内联键盘按钮](https://core.telegram.org/bots/api#inlinekeyboardbutton),它可以获取基本用户信息,并可用于代表用户向机器人聊天发送消息。 + +如果仅接收文本数据不足够,或者您需要更高级和个性化的界面,您可以使用 **web_app** 类型的 [内联键盘按钮](https://core.telegram.org/bots/api#inlinekeyboardbutton) 打开一个小程序。 + +从按钮中打开的小程序会和按钮中指定的 URL 一样。除了用户的 [主题设置](https://core.telegram.org/bots/webapps#color-schemes),它还会收到基本的用户信息(ID、名称、用户名、语言代码)和一个唯一的会话标识符,**query_id**,这允许代表用户的消息被发送回机器人。 + +机器人可以调用 Bot API 方法 [answerWebAppQuery](https://core.telegram.org/bots/api#answerwebappquery) 来发送内联消息从用户返回到机器人并关闭小程序。接收到消息后,机器人可以继续与用户通信。 + +**适合:** + +- 完全成熟的 Web 服务和任何类型的集成。 +- 使用案例实际上是 **无限的**。 + +### 从菜单按钮启动小程序 + +**简而言之:** 小程序可以从定制的菜单按钮启动。这仅仅提供了一种更快访问应用的方式,并且与 [从内联按钮启动小程序](https://core.telegram.org/bots/webapps#inline-button-web-apps) 没有区别。 + +默认情况下,与机器人的聊天总是显示一个方便的 **菜单按钮**,提供快速访问所有列出的 [命令](https://core.telegram.org/bots#commands)。使用 [Bot API 6.0](https://core.telegram.org/bots/api-changelog#april-16-2022),这个按钮可以用来 **启动一个小程序** 代替。 + +要配置菜单按钮,您必须指定它应该显示的文本和小程序的 URL。有两种设置这些参数的方法: + +- 为了为 **所有用户** 自定义按钮,请使用 [@BotFather](https://t.me/botfather)(/setmenubutton 命令或 _Bot 设置 > 菜单按钮_)。 +- 为了为 **所有用户** 和 **特定用户** 自定义按钮,请使用 Bot API 中的 [setChatMenuButton](https://core.telegram.org/bots/api#setchatmenubutton) 方法。例如,根据用户的语言更改按钮文本,或根据用户在您的机器人中的设置显示不同的 Web 应用链接。 + +除此之外,通过菜单按钮打开的 Web 应用与 [使用内联按钮时](https://core.telegram.org/bots/webapps#inline-button-web-apps) 的工作方式完全相同。 + +[@DurgerKingBot](https://t.me/durgerkingbot) 允许从内联按钮和菜单按钮启动其小程序。 + +### 内联模式小程序 + +**简而言之:** 通过 **web_app** 类型的 [InlineQueryResultsButton](https://core.telegram.org/bots/api#inlinequeryresultsbutton) 启动的小程序可以在内联模式中的任何地方使用。用户可以在 Web 界面中创建内容,然后通过内联模式无缝发送到当前聊天。 + +您可以在 [answerInlineQuery](https://core.telegram.org/bots/api#answerinlinequery) 方法中使用 _button_ 参数来显示一个特殊的“切换到小程序”的按钮,它可以在内联结果的上方或者代替内联结果显示。这个按钮将 **打开一个指定 URL 的小程序**。完成后,您可以调用 [Telegram.WebApp.switchInlineQuery](https://core.telegram.org/bots/webapps#initializing-web-apps) 方法将用户送回内联模式。 + +内联小程序 **无法访问** 聊天 - 它们不能读取消息或代表用户发送新消息。要发送消息,用户必须被重定向到 **内联模式** 并主动选择一个结果。 + +**适合:** + +- 在内联模式中的完全成熟的 Web 服务和集成。 + +### 直接链接小程序 + +**简而言之:** 小程序机器人可以从任何聊天中的直接链接启动。它们支持 _startapp_ 参数并且知道当前聊天上下文。 + +NEW 您可以使用直接链接在当前聊天中 **直接打开一个小程序**。如果链接中包含非空的 _startapp_ 参数,它将在 _start_param_ 字段和 GET 参数 _tgWebAppStartParam_ 中传递给小程序。 + +在这种模式下,小程序可以使用 _chat_type_ 和 _chat_instance_ 参数来跟踪当前聊天上下文。这引入了对多个聊天成员的 **并发** 和 **共享** 使用的支持 - 创建实时白板、团体订购、多人游戏和类似应用。 + +从直接链接打开的小程序 **无法访问** 聊天 - 它们不能读取消息或代表用户发送新消息。要发送消息,用户必须被重定向到 **内联模式** 并主动选择一个结果。 + +**示例** + +- https://t.me/botusername/appname +- https://t.me/botusername/appname?startapp=command + +**适合:** + +- 任何用户都可以一键打开的完全成熟的 Web 服务和集成。 +- 协作、多人或团队合作导向的聊天上下文中的服务。 + +使用案例实际上是 **无限的**。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/testing-apps.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/testing-apps.mdx new file mode 100644 index 0000000000..5e01dc0937 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/testing-apps.mdx @@ -0,0 +1,92 @@ +import ConceptImage from '@site/src/components/conceptImage' + +# 测试小程序 + +## 在测试环境中使用机器人 + +要登录测试环境,请使用以下任一方式: + +- **iOS:** 在设置图标上点击 10 次 > 账号 > 登录另一个账户 > 测试。 +- **Telegram 桌面版:** 打开 ☰ 设置 > Shift + Alt + 右键点击“添加账号”并选择“测试服务器”。 +- **macOS:** 点击设置图标 10 次以打开调试菜单,⌘ + 点击“添加账号”并通过电话号码登录。 + +测试环境与主环境完全分开,因此你需要创建一个**新的用户账号**和一个与 @BotFather 的**新机器人**。 + +收到你的机器人令牌后,你可以按照此格式向 Bot API 发送请求: + +`https://api.telegram.org/bot/test/METHOD_NAME` + +**注意:** 在使用测试环境时,你可以使用未启用 TLS 的 HTTP 链接来测试你的小程序。 + +## 小程序的调试模式 + +使用这些工具在你的小程序中找到特定于应用程序的问题: + +### 安卓 + +- 在你的设备上[启用 USB-调试](https://developer.chrome.com/docs/devtools/remote-debugging/)。 +- 在 Telegram 设置中,一直滚动到底部,两次按住**版本号**。 +- 在调试设置中选择_启用 WebView 调试_。 +- 将手机连接到电脑并在 Chrome 中打开 chrome://inspect/#devices - 当你在手机上启动时,你会在那里看到你的小程序。 + +### Telegram 桌面版(Windows 和 Linux) + +- 在**Windows**或**Linux**上下载并启动 Telegram 桌面版的[测试版](https://desktop.telegram.org/changelog#beta-version)(尚不支持 macOS 上的 Telegram 桌面版)。 +- 转到 _设置 > 高级 > 实验性设置 > 启用 webview 检查_。 +- 在 WebView 中点击右键并选择 _审查_。 + +### Telegram macOS + +- 下载并启动 Telegram macOS 的[测试版](https://telegram.org/dl/macos/beta)。 +- 快速点击设置图标 5 次以打开调试菜单并启用“调试小程序”。 + +在小程序中点击右键并选择 _检查元素_。 + +## 使用 Eruda 测试 + +[Eruda](https://github.com/liriliri/eruda) 是一个提供基于 Web 的控制台的工具,用于在移动设备和桌面浏览器上调试和检查网页。以下是在 Telegram 小程序项目中使用 Eruda 的逐步指南。 + +![1](/img/docs/telegram-apps/eruda-1.png) + +### 步骤 1:包含 Eruda 库 + +首先,你需要在你的 HTML 文件中包含 Eruda 库。你可以通过 CDN 包含它: + +```html + + +``` + +或者你可以在 npm 上获取它。 + +```bash npm2yarn +npm install eruda --save +``` + +### 步骤 2:初始化 Eruda + +接下来,你需要初始化 Eruda。你通常在网页加载时执行此操作。如果你通过 CDN 运行 Eruda。 + +```html + + + +``` + +如果你喜欢新的工具和包,则将此脚本添加到你的项目中: + +```jsx +import eruda from 'eruda' + +eruda.init() +``` + +### 步骤 3:启动 Eruda + +部署你的小程序,启动它,然后只需按 Eruda 图标即可开始调试! + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/tips-and-tricks.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/tips-and-tricks.mdx new file mode 100644 index 0000000000..80b21df786 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/guidelines/tips-and-tricks.mdx @@ -0,0 +1,54 @@ +# 小贴士和技巧 + +在本页中,您将找到一系列与TMA中常见问题相关的常见问题解答。 + +### 如何解决 TMA 中的缓存溢出问题? + +:::tip +只有重新安装Telegram应用程序可能会有所帮助。 +::: + +### 有关 HTML 文件的缓存头部是否有任何推荐? + +:::tip +It's preferable to switch off cache in the HTML. To ensure your cache switched off, specify headers in your request according the following: + +```curl +Cache-Control: no-store, must-revalidate +Pragma: no-cache +Expires: 0 +``` + +::: + +### 推荐用于 TMA 开发的 IDE 是什么? + +在Google Chrome中进行开发过程更加方便,因为有熟悉的开发工具。 + +您可以检索小程序的启动参数并在Chrome中打开此链接。在我们的案例中,最简单的方法是从Telegram的Web版本检索启动参数:https://web.telegram.org/ + +### 结束行为 + +在许多Web应用程序中,用户在向上滚动时可能会无意中关闭应用。如果他们将应用程序的一个部分拖得太远,无意中触发了应用关闭,就会发生这种情况。 + +

+
+ closing_behaviour_durgerking +
+

+ +为了防止这种意外关闭,启用TMA中的`closing_behavior`。这个方法会添加一个对话框,用户可以批准或拒绝关闭Web应用。 + +```typescript +window.Telegram.WebApp.enableClosingConfirmation() +``` + +## 如何为 TMA 中特定语言指定描述? + +:::tip +You can configure your description with following methods: + +- https://core.telegram.org/bots/api#setmydescription +- https://core.telegram.org/bots/api#setmyshortdescription + +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/app-examples.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/app-examples.mdx new file mode 100644 index 0000000000..bc81986000 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/app-examples.mdx @@ -0,0 +1,310 @@ +import Button from '@site/src/components/button' + +# TMA 示例 + +查看下面的示例,了解如何创建您自己的 Telegram 小程序。 + +## 基础 TMA 示例 + +

+
+ logo of telegram mini apps +
+

+ +这是一个使用纯 JavaScript、HTML 和 CSS 实现的基础且直接的 Telegram 小程序(TMA)。该项目旨在提供一个创建简单 TMA 并在 Telegram 内启动它的最简化示例,无需依赖复杂的构建工具或前沿库。 + +- 可通过直接链接访问应用:[t.me/simple_telegram_mini_app_bot/app](https://t.me/simple_telegram_mini_app_bot/app) +- 或者您可以通过机器人菜单按钮启动应用:[t.me/simple_telegram_mini_app_bot](https://t.me/simple_telegram_mini_app_bot) +- 部署 URL:https://telegram-mini-apps-dev.github.io/vanilla-js-boilerplate + + + + + +### 特点 + +- 极简用户界面。 +- 未使用外部库或框架。 +- 易于理解和修改。 + +### 入门 + +#### 必要条件 + +要运行此示例,您需要一个支持 JavaScript 的现代 web 浏览器。 + +#### 安装 + +1. 克隆此库到您的本地机器: + +```bash +git clone https://github.com/Telegram-Mini-Apps-Dev/vanilla-js-boilerplate.git +``` + +2. 导航至项目目录: + +```bash +cd vanilla-js-boilerplate +``` + +在您偏好的代码编辑器或 IDE 中打开 index.html。 + +### 使用 + +1. 在您偏好的代码编辑器或 IDE 中打开 index.html。 +2. 做出您的改动 +3. 创建您自己的 GitHub 库,提交并推送您的更新。 +4. 转到您的 GitHub 库页面并打开设置。检查“页面”选项卡和“构建与部署”部分。如果选择了 GitHub Actions 选项,资产应该被部署到页面上,并且会有像 `https://.github.io/vanilla-js-boilerplate/` 这样的 URL。您可以复制此 URL,并使用 [BotFather](https://tg.me/BotFather) 机器人创建您自己的 TWA。 + +## 当前 TMA 示例 + +### 介绍 + +Vite(在法语中意味着“快”)是一个前端构建工具和开发服务器,旨在为现代 web 项目提供更快、更精简的开发体验。我们将利用 Vite 创建 Telegram 小程序示例。 + +您可以在这里找到示例项目 https://github.com/Telegram-Mini-Apps-Dev/vite-boilerplate 或者您可以按照以下说明操作。 + +### 必要条件 + +我们将从搭建您的 Vite 项目开始。 + +使用 NPM: + +```bash +$ npm create vite@latest +``` + +使用 Yarn: + +```bash +$ yarn create vite +``` + +然后按照提示操作! + +或者您可以直接运行以下命令创建带有 TypeScript 支持的 React 项目: + +```bash +# npm 7+, extra double-dash is needed: +npm create vite my-react-telegram-web-app -- --template react-ts + +# or yarn +yarn create vite my-react-telegram-web-app --template react-ts + +# this will change the directory to recently created project +cd my-react-telegram-web-app +``` + +### 小程序的开发 + +现在我们需要开始项目的开发模式,请在终端运行以下命令: + +```bash +# npm +npm install +npm run dev --host + +# or yarn +yarn +yarn dev --host +``` + +`--host` 选项允许获取带有 IP 地址的 URL,您可以在开发过程中用于测试。重要说明:在开发模式下,我们将使用自签名 SSL 证书,这将使我们能够仅在 Telegram 的 web 版本 [https://web.telegram.org](https://web.telegram.org/a/#6549734463)/ 中测试我们的应用,由于其他平台(iOS、Android、MacOS)的政策。 + +我们需要添加 `@vitejs/plugin-basic-ssl` 插件: + +```bash npm2yarn +npm install @vitejs/plugin-basic-ssl +``` + +现在我们需要更改 `vite.config.ts`。添加导入: + +```jsx +import basicSsl from '@vitejs/plugin-basic-ssl'; +``` + +并添加插件 + +```jsx +export default defineConfig({ + plugins: [react(), basicSsl()] +}); +``` + +您可以使用 `ngrok` 将您的本地服务器暴露给互联网并附加 SSL 证书。您将能够在所有 Telegram 平台上使用热模块替换进行开发。打开新的终端窗口并运行: + +```bash +# where 5173 is the port number from npm/yarn dev --host +ngrok http 5173 +``` + +此外,我们将准备我们的项目以部署到 GitHub Pages: + +```jsx +export default defineConfig({ + plugins: [react(), basicSsl()], + build: { + outDir: './docs' + }, + base: './' +}); +``` + +我们将使用 GitHub Actions 部署脚本,该脚本将在针对 master 分支的推送上运行。从您的项目根目录开始: + +```bash +# we are going to create GitHub Actions config for deployment +mkdir .github +cd .github +mkdir workflows +cd workflows +touch static.yml +``` + +现在将此配置添加到 `static.yml`: + +```yaml +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ['master'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: './' + - name: Install dependencies + run: npm install + - name: Build + run: npm run build + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + # Upload dist repository + path: './docs' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 +``` + +别忘了在您的 GitHub 库的设置→页面中为构建和部署选择 GitHub Actions 选项。现在,每次推送后,您的代码将被部署到页面。 + +![Screenshot 2023-09-11 at 22.07.44.png](/img/docs/telegram-apps/modern-1.png) + +现在我们将添加 `@twa-dev/sdk`。Telegram 通过[链接](https://core.telegram.org/bots/webapps#initializing-web-apps)分发 SDK。这是一种处理库的旧时方式。`@twa-dev/sdk` 包允许像处理 npm 包一样使用 SDK,并支持 TypeScript。 + +```bash npm2yarn +npm install @twa-dev/sdk +``` + +打开 `/src/main.tsx` 文件并添加以下内容: + +```tsx +import WebApp from '@twa-dev/sdk' + +WebApp.ready(); + +ReactDOM.createRoot... +``` + +`WebApp.ready()` - 是一个方法,向 Telegram 应用程序通知小程序已准备好显示。建议尽可能早地调用此方法,一旦加载了所有必要的接口元素。一旦调用此方法,加载的占位符将被隐藏,小程序将被显示。 + +然后我们将添加与用户的一些交互。进入 `src/App.tsx`,我们将添加带有警告的按钮。 + +```tsx +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +import WebApp from '@twa-dev/sdk' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + React

+
+ +
+ {/* Here we add our button with alert callback */} +
+ +
+ + ) +} + +export default App +``` + +现在我们需要创建 Telegram 机器人,这样我们就可以在Telegram应用程序中启动 Telegram 小程序。 + +### 为应用设置机器人 + +要将您的小程序连接到 Telegram,您需要创建一个机器人并为其设置小程序。按照以下步骤设置新的 Telegram 机器人: + + + +### 提示 + +使用自签名 SSL 证书,您可以遇到此类警告的问题。点击“Advanced”按钮,然后点击 `Proceed `。未采取这些步骤,您将无法在 Telegram 的 web 版本中进行调试 + +![Screenshot 2023-09-11 at 18.58.24.png](/img/docs/telegram-apps/modern-2.png) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/design-guidelines.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/design-guidelines.mdx new file mode 100644 index 0000000000..6fa94014db --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/design-guidelines.mdx @@ -0,0 +1,64 @@ +# TMA 设计指南 + +:::info +从 **6.10** 版本开始,Telegram 更新了小程序的配色方案:修正了一些旧的配色,同时添加了新的配色。 +::: + +为了更好地理解背景,让我们回顾一下历史上的更新。 + +更新日志。 + +1. `bg_color` 和 `secondary_bg_color` 已更新。 + +![](/img/docs/tma-design-guidelines/tma-design_1.png) + +原因是: + +• 最初,这些颜色是为页面背景而不是UI控制项设计的。 + +• 因此,为了保持一致性,这些颜色已经被更新。 + +• 为了给不同部分和卡片着色,添加了 `section_bg_color`。 + +为了提高应用程序的外观,您应该稍微调整颜色变量的使用。 + +上述是一个清晰的例子,准确解释了iOS上会发生什么变化。在Android上不应有任何变化。 + +新颜色。 +此外,还增加了许多新颜色。它们中的大多数在Android上最为明显。因此,下面的示例将基于Android展示,但适用于所有平台。 + + + +2. 对于小程序,现在可以使用Telegram header的颜色。 + + + +3. 已经提供了 token accent_text_color,它适用于应用程序中的任何突出元素。以前,大家都使用了不太合适的 dark link_color。 + +![](/img/docs/tma-design-guidelines/tma-design_4.png) + +4. 对于所有次要cell标签,现在最好使用 `subtitle_text_color`。这将提供更高对比度的标签,提高应用程序的可访问性。 + +![](/img/docs/tma-design-guidelines/tma-design_5.png) + +5. 对于卡片的节的header,现在有了专用的token:`section_header_text_color`。 + +![](/img/docs/tma-design-guidelines/tma-design_6.png) + +6. 对于按下会导致破坏性行动的cell,现在可以使用 `destructive_text_color` 而不是自定义颜色。 + + +

+
+ +
+

+``` + +7. 一个合理的问题是:现在应该如何使用 `link_color` 和 `hint_color`? + +我建议将它们用作部分下方的提示部分的颜色,以及 `secondary_bg_color` 这类背景的链接颜色。 + +## 参阅 + +- [原始来源](https://telegra.ph/Changes-in-Color-Variables-for-Telegram-Mini-Apps-11-20) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/step-by-step-guide.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/step-by-step-guide.mdx new file mode 100644 index 0000000000..fbd26f16b9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tma/tutorials/step-by-step-guide.mdx @@ -0,0 +1,63 @@ +# TMA 启动教程 + +Telegram Mini Apps(TMA)是在 Telegram 消息传递应用程序内运行的 Web 应用程序。它们是使用 Web 技术构建的 —— HTML、CSS 和 JavaScript。Telegram Mini Apps 可用于创建 DApps、游戏以及其他可以在 Telegram 内运行的应用程序类型。 + +## 创建你的应用程序 + +1. 要将你的小程序连接到 Telegram,请使用此代码放置 SDK 脚本 `telegram-web-app.js`: + +```html + +``` + +:::tip +It's preferable to switch off cache in the HTML. To ensure your cache is switched off, specify headers in your request according to the following: + +```curl +Cache-Control: no-store, must-revalidate +Pragma: no-cache +Expires: 0 +``` + +::: + +2. 脚本连接后,一个 **[window.Telegram.WebApp](https://core.telegram.org/bots/webapps#initializing-web-apps)** 对象就变得可用。你可以在此阅读更多有关利用 [`telegram-web-app.js`](https://docs.ton.org/develop/dapps/telegram-apps/app-examples#basic-twa-example) 创建小程序的信息。 + +3. 连接 SDK 的现代方式是 Telegram Mini Apps SDK 的 npm 包: + +```bash npm2yarn +npm i @twa-dev/sdk +``` + +你可以在此处找到 [`@twa-dev/sdk`](https://docs.ton.org/develop/dapps/telegram-apps/app-examples#modern-twa-example) 的指南。 + +5. 当你的小程序准备就绪并部署到 Web 服务器时,进行下一步。 + +## 为应用程序设置机器人 + +要将你的小程序连接到 Telegram,你需要创建一个机器人并为其设置一个小程序。按照这些步骤设置一个新的 Telegram 机器人: + +### 1. 与 BotFather 启动对话 + +- 打开 Telegram 应用程序或网页版本。 +- 在搜索栏中搜索 `@BotFather` 或跟随链接 https://t.me/BotFather。 +- 通过点击 `START` 按钮来开始与 BotFather 的聊天。 + +### 2. 创建一个新机器人 + +- 向 BotFather 发送 `/newbot` 命令。 +- BotFather 将要求你为你的机器人选择一个名字。这是一个显示名称,可以包含空格。 +- 接下来,你将被要求为你的机器人选择一个用户名。这必须以 `bot` 结尾(例如,`sample_bot`)并且是唯一的。 + +### 3. 设置机器人小程序 + +- 向 BotFather 发送 `/mybots` 命令。 +- 从列表中选择你的机器人并选择 **Bot 设置** 选项 +- 选择 **菜单按钮** 选项 +- 选择 **编辑菜单按钮 URL** 选项并发送你的 Telegram 小程序的 URL,例如从 GitHub Pages 部署的链接。 + +### 4. 访问机器人 + +- 现在你可以在 Telegram 的搜索栏中使用其用户名搜索你的机器人。 +- 按下紧挨附件选择器旁边的按钮来在消息传递应用程序中启动你的 Telegram 小程序 +- 你太棒了! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/mint-your-first-token.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/mint-your-first-token.md new file mode 100644 index 0000000000..7bc2e45c01 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/mint-your-first-token.md @@ -0,0 +1,214 @@ +# 铸造你的第一个 Jetton + +欢迎,开发者!很高兴你能来到这里。👋 + +在这篇文章中,我们将告诉你如何在TON上创建你的第一个可替代代币(Jetton)。 + +为了铸造Jettons,我们将使用[TON Minter](https://minter.ton.org/)浏览器服务。 + +## 📖 你将学到什么 + +在这篇文章中,你将学会如何: + +- 使用浏览器部署Jetton +- 自定义你的代币 +- 管理和使用你的代币 +- 编辑代币参数 + +## 📌 在开始之前准备 + +1. 首先你需要有一个[Tonhub](https://ton.app/wallets/tonhub-wallet) / [Tonkeeper](https://ton.app/wallets/tonkeeper)钱包或[Chrome扩展](https://ton.app/wallets/chrome-plugin)或任何其他该服务支持的钱包。 +2. 你的余额上必须有超过0.25 Toncoin + 覆盖区块链手续费的资金。 + +:::tip 新手提示 +~0.5 TON 对这个教程来说绝对足够了。 +::: + +## 🚀 开始吧! + +使用你的网络浏览器打开服务[TON Minter](https://minter.ton.org/)。 + +![image](/img/tutorials/jetton/jetton-main-page.png) + +### 使用浏览器部署 Jetton + +#### 连接钱包 + +点击`Connect Wallet`按钮连接你的[Tonhub](https://ton.app/wallets/tonhub-wallet)钱包或[Chrome扩展](https://ton.app/wallets/chrome-plugin)或以下的其他钱包。 + +#### ![image](/img/tutorials/jetton/jetton-connect-wallet.png) + +在[移动钱包(Tonhub等)](https://ton.app/wallets/tonhub-wallet)**扫描二维码**或通过[Chrome扩展](https://ton.app/wallets/chrome-plugin)**登录**到钱包。 + +#### 填写相关信息 + +1. 名称(通常1-3个词)。 +2. 符号(通常3-5个大写字符)。 +3. 数量(例如,1,000,000)。 +4. 代币描述(可选)。 + +#### 代币标志URL(可选) + +![image](/img/tutorials/jetton/jetton-token-logo.png) + +如果你想拥有一个吸引人的Jetton代币,你需要一个存放在某处的漂亮标志。例如: + +- https://bitcoincash-example.github.io/website/logo.png + +:::info +You can easily find out about url placement of the logo in the [repository](https://github.com/ton-blockchain/minter-contract#jetton-metadata-field-best-practices) in paragraph "Where is this metadata stored". + +- 链上。 +- 链下IPFS。 +- 链下网站。 + ::: + +#### 如何创建你的标志URL? + +1. 准备一个256x256像素的代币标志PNG图片,带有透明背景。 +2. 获取你的标志链接。一个好的解决方案是[GitHub页面](https://pages.github.com/)。我们就用它吧。 +3. [创建一个名为`website`的新公共代码库](https://docs.github.com/en/get-started/quickstart/create-a-repo)。 +4. 上传你准备好的图片到git并启用`GitHub页面`。 + 1. [为你的库添加GitHub页面](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site)。 + 2. [上传你的图片并获取链接](https://docs.github.com/en/repositories/working-with-files/managing-files/adding-a-file-to-a-repository)。 +5. 如果你有自己的域名,那么最好使用`.org`而不是`github.io`。 + +## 💸 发送Jettons + +在屏幕的右侧,你可以**发送代币**到多货币钱包,如[Tonkeeper](https://tonkeeper.com/)或[Tonhub](https://ton.app/wallets/tonhub-wallet)。 + +![image](/img/tutorials/jetton/jetton-send-tokens.png) + +:::info +You always also **burn** your Jettons to reduce their amount. + +![image](/img/tutorials/jetton/jetton-burn-tokens.png) +::: + +### 📱 使用 Tonkeeper 从手机发送代币 + +必要条件: + +1. 你的余额上必须已经有代币,才能发送它们。 +2. 必须有至少0.1 Toncoin来支付交易费。 + +#### 分步指南 + +然后回到**你的代币**,设置要发送的**数量**,并输入**接收者地址**。 + +![image](/img/tutorials/jetton/jetton-send-tutorial.png) + +## 📚 在网站上使用代币 + +通过在网站顶端的**搜索框**输入代币地址,你可以访问并使用所有者权限来管理。 + +:::info +The address can be found on the right side if you are already in the owner panel, or you can find the token address when receiving an airdrop. + +![image](/img/tutorials/jetton/jetton-wallet-address.png) +::: + +## ✏️ Jetton(代币)定制 + +使用[FunC](/develop/func/overview)语言,你可以根据你的喜好更改代币的行为。 + +要进行任何更改,请从这里开始: + +- https://github.com/ton-blockchain/minter-contract + +### 开发者的分步指南 + +1. 确保你有[tonstarter-contracts](https://github.com/ton-defi-org/tonstarter-contracts)库中的所有"依赖和要求"。 +2. 克隆[minter-contract库](https://github.com/ton-blockchain/minter-contract)并重命名该项目。 +3. 要安装,你需要在根目录下打开一个终端并运行: + +```bash npm2yarn +npm install +``` + +4. 以同样的方式编辑原始智能合约文件,所有合约文件都在`contracts/*.fc` + +5. 使用下面的命令构建项目: + +```bash npm2yarn +npm run build +``` + +构建结果将描述创建所需文件的过程,以及查找智能合约的过程。 + +:::info +阅读控制台,那里有很多提示! +::: + +6. 你可以使用以下命令测试你的更改: + +```bash npm2yarn +npm run test +``` + +7. 通过更改`build/jetton-minter.deploy.ts`中的JettonParams对象,编辑**名称**和其它代币元数据。 + +```js +// This is example data - Modify these params for your own jetton! +// - Data is stored on-chain (except for the image data itself) +// - Owner should usually be the deploying wallet's address. + +const jettonParams = { + owner: Address.parse("EQD4gS-Nj2Gjr2FYtg-s3fXUvjzKbzHGZ5_1Xe_V0-GCp0p2"), + name: "MyJetton", + symbol: "JET1", + image: "https://www.linkpicture.com/q/download_183.png", // Image url + description: "My jetton", +}; +``` + +8. 使用以下命令部署一个代币: + +```bash npm2yarn +npm run deploy +``` + +运行你的项目的结果: + + +```js +> @ton-defi.org/jetton-deployer-contracts@0.0.2 deploy +> ts-node ./build/_deploy.ts + +================================================================= +Deploy script running, let's find some contracts to deploy.. + +* We are working with 'mainnet' + +* Config file '.env' found and will be used for deployment! + - Wallet address used to deploy from is: YOUR-ADDRESS + - Wallet balance is YOUR-BALANCE TON, which will be used for gas + +* Found root contract 'build/jetton-minter.deploy.ts - let's deploy it': + - Based on your init code+data, your new contract address is: YOUR-ADDRESS + - Let's deploy the contract on-chain.. + - Deploy transaction sent successfully + - Block explorer link: https://tonwhales.com/explorer/address/YOUR-ADDRESS + - Waiting up to 20 seconds to check if the contract was actually deployed.. + - SUCCESS! Contract deployed successfully to address: YOUR-ADDRESS + - New contract balance is now YOUR-BALANCE TON, make sure it has enough to pay rent + - Running a post deployment test: +{ + name: 'MyJetton', + description: 'My jetton', + image: 'https://www.linkpicture.com/q/download_183.png', + symbol: 'JET1' +} +``` + + +## 接下来 + +如果你想更深入地了解,请阅读Tal Kol的这篇文章: + +- [如何以及为什么要分片你的智能合约——研究TON Jettons的解剖学](https://blog.ton.org/how-to-shard-your-ton-smart-contract-and-why-studying-the-anatomy-of-tons-jettons) + +## 参考资料 + +- 项目:https://github.com/ton-blockchain/minter-contract +- 作者Slava:([Telegram @delovoyslava](https://t.me/delovoyslava),[GitHub上的delovoyhomie](https://github.com/delovoyhomie)) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/nft-minting-guide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/nft-minting-guide.md new file mode 100644 index 0000000000..836a0fb91a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/nft-minting-guide.md @@ -0,0 +1,1121 @@ +# 逐步创建 NFT 集合的教程 + +## 👋 引言 +非同质化代币(NFT)已成为数字艺术和收藏品世界中最热门的话题之一。NFT是使用区块链技术验证所有权和真实性的独特数字资产。它们为创作者和收藏家提供了将数字艺术、音乐、视频和其他形式的数字内容货币化和交易的新可能性。近年来,NFT市场爆炸性增长,一些高调的销售额达到了数百万美元。在本文中,我们将逐步在TON上构建我们的NFT集合。 + +**这是你在本教程结束时将创建的鸭子集合的精美图片:** + +![](/img/tutorials/nft/collection.png) + +## 🦄 你将会学到什么 +1. 你将在TON上铸造NFT集合 +2. 你将理解TON上的NFT是如何工作的 +3. 你将把NFT出售 +4. 你将把元数据上传到[pinata.cloud](https://pinata.cloud) + +## 💡 必要条件 +你必须已经有一个测试网钱包,里面至少有2 TON。可以从[@testgiver_ton_bot](https://t.me/testgiver_ton_bot)获取测试网币。 + +:::info 如何打开我的Tonkeeper钱包的测试网版本? +要在tonkeeper中打开测试网网络,请转到设置并点击位于底部的tonkeeper logo 5次,之后选择测试网而不是主网。 +::: + +我们将使用Pinata作为我们的IPFS存储系统,因此你还需要在[pinata.cloud](https://pinata.cloud)上创建一个帐户并获取api_key & api_secreat。官方Pinata [文档教程](https://docs.pinata.cloud/pinata-api/authentication)可以帮助完成这一点。只要你拿到这些api令牌,我就在这里等你!!! + +## 💎 什么是 TON 上的 NFT? + +在开始我们教程的主要部分之前,我们需要了解一下通常意义上TON中NFT是如何工作的。出乎意料的是,我们将从解释ETH中NFT的工作原理开始,为了理解TON中NFT实现的特殊性,与行业中常见的区块链相比。 + +### ETH 上的 NFT 实现 + +ETH中NFT的实现极其简单 - 存在1个主要的集合合约,它存储一个简单的哈希映射,该哈希映射反过来存储此集合中NFT的数据。所有与此集合相关的请求(如果任何用户想要转移NFT、将其出售等)都特别发送到此1个集合合约。 + +![](/img/tutorials/nft/eth-collection.png) + +### 在 TON 中如此实现可能出现的问题 + +在TON的上下文中,此类实现的问题由[TON的NFT标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md)完美描述: + +* 不可预测的燃料消耗。在TON中,字典操作的燃料消耗取决于确切的键集。此外,TON是一个异步区块链。这意味着,如果你向一个智能合约发送一个消息,那么你不知道有多少来自其他用户的消息会在你的消息之前到达智能合约。因此,你不知道当你的消息到达智能合约时字典的大小会是多少。这对于简单的钱包 -> NFT智能合约交互是可以的,但对于智能合约链,例如钱包 -> NFT智能合约 -> 拍卖 -> NFT智能合约,则不可接受。如果我们不能预测燃料消耗,那么可能会出现这样的情况:NFT智能合约上的所有者已经更改,但拍卖操作没有足够的Toncoin。不使用字典的智能合约可以提供确定性的燃料消耗。 + +* 不可扩展(成为瓶颈)。TON的扩展性基于分片的概念,即在负载下自动将网络划分为分片链。流行NFT的单个大智能合约与这一概念相矛盾。在这种情况下,许多交易将引用一个单一的智能合约。TON架构为分片的智能合约提供了设施(参见白皮书),但目前尚未实现。 + +*简而言之,ETH的解决方案不可扩展且不适用于像TON这样的异步区块链。* + +### TON 上的 NFT 实现 + +在TON中,我们有1个主合约-我们集合的智能合约,它存储它的元数据和它所有者的地址,以及最重要的 - 如果我们想要创建("铸造")新的NFT项目 - 我们只需要向这个集合合约发送消息。而这个集合合约将为我们部署新NFT项目的合约,并提供我们提供的数据。 + +![](/img/tutorials/nft/ton-collection.png) + +:::info +如果你想更深入地了解这个话题,可以查看[TON上的NFT处理](/develop/dapps/asset-processing/nfts)文章或阅读[NFT标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) +::: + +## ⚙ 设置开发环境 +让我们从创建一个空项目开始: + +1. 创建新文件夹 +`mkdir MintyTON` +2. 打开这个文件夹 +`cd MintyTON` +3. 初始化我们的项目 `yarn init -y` +4. 安装typescript +``` +yarn add typescript @types/node -D +``` +5. 将以下配置复制到tsconfig.json中 +```json +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": ["ES2022"], + "moduleResolution": "node", + "sourceMap": true, + "outDir": "dist", + "baseUrl": "src", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "strict": true, + "esModuleInterop": true, + "strictPropertyInitialization": false + }, + "include": ["src/**/*"] +} +``` +6. 向package.json添加脚本以构建并启动我们的应用程序 +```json +"scripts": { + "start": "tsc --skipLibCheck && node dist/app.js" + }, +``` + +7. 安装所需的库 +``` +yarn add @pinata/sdk dotenv ton ton-core ton-crypto +``` +8. 创建`.env`文件并根据此模板添加你自己的数据 +``` +PINATA_API_KEY=your_api_key +PINATA_API_SECRET=your_secret_api_key +MNEMONIC=word1 word2 word3 word4 +TONCENTER_API_KEY=aslfjaskdfjasasfas +``` +你可以从[@tonapibot](https://t.me/tonapibot)获取toncenter api key并选择mainnet或testnet。在 `MNEMONIC` 变量中存储集合所有者钱包种子短语的24个单词。 + +太好了!现在我们准备好开始为我们的项目编写代码了。 + +### 编写辅助函数 + +首先,让我们在`src/utils.ts`中创建一个函数,该函数将通过助记词打开我们的钱包并返回它的publicKey/secretKey。 + +我们根据24个单词(种子短语)获取一对密钥: +```ts +import { KeyPair, mnemonicToPrivateKey } from "ton-crypto"; +import { + beginCell, + Cell, + OpenedContract, + TonClient, + WalletContractV4, +} from "ton"; + +export type OpenedWallet = { + contract: OpenedContract; + keyPair: KeyPair; +}; + +export async function openWallet(mnemonic: string[], testnet: boolean) { + const keyPair = await mnemonicToPrivateKey(mnemonic); +} +``` + +创建一个类实例以与toncenter交互: +```ts +const toncenterBaseEndpoint: string = testnet + ? "https://testnet.toncenter.com" + : "https://toncenter.com"; + +const client = new TonClient({ + endpoint: `${toncenterBaseEndpoint}/api/v2/jsonRPC`, + apiKey: process.env.TONCENTER_API_KEY, +}); +``` + +最后打开我们的钱包: +```ts +const wallet = WalletContractV4.create({ + workchain: 0, + publicKey: keyPair.publicKey, + }); + +const contract = client.open(wallet); +return { contract, keyPair }; +``` + +很好,之后我们将创建我们项目的主要入口点`app.ts`。 +在这里,我们将使用刚刚创建的`openWallet`函数并调用我们的主函数`init`。 +目前足够了。 +```ts +import * as dotenv from "dotenv"; + +import { openWallet } from "./utils"; +import { readdir } from "fs/promises"; + +dotenv.config(); + +async function init() { + const wallet = await openWallet(process.env.MNEMONIC!.split(" "), true); +} + +void init(); +``` + +最后,让我们创建`delay.ts`文件,在这个文件中,我们将创建一个函数来等待`seqno`增加。 +```ts +import { OpenedWallet } from "utils"; + +export async function waitSeqno(seqno: number, wallet: OpenedWallet) { + for (let attempt = 0; attempt < 10; attempt++) { + await sleep(2000); + const seqnoAfter = await wallet.contract.getSeqno(); + if (seqnoAfter == seqno + 1) break; + } +} + +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +``` + +:::info 什么是seqno? +简单来说,seqno就是由钱包发送的外部交易的计数器。 +Seqno用于预防重放攻击。当交易发送到钱包智能合约时,它将交易的seqno字段与其存储中的字段进行比较。如果它们匹配,交易被接受并且存储的seqno增加一。如果它们不匹配,交易被丢弃。这就是为什么我们需要在每次发送外部交易后稍等一会儿。 +::: + + +## 🖼 准备元数据 + +元数据 - 只是一些简单的信息,将描述我们的NFT或集合。例如它的名称、它的描述等。 + +首先,我们需要在`/data/images`中存储我们NFT的图片,命名为`0.png`、`1.png`...用于物品的照片,以及`logo.png`用于我们集合的头像。你可以轻松[下载](/img/tutorials/nft/ducks.zip)包含鸭子图片的包或将你的图片放入该文件夹。我们还将在`/data/metadata/`文件夹中存储所有的元数据文件。 + +### NFT 规范 + +TON上的大多数产品支持以下元数据规范来存储有关NFT集合的信息: + +名称 | 解释 +---|--- +name | 集合名称 +description | 集合描述 +image | 将显示为头像的图片链接。支持的链接格式:https、ipfs、TON Storage。 +cover_image | 将显示为集合封面图片的图片链接。 +social_links | 项目社交媒体配置文件的链接列表。使用不超过10个链接。 + +![image](/img/tutorials/nft/collection-metadata.png) + +根据这些信息,让我们创建我们自己的元数据文件`collection.json`,它将描述我们集合的元数据! +```json +{ + "name": "Ducks on TON", + "description": "This collection is created for showing an example of minting NFT collection on TON. You can support creator by buying one of this NFT.", + "social_links": ["https://t.me/DucksOnTON"] +} +``` +请注意,我们没有写"image"参数,稍后你会知道原因,请稍等! + +在创建了集合的元数据文件之后,我们需要创建我们NFT的元数据。 + +NFT项目元数据的规范: + +名称 | 解释 +---|--- +name | NFT名称。推荐长度:不超过15-30个字符 +description | NFT描述。推荐长度:不超过500个字符 +image | NFT图片链接。 +attributes | NFT属性。属性列表,其中指定了trait_type (属性名称)和value (属性的简短描述)。 +lottie | Lottie动画的json文件链接。如果指定,在NFT页面将播放来自此链接的Lottie动画。 +content_url | 额外内容的链接。 +content_type | 通过content_url链接添加的内容的类型。例如,视频/mp4文件。 + +![image](/img/tutorials/nft/item-metadata.png) + + +```json +{ + "name": "Duck #00", + "description": "What about a round of golf?", + "attributes": [{ "trait_type": "Awesomeness", "value": "Super cool" }] +} +``` + +之后,你可以根据需要创建尽可能多的NFT项目及其元数据文件。 + +### 上传元数据 + +现在让我们编写一些代码,将我们的元数据文件上传到IPFS。创建 `metadata.ts` 文件并添加所需的导入: +```ts +import pinataSDK from "@pinata/sdk"; +import { readdirSync } from "fs"; +import { writeFile, readFile } from "fs/promises"; +import path from "path"; +``` + +之后,我们需要创建一个函数,这个函数将把我们文件夹中的所有文件实际上传到IPFS: +```ts +export async function uploadFolderToIPFS(folderPath: string): Promise { + const pinata = new pinataSDK({ + pinataApiKey: process.env.PINATA_API_KEY, + pinataSecretApiKey: process.env.PINATA_API_SECRET, + }); + + const response = await pinata.pinFromFS(folderPath); + return response.IpfsHash; +} +``` + +太棒了!让我们回到之前的问题:为什么我们在元数据文件中留下了“image”字段为空?想象一下你想在你的集合中创建1000个NFT,并且你必须手动遍历每个项目并手动插入图片链接。 +这真的很不方便,所以让我们编写一个函数来自动完成这个操作! + +```ts +export async function updateMetadataFiles(metadataFolderPath: string, imagesIpfsHash: string): Promise { + const files = readdirSync(metadataFolderPath); + + files.forEach(async (filename, index) => { + const filePath = path.join(metadataFolderPath, filename) + const file = await readFile(filePath); + + const metadata = JSON.parse(file.toString()); + metadata.image = + index != files.length - 1 + ? `ipfs://${imagesIpfsHash}/${index}.jpg` + : `ipfs://${imagesIpfsHash}/logo.jpg`; + + await writeFile(filePath, JSON.stringify(metadata)); + }); +} +``` +这里我们首先读取指定文件夹中的所有文件: +```ts +const files = readdirSync(metadataFolderPath); +``` + +遍历每个文件并获取其内容 +```ts +const filePath = path.join(metadataFolderPath, filename) +const file = await readFile(filePath); + +const metadata = JSON.parse(file.toString()); +``` + +之后,如果不是文件夹中的最后一个文件,我们将图像字段的值分配为 `ipfs://{IpfsHash}/{index}.jpg`,否则为 `ipfs://{imagesIpfsHash}/logo.jpg` 并实际用新数据重写我们的文件。 + +metadata.ts 的完整代码: +```ts +import pinataSDK from "@pinata/sdk"; +import { readdirSync } from "fs"; +import { writeFile, readFile } from "fs/promises"; +import path from "path"; + +export async function uploadFolderToIPFS(folderPath: string): Promise { + const pinata = new pinataSDK({ + pinataApiKey: process.env.PINATA_API_KEY, + pinataSecretApiKey: process.env.PINATA_API_SECRET, + }); + + const response = await pinata.pinFromFS(folderPath); + return response.IpfsHash; +} + +export async function updateMetadataFiles(metadataFolderPath: string, imagesIpfsHash: string): Promise { + const files = readdirSync(metadataFolderPath); + + files.forEach(async (filename, index) => { + const filePath = path.join(metadataFolderPath, filename) + const file = await readFile(filePath); + + const metadata = JSON.parse(file.toString()); + metadata.image = + index != files.length - 1 + ? `ipfs://${imagesIpfsHash}/${index}.jpg` + : `ipfs://${imagesIpfsHash}/logo.jpg`; + + await writeFile(filePath, JSON.stringify(metadata)); + }); +} +``` + +太好了,让我们在我们的 app.ts 文件中调用这些方法。 +添加我们函数的导入: +```ts +import { updateMetadataFiles, uploadFolderToIPFS } from "./metadata"; +``` + +保存元数据/图片文件夹路径变量并调用我们的函数上传元数据。 +```ts +async function init() { + const metadataFolderPath = "./data/metadata/"; + const imagesFolderPath = "./data/images/"; + + const wallet = await openWallet(process.env.MNEMONIC!.split(" "), true); + + console.log("Started uploading images to IPFS..."); + const imagesIpfsHash = await uploadFolderToIPFS(imagesFolderPath); + console.log( + `Successfully uploaded the pictures to ipfs: https://gateway.pinata.cloud/ipfs/${imagesIpfsHash}` + ); + + console.log("Started uploading metadata files to IPFS..."); + await updateMetadataFiles(metadataFolderPath, imagesIpfsHash); + const metadataIpfsHash = await uploadFolderToIPFS(metadataFolderPath); + console.log( + `Successfully uploaded the metadata to ipfs: https://gateway.pinata.cloud/ipfs/${metadataIpfsHash}` + ); +} +``` + +之后你可以运行 `yarn start` 并查看部署的元数据链接! + +### 编码离线内容 + +我们如何将链接到智能合约中存储的元数据文件?这个问题可以通过[Token Data 标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md)得到完全回答。在某些情况下,仅仅提供所需的标志并以ASCII字符提供链接是不够的,这就是为什么我们考虑使用蛇形格式将我们的链接分成几个部分的选项。 + +首先创建一个函数,将我们的缓冲区转换成块: +```ts +function bufferToChunks(buff: Buffer, chunkSize: number) { + const chunks: Buffer[] = []; + while (buff.byteLength > 0) { + chunks.push(buff.subarray(0, chunkSize)); + buff = buff.subarray(chunkSize); + } + return chunks; +} +``` + +并创建一个函数,将所有块绑定成1个蛇形cell: +```ts +function makeSnakeCell(data: Buffer): Cell { + const chunks = bufferToChunks(data, 127); + + if (chunks.length === 0) { + return beginCell().endCell(); + } + + if (chunks.length === 1) { + return beginCell().storeBuffer(chunks[0]).endCell(); + } + + let curCell = beginCell(); + + for (let i = chunks.length - 1; i >= 0; i--) { + const chunk = chunks[i]; + + curCell.storeBuffer(chunk); + + if (i - 1 >= 0) { + const nextCell = beginCell(); + nextCell.storeRef(curCell); + curCell = nextCell; + } + } + + return curCell.endCell(); +} +``` + +最后,我们需要创建一个函数,使用这些函数将离线内容编码为cell: +```ts +export function encodeOffChainContent(content: string) { + let data = Buffer.from(content); + const offChainPrefix = Buffer.from([0x01]); + data = Buffer.concat([offChainPrefix, data]); + return makeSnakeCell(data); +} +``` + +## 🚢 部署 NFT 集合 +当我们的元数据已经准备好并且已经上传到IPFS时,我们可以开始部署我们的集合了! + +我们将在 `/contracts/NftCollection.ts` 文件中创建一个文件,该文件将存储与我们的集合相关的所有逻辑。我们将从导入开始: +```ts +import { + Address, + Cell, + internal, + beginCell, + contractAddress, + StateInit, + SendMode, +} from "ton-core"; +import { encodeOffChainContent, OpenedWallet } from "../utils"; +``` + +并声明一个类型,它将描述我们集合所需的初始化数据: +```ts +export type collectionData = { + ownerAddress: Address; + royaltyPercent: number; + royaltyAddress: Address; + nextItemIndex: number; + collectionContentUrl: string; + commonContentUrl: string; +} +``` + +名称 | 解释 +---|--- +ownerAddress | 将被设置为我们集合的所有者的地址。只有所有者能够铸造新NFT +royaltyPercent | 每次销售金额的百分比,将转到指定地址 +royaltyAddress | 将从这个NFT集合的销售中接收版税的钱包地址 +nextItemIndex | 下一个NFT项目应该有的索引 +collectionContentUrl | 集合元数据的URL +commonContentUrl | NFT项目元数据的基础URL + +首先编写一个私有方法,用于返回带有我们集合代码的cell: + +```ts +export class NftCollection { + private collectionData: collectionData; + + constructor(collectionData: collectionData) { + this.collectionData = collectionData; + } + + private createCodeCell(): Cell { + const NftCollectionCodeBoc = + "te6cckECFAEAAh8AART/APSkE/S88sgLAQIBYgkCAgEgBAMAJbyC32omh9IGmf6mpqGC3oahgsQCASAIBQIBIAcGAC209H2omh9IGmf6mpqGAovgngCOAD4AsAAvtdr9qJofSBpn+pqahg2IOhph+mH/SAYQAEO4tdMe1E0PpA0z/U1NQwECRfBNDUMdQw0HHIywcBzxbMyYAgLNDwoCASAMCwA9Ra8ARwIfAFd4AYyMsFWM8WUAT6AhPLaxLMzMlx+wCAIBIA4NABs+QB0yMsCEsoHy//J0IAAtAHIyz/4KM8WyXAgyMsBE/QA9ADLAMmAE59EGOASK3wAOhpgYC42Eit8H0gGADpj+mf9qJofSBpn+pqahhBCDSenKgpQF1HFBuvgoDoQQhUZYBWuEAIZGWCqALnixJ9AQpltQnlj+WfgOeLZMAgfYBwGyi544L5cMiS4ADxgRLgAXGBEuAB8YEYGYHgAkExIREAA8jhXU1DAQNEEwyFAFzxYTyz/MzMzJ7VTgXwSED/LwACwyNAH6QDBBRMhQBc8WE8s/zMzMye1UAKY1cAPUMI43gED0lm+lII4pBqQggQD6vpPywY/egQGTIaBTJbvy9AL6ANQwIlRLMPAGI7qTAqQC3gSSbCHis+YwMlBEQxPIUAXPFhPLP8zMzMntVABgNQLTP1MTu/LhklMTugH6ANQwKBA0WfAGjhIBpENDyFAFzxYTyz/MzMzJ7VSSXwXiN0CayQ=="; + return Cell.fromBase64(NftCollectionCodeBoc); + } +} +``` +在这段代码中,我们只是从集合智能合约的base64表示中读取cell。 + +剩下的只有我们集合初始化数据的cell了。 + +基本上,我们只需要以正确的方式存储来自collectionData的数据。首先,我们需要创建一个空cell,并在其中存储集合所有者地址和将要铸造的下一个项目的索引。 + +```ts +private createDataCell(): Cell { + const data = this.collectionData; + const dataCell = beginCell(); + + dataCell.storeAddress(data.ownerAddress); + dataCell.storeUint(data.nextItemIndex, 64); +``` + +在此之后,我们创建一个将存储我们收藏内容的空cell,之后存储对含有我们收藏内容编码的cell的引用。紧接着,在我们的主数据cell中存储对contentCell的引用。 +```ts +const contentCell = beginCell(); + +const collectionContent = encodeOffChainContent(data.collectionContentUrl); + +const commonContent = beginCell(); +commonContent.storeBuffer(Buffer.from(data.commonContentUrl)); + +contentCell.storeRef(collectionContent); +contentCell.storeRef(commonContent.asCell()); +dataCell.storeRef(contentCell); +``` + +之后,我们只是创建NFT项目的代码cell,这些项目将在我们的收藏中被创建,并在dataCell中存储对这个cell的引用。 + +```ts +const NftItemCodeCell = Cell.fromBase64( + "te6cckECDQEAAdAAART/APSkE/S88sgLAQIBYgMCAAmhH5/gBQICzgcEAgEgBgUAHQDyMs/WM8WAc8WzMntVIAA7O1E0NM/+kAg10nCAJp/AfpA1DAQJBAj4DBwWW1tgAgEgCQgAET6RDBwuvLhTYALXDIhxwCSXwPg0NMDAXGwkl8D4PpA+kAx+gAxcdch+gAx+gAw8AIEs44UMGwiNFIyxwXy4ZUB+kDUMBAj8APgBtMf0z+CEF/MPRRSMLqOhzIQN14yQBPgMDQ0NTWCEC/LJqISuuMCXwSED/LwgCwoAcnCCEIt3FzUFyMv/UATPFhAkgEBwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7AAH2UTXHBfLhkfpAIfAB+kDSADH6AIIK+vCAG6EhlFMVoKHeItcLAcMAIJIGoZE24iDC//LhkiGOPoIQBRONkchQCc8WUAvPFnEkSRRURqBwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7ABBHlBAqN1viDACCAo41JvABghDVMnbbEDdEAG1xcIAQyMsFUAfPFlAF+gIVy2oSyx/LPyJus5RYzxcBkTLiAckB+wCTMDI04lUC8ANqhGIu" +); +dataCell.storeRef(NftItemCodeCell); +``` + +版税参数通过royaltyFactor、royaltyBase、royaltyAddress在智能合约中存储。版税百分比可以用公式`(royaltyFactor / royaltyBase) * 100%`计算。因此,如果我们知道royaltyPercent,获取royaltyFactor就不是问题。 + +```ts +const royaltyBase = 1000; +const royaltyFactor = Math.floor(data.royaltyPercent * royaltyBase); +``` + +在我们的计算之后,我们需要在单独的cell中存储版税数据,并在dataCell中提供对这个cell的引用。 + +```ts +const royaltyCell = beginCell(); +royaltyCell.storeUint(royaltyFactor, 16); +royaltyCell.storeUint(royaltyBase, 16); +royaltyCell.storeAddress(data.royaltyAddress); +dataCell.storeRef(royaltyCell); + +return dataCell.endCell(); +} +``` + +现在,让我们实际编写一个getter,它将返回我们集合的StateInit: +```ts +public get stateInit(): StateInit { + const code = this.createCodeCell(); + const data = this.createDataCell(); + + return { code, data }; +} +``` + +还有一个getter,它将计算我们集合的地址(TON中智能合约的地址只是其StateInit的散列) +```ts +public get address(): Address { + return contractAddress(0, this.stateInit); + } +``` + +我们仅剩下编写一个方法,该方法将把智能合约部署到区块链上! + +```ts +public async deploy(wallet: OpenedWallet) { + const seqno = await wallet.contract.getSeqno(); + await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: "0.05", + to: this.address, + init: this.stateInit, + }), + ], + sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, + }); + return seqno; + } +``` +在我们的情况下,部署新智能合约就是从我们的钱包向集合地址(如果我们有StateInit,则可以计算出此地址)发送消息! + +当所有者铸造新的NFT时,集合接受所有者的消息并向创建的NFT智能合约发送新消息(这需要支付费用),所以让我们编写一个方法,该方法将根据铸造的nfts数量来补充集合的余额: +```ts +public async topUpBalance( + wallet: OpenedWallet, + nftAmount: number + ): Promise { + const feeAmount = 0.026 // 我们案例中1笔交易的大约费用值 + const seqno = await wallet.contract.getSeqno(); + const amount = nftAmount * feeAmount; + + await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: amount.toString(), + to: this.address.toString({ bounceable: false }), + body: new Cell(), + }), + ], + sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, + }); + + return seqno; + } +``` + +完美,现在让我们在`app.ts`中添加几行,部署新的收藏: +```ts +console.log("Start deploy of nft collection..."); +const collectionData = { + ownerAddress: wallet.contract.address, + royaltyPercent: 0.05, // 0.05 = 5% + royaltyAddress: wallet.contract.address, + nextItemIndex: 0, + collectionContentUrl: `ipfs://${metadataIpfsHash}/collection.json`, + commonContentUrl: `ipfs://${metadataIpfsHash}/`, +}; +const collection = new NftCollection(collectionData); +let seqno = await collection.deploy(wallet); +console.log(`Collection deployed: ${collection.address}`); +await waitSeqno(seqno, wallet); +``` + +## 🚢 部署 NFT 项目 +当我们的收藏准备好后,我们可以开始铸造我们的NFT!我们将存储代码在`src/contracts/NftItem.ts` + +意外地,但现在我们需要回到`NftCollection.ts`。并在文件顶部的`collectionData`附近添加此类型。 + +```ts +export type mintParams = { + queryId: number | null, + itemOwnerAddress: Address, + itemIndex: number, + amount: bigint, + commonContentUrl: string +} +``` +名称 | 说明 +---|--- +itemOwnerAddress | 将被设置为项目所有者的地址 +itemIndex | NFT项目的索引 +amount | 将随部署发送到NFT的TON金额 +commonContentUrl | 项目URL的完整链接可以显示为集合的"commonContentUrl" + 此commonContentUrl + + +并在NftCollection类中创建一个方法,该方法将构建部署我们NFT项目的主体。首先存储一个位,该位将指示给集合智能合约我们想要创建新的NFT。之后只存储此NFT项目的queryId和索引。 + +```ts +public createMintBody(params: mintParams): Cell { + const body = beginCell(); + body.storeUint(1, 32); + body.storeUint(params.queryId || 0, 64); + body.storeUint(params.itemIndex, 64); + body.storeCoins(params.amount); + } +``` + +随后创建一个空cell并在其中存储这个NFT的所有者地址: +```ts + const nftItemContent = beginCell(); + nftItemContent.storeAddress(params.itemOwnerAddress); +``` + +并在这个cell中存储对此项目元数据的引用: +```ts +const uriContent = beginCell(); +uriContent.storeBuffer(Buffer.from(params.commonContentUrl)); +nftItemContent.storeRef(uriContent.endCell()); +``` + +在我们的主体cell中存储对带有项目内容的cell的引用: +```ts +body.storeRef(nftItemContent.endCell()); +return body.endCell(); +``` + +很好!现在我们可以回到`NftItem.ts`。我们要做的全部就是向我们的集合合约发送带有我们NFT主体的消息。 + +```ts +import { internal, SendMode } from "ton-core"; +import { OpenedWallet } from "utils"; +import { NftCollection, mintParams } from "./NftCollection"; + +export class NftItem { + private collection: NftCollection; + + constructor(collection: NftCollection) { + this.collection = collection; + } + + public async deploy( + wallet: OpenedWallet, + params: mintParams + ): Promise { + const seqno = await wallet.contract.getSeqno(); + await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: "0.05", + to: this.collection.address, + body: this.collection.createMintBody(params), + }), + ], + sendMode: SendMode.IGNORE_ERRORS + SendMode.PAY_GAS_SEPARATELY, + }); + return seqno; + } +} +``` + +最后,我们将编写简短方法,该方法将通过其索引获取NFT的地址。 + +从创建客户端变量开始,它将帮助我们调用集合的get方法。 +```ts +static async getAddressByIndex( + collectionAddress: Address, + itemIndex: number +): Promise
{ + const client = new TonClient({ + endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", + apiKey: process.env.TONCENTER_API_KEY, + }); +} +``` + +然后我们将调用集合的get方法,该方法将返回此集合中具有该索引的NFT的地址 +```ts +const response = await client.runMethod( + collectionAddress, + "get_nft_address_by_index", + [{ type: "int", value: BigInt(itemIndex) }] +); +``` + +... 并解析这个地址! +```ts +return response.stack.readAddress(); +``` + + +现在,让我们在`app.ts`中添加一些代码,以自动化每个NFT的铸造过程。首先读取包含我们元数据的文件夹中的所有文件: +```ts +const files = await readdir(metadataFolderPath); +files.pop(); +let index = 0; +``` + +其次,为我们的收藏充值: +```ts +seqno = await collection.topUpBalance(wallet, files.length); +await waitSeqno(seqno, wallet); +console.log(`Balance topped-up`); +``` + +最终,遍历每个带有元数据的文件,创建`NftItem`实例并调用部署方法。之后我们需要稍等一会,直到seqno增加: +```ts +for (const file of files) { + console.log(`Start deploy of ${index + 1} NFT`); + const mintParams = { + queryId: 0, + itemOwnerAddress: wallet.contract.address, + itemIndex: index, + amount: toNano("0.05"), + commonContentUrl: file, + }; + + const nftItem = new NftItem(collection); + seqno = await nftItem.deploy(wallet, mintParams); + console.log(`Successfully deployed ${index + 1} NFT`); + await waitSeqno(seqno, wallet); + index++; + } +``` + +## 🏷 将 NFT 出售 + +为了将nft出售,我们需要两个智能合约。 + +- 市场,仅负责创建新销售的逻辑 +- 销售合约,负责购买/取消销售的逻辑 + +### 部署市场 +在`/contracts/NftMarketplace.ts`中创建新文件。像往常一样创建基本类,该类将接受这个市场所有者的地址,并使用这个智能合约的代码(我们将使用[NFT-Marketplace智能合约的基础版本](https://github.com/ton-blockchain/token-contract/blob/main/nft/nft-marketplace.fc))及初始数据创建cell。 + +```ts +import { + Address, + beginCell, + Cell, + contractAddress, + internal, + SendMode, + StateInit, +} from "ton-core"; +import { OpenedWallet } from "utils"; + +export class NftMarketplace { + public ownerAddress: Address; + + constructor(ownerAddress: Address) { + this.ownerAddress = ownerAddress; + } + + + public get stateInit(): StateInit { + const code = this.createCodeCell(); + const data = this.createDataCell(); + + return { code, data }; + } + + private createDataCell(): Cell { + const dataCell = beginCell(); + + dataCell.storeAddress(this.ownerAddress); + + return dataCell.endCell(); + } + + private createCodeCell(): Cell { + const NftMarketplaceCodeBoc = "te6cckEBBAEAbQABFP8A9KQT9LzyyAsBAgEgAgMAqtIyIccAkVvg0NMDAXGwkVvg+kDtRND6QDASxwXy4ZEB0x8BwAGOK/oAMAHU1DAh+QBwyMoHy//J0Hd0gBjIywXLAljPFlAE+gITy2vMzMlx+wCRW+IABPIwjvfM5w=="; + return Cell.fromBase64(NftMarketplaceCodeBoc) + } +} +``` + +然后,让我们创建方法,用于基于StateInit计算我们智能合约的地址: +```ts +public get address(): Address { + return contractAddress(0, this.stateInit); + } +``` + +之后我们需要创建方法,实际上部署我们的市场: + +```ts +public async deploy(wallet: OpenedWallet): Promise { + const seqno = await wallet.contract.getSeqno(); + await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: "0.5", + to: this.address, + init: this.stateInit, + }), + ], + sendMode: SendMode.IGNORE_ERRORS + SendMode.PAY_GAS_SEPARATELY, + }); + return seqno; + } +``` + +如您所见,这段代码与其他智能合约的部署(nft-item智能合约,新集合的部署)并无不同。唯一的区别是您可以看到我们最初不是用0.05 TON而是用0.5 TON为我们的市场充值。这是什么原因呢?当部署新的智能销售合约时,市场接受请求,处理它,并向新合约发送消息(是的,情况类似于NFT集合)。这就是为什么我们需要额外的TON来支付费用。 + +最终,让我们在`app.ts`文件中添加几行代码,部署我们的市场: +```ts +console.log("Start deploy of new marketplace "); +const marketplace = new NftMarketplace(wallet.contract.address); +seqno = await marketplace.deploy(wallet); +await waitSeqno(seqno, wallet); +console.log("Successfully deployed new marketplace"); +``` + +### 部署销售合约 + +太好了!现在我们已经可以部署我们NFT销售的智能合约了。它将如何工作?我们需要部署新合约,之后将我们的nft“转让”给销售合约(换句话说,我们只需改变我们NFT的所有者为销售合约中的数据项)。在本教程中,我们将使用[nft-fixprice-sale-v2](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v2.fc)销售智能合约。 + +首先让我们声明新类型,该类型将描述我们销售智能合约的数据: +```ts +import { + Address, + beginCell, + Cell, + contractAddress, + internal, + SendMode, + StateInit, + storeStateInit, + toNano, +} from "ton-core"; +import { OpenedWallet } from "utils"; + +export type GetGemsSaleData = { + isComplete: boolean; + createdAt: number; + marketplaceAddress: Address; + nftAddress: Address; + nftOwnerAddress: Address | null; + fullPrice: bigint; + marketplaceFeeAddress: Address; + marketplaceFee: bigint; + royaltyAddress: Address; + royaltyAmount: bigint; +}; +``` + +现在让我们创建类,并创建一个基本方法,用于为我们的智能合约创建初始化数据cell。 + +我们将从创建一个包含费用信息的cell开始。我们需要存储将接收市场费用的地址,发送给市场的TON作为费用的数量。存储将从销售中获得版税的地址和版税金额。 +```ts +export class NftSale { + private data: GetGemsSaleData; + + constructor(data: GetGemsSaleData) { + this.data = data; + } + + private createDataCell(): Cell { + const saleData = this.data; + + const feesCell = beginCell(); + + feesCell.storeAddress(saleData.marketplaceFeeAddress); + feesCell.storeCoins(saleData.marketplaceFee); + feesCell.storeAddress(saleData.royaltyAddress); + feesCell.storeCoins(saleData.royaltyAmount); + } +} +``` + +紧随其后,我们可以创建空cell,并按正确的顺序在其中存储来自saleData的信息,紧接着存储对费用信息的引用: +```ts +const dataCell = beginCell(); + +dataCell.storeUint(saleData.isComplete ? 1 : 0, 1); +dataCell.storeUint(saleData.createdAt, 32); +dataCell.storeAddress(saleData.marketplaceAddress); +dataCell.storeAddress(saleData.nftAddress); +dataCell.storeAddress(saleData.nftOwnerAddress); +dataCell.storeCoins(saleData.fullPrice); +dataCell.storeRef(feesCell.endCell()); + +return dataCell.endCell(); +``` + +像往常一样添加方法,获取stateInit,初始化代码cell和我们智能合约的地址。 +```ts +public get address(): Address { + return contractAddress(0, this.stateInit); +} + +public get stateInit(): StateInit { + const code = this.createCodeCell(); + const data = this.createDataCell(); + + return { code, data }; +} + +private createCodeCell(): Cell { + const NftFixPriceSaleV2CodeBoc = + "te6cckECDAEAAikAART/APSkE/S88sgLAQIBIAMCAATyMAIBSAUEAFGgOFnaiaGmAaY/9IH0gfSB9AGoYaH0gfQB9IH0AGEEIIySsKAVgAKrAQICzQgGAfdmCEDuaygBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkHCAEMjLBVADzxYB+gLLaslx+wAlwgAl10nCArCOF1BFcIAQyMsFUAPPFgH6AstqyXH7ABAjkjQ04lpwgBDIywVQA88WAfoCy2rJcfsAcCCCEF/MPRSBwCCIYAYyMsFKs8WIfoCy2rLHxPLPyPPFlADzxbKACH6AsoAyYMG+wBxVVAGyMsAFcsfUAPPFgHPFgHPFgH6AszJ7VQC99AOhpgYC42EkvgnB9IBh2omhpgGmP/SB9IH0gfQBqGBNgAPloyhFrpOEBWccgGRwcKaDjgskvhHAoomOC+XD6AmmPwQgCicbIiV15cPrpn5j9IBggKwNkZYAK5Y+oAeeLAOeLAOeLAP0BZmT2qnAbE+OAcYED6Y/pn5gQwLCQFKwAGSXwvgIcACnzEQSRA4R2AQJRAkECPwBeA6wAPjAl8JhA/y8AoAyoIQO5rKABi+8uHJU0bHBVFSxwUVsfLhynAgghBfzD0UIYAQyMsFKM8WIfoCy2rLHxnLPyfPFifPFhjKACf6AhfKAMmAQPsAcQZQREUVBsjLABXLH1ADzxYBzxYBzxYB+gLMye1UABY3EDhHZRRDMHDwBTThaBI="; + + return Cell.fromBase64(NftFixPriceSaleV2CodeBoc); +} +``` + +只剩下创建我们将发送到我们市场的消息以部署销售合约,并实际发送此消息 + +首先,我们将创建一个cell,用于存储我们新销售合约的StateInit +```ts +public async deploy(wallet: OpenedWallet): Promise { + const stateInit = beginCell() + .store(storeStateInit(this.stateInit)) + .endCell(); +} +``` + +创建一个带有消息主体的cell。首先我们需要设置操作码为1(以指示市场,我们想要部署新的销售智能合约)。之后我们需要存储将发送到我们新销售智能合约的币值。最后我们需要存储对新智能合约的stateInit和将发送到这个新智能合约的主体的2个引用。 +```ts +const payload = beginCell(); +payload.storeUint(1, 32); +payload.storeCoins(toNano("0.05")); +payload.storeRef(stateInit); +payload.storeRef(new Cell()); +``` + +最后,让我们发送我们的消息: +```ts +const seqno = await wallet.contract.getSeqno(); +await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: "0.05", + to: this.data.marketplaceAddress, + body: payload.endCell(), + }), + ], + sendMode: SendMode.IGNORE_ERRORS + SendMode.PAY_GAS_SEPARATELY, +}); +return seqno; +``` + +完美,在销售智能合约部署完成后,剩下的就是将我们NFT项目的所有者更改为此销售的地址。 + +### 转移项目 +转移一个项目是什么意思?只需从所有者的钱包向智能合约发送消息,告知谁是该项目的新所有者即可。 + +转到`NftItem.ts`,并在NftItem类中创建一个新的静态方法,用于创建此类消息的主体: + +只需创建一个空cell并填充数据。 +```ts +static createTransferBody(params: { + newOwner: Address; + responseTo?: Address; + forwardAmount?: bigint; + }): Cell { + const msgBody = beginCell(); + msgBody.storeUint(0x5fcc3d14, 32); // 操作码 + msgBody.storeUint(0, 64); // 查询id + msgBody.storeAddress(params.newOwner); + + } +``` + +除了操作码、查询id和新所有者的地址外,我们还必须存储要发送成功转移确认响应的地址和输入消息剩余的币值。将要发送给新所有者的TON数量以及他是否会收到文本payload。 + +```ts +msgBody.storeAddress(params.responseTo || null); +msgBody.storeBit(false); // 没有自定义payload +msgBody.storeCoins(params.forwardAmount || 0); +msgBody.storeBit(0); // 没有forward_payload + +return msgBody.endCell(); +``` + +并创建一个转移NFT的函数。 + +```ts +static async transfer( + wallet: OpenedWallet, + nftAddress: Address, + newOwner: Address + ): Promise { + const seqno = await wallet.contract.getSeqno(); + + await wallet.contract.sendTransfer({ + seqno, + secretKey: wallet.keyPair.secretKey, + messages: [ + internal({ + value: "0.05", + to: nftAddress, + body: this.createTransferBody({ + newOwner, + responseTo: wallet.contract.address, + forwardAmount: toNano("0.02"), + }), + }), + ], + sendMode: SendMode.IGNORE_ERRORS + SendMode.PAY_GAS_SEPARATELY, + }); + return seqno; + } +``` + +很好,现在我们已经非常接近结束了。回到`app.ts`,让我们获取我们想要出售的nft的地址: +```ts +const nftToSaleAddress = await NftItem.getAddressByIndex(collection.address, 0); +``` + +创建一个将存储我们销售信息的变量: +```ts +const saleData: GetGemsSaleData = { + isComplete: false, + createdAt: Math.ceil(Date.now() / 1000), + marketplaceAddress: marketplace.address, + nftAddress: nftToSaleAddress, + nftOwnerAddress: null, + fullPrice: toNano("10"), + marketplaceFeeAddress: wallet.contract.address, + marketplaceFee: toNano("1"), + royaltyAddress: wallet.contract.address, + royaltyAmount: toNano("0.5"), +}; +``` +请注意,我们将nftOwnerAddress设置为null,因为如果这样做,我们的销售合约将只接受我们部署时的币值。 + +部署我们的销售: +```ts +const nftSaleContract = new NftSale(saleData); +seqno = await nftSaleContract.deploy(wallet); +await waitSeqno(seqno, wallet); +``` + +... 并进行转移! +```ts +await NftItem.transfer(wallet, nftToSaleAddress, nftSaleContract.address); +``` + +现在我们可以启动我们的项目并享受这个过程! +``` +yarn start +``` +访问 https://testnet.getgems.io/YOUR_COLLECTION_ADDRESS_HERE 并看看这完美的鸭子! + +## 结语 + +今天您学到了许多关于TON的新东西,甚至在测试网中创建了自己的精美NFT收藏!如果您仍有任何疑问或发现错误 - 随时写信给作者 - [@coalus](https:/t.me/coalus) + +## 参考资料 + +- [GetGems NFT合约](https:/github.com/getgems-io/nft-contracts) +- [NFT标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md) + +## 关于作者 +- Coalus:[Telegram](https:/t.me/coalus) 和 [Github](https:/github.com/coalus) \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-2.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-2.md new file mode 100644 index 0000000000..ee6df9cdd5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-2.md @@ -0,0 +1,540 @@ +--- +description: 在本文中,我们将创建一个简单的Telegram机器人,用于接收TON支付。 +--- + +# 带有自己余额的机器人 + +在本文中,我们将创建一个简单的Telegram机器人,用于接收TON支付。 + +## 🦄 外观 + +机器人将如下所示: + +![image](/img/tutorials/bot1.png) + +### 源代码 + +源代码可在GitHub上获得: + +- https://github.com/Gusarich/ton-bot-example + +## 📖 你将学到什么 + +你将学会: + +- 使用Aiogram在Python3中创建一个Telegram机器人 +- 使用SQLITE数据库 +- 使用公共TON API + +## ✍️ 开始之前你需要 + +如果还没有安装[Python](https://www.python.org/),请先安装。 + +还需要以下PyPi库: + +- aiogram +- requests + +你可以在终端中用一条命令安装它们。 + +```bash +pip install aiogram==2.21 requests +``` + +## 🚀 开始吧! + +为我们的机器人创建一个目录,其中包含四个文件: + +- `bot.py`—运行Telegram机器人的程序 +- `config.py`—配置文件 +- `db.py`—与sqlite3数据库交互的模块 +- `ton.py`—处理TON支付的模块 + +目录应该看起来像这样: + +``` +my_bot +├── bot.py +├── config.py +├── db.py +└── ton.py +``` + +现在,让我们开始编写代码吧! + +## 配置 + +我们先从`config.py`开始,因为它是最小的一个。我们只需要在其中设置一些参数。 + +**config.py** + +```python +BOT_TOKEN = 'YOUR BOT TOKEN' +DEPOSIT_ADDRESS = 'YOUR DEPOSIT ADDRESS' +API_KEY = 'YOUR API KEY' +RUN_IN_MAINNET = True # Switch True/False to change mainnet to testnet + +if RUN_IN_MAINNET: + API_BASE_URL = 'https://toncenter.com' +else: + API_BASE_URL = 'https://testnet.toncenter.com' +``` + +这里你需要在前三行填入值: + +- `BOT_TOKEN`是你的Telegram机器人令牌,可以在[创建机器人](https://t.me/BotFather)后获得。 +- `DEPOSIT_ADDRESS`是你的项目钱包地址,将接受所有支付。你可以简单地创建一个新的TON钱包并复制其地址。 +- `API_KEY`是你从TON Center获得的API密钥,可以在[这个机器人](https://t.me/tonapibot)中获得。 + +你还可以选择你的机器人是运行在测试网上还是主网上(第4行)。 + +配置文件就是这些了,我们可以继续向前了! + +## 数据库 + +现在让我们编辑`db.py`文件,该文件将处理我们机器人的数据库。 + +导入sqlite3库。 + +```python +import sqlite3 +``` + +初始化数据库连接和游标(你可以选择任何文件名,而不仅限于`db.sqlite`)。 + +```python +con = sqlite3.connect('db.sqlite') +cur = con.cursor() +``` + +为了存储关于用户的信息(在我们的案例中是他们的余额),创建一个名为"Users"的表,包含用户ID和余额行。 + +```python +cur.execute('''CREATE TABLE IF NOT EXISTS Users ( + uid INTEGER, + balance INTEGER + )''') +con.commit() +``` + +现在我们需要声明一些函数来处理数据库。 + +`add_user`函数将用于将新用户插入数据库。 + +```python +def add_user(uid): + # new user always has balance = 0 + cur.execute(f'INSERT INTO Users VALUES ({uid}, 0)') + con.commit() +``` + +`check_user`函数将用于检查用户是否存在于数据库中。 + +```python +def check_user(uid): + cur.execute(f'SELECT * FROM Users WHERE uid = {uid}') + user = cur.fetchone() + if user: + return True + return False +``` + +`add_balance`函数将用于增加用户的余额。 + +```python +def add_balance(uid, amount): + cur.execute(f'UPDATE Users SET balance = balance + {amount} WHERE uid = {uid}') + con.commit() +``` + +`get_balance`函数将用于检索用户的余额。 + +```python +def get_balance(uid): + cur.execute(f'SELECT balance FROM Users WHERE uid = {uid}') + balance = cur.fetchone()[0] + return balance +``` + +`db.py`文件的内容就这些了! + +现在,我们可以在机器人的其他组件中使用这四个函数来处理数据库。 + +## TON Center API + +在`ton.py`文件中,我们将声明一个函数,该函数将处理所有新的存款,增加用户余额,并通知用户。 + +### getTransactions 方法 + +我们将使用TON Center API。他们的文档在这里: +https://toncenter.com/api/v2/ + +我们需要[getTransactions](https://toncenter.com/api/v2/#/accounts/get_transactions_getTransactions_get)方法来获取某个账户最新交易的信息。 + +让我们看看这个方法作为输入参数需要什么以及它返回了什么。 + +只有一个必填的输入字段`address`,但我们还需要`limit`字段来指定我们想要返回多少个交易。 + +现在让我们尝试在[TON Center 网站](https://toncenter.com/api/v2/#/accounts/get_transactions_getTransactions_get)上运行这个方法,使用任何一个已存在的钱包地址,以了解我们应该从输出中得到什么。 + +```json +{ + "ok": true, + "result": [ + { + ... + }, + { + ... + } + ] +} +``` + +好的,所以当一切正常时,`ok`字段被设置为`true`,并且我们有一个数组`result`,列出了`limit`最近的交易。现在让我们看看单个交易: + +```json +{ + "@type": "raw.transaction", + "utime": 1666648337, + "data": "...", + "transaction_id": { + "@type": "internal.transactionId", + "lt": "32294193000003", + "hash": "ez3LKZq4KCNNLRU/G4YbUweM74D9xg/tWK0NyfuNcxA=" + }, + "fee": "105608", + "storage_fee": "5608", + "other_fee": "100000", + "in_msg": { + "@type": "raw.message", + "source": "EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL", + "destination": "EQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doc2lN", + "value": "100000000", + "fwd_fee": "666672", + "ihr_fee": "0", + "created_lt": "32294193000002", + "body_hash": "tDJM2A4YFee5edKRfQWLML5XIJtb5FLq0jFvDXpv0xI=", + "msg_data": { + "@type": "msg.dataText", + "text": "SGVsbG8sIHdvcmxkIQ==" + }, + "message": "Hello, world!" + }, + "out_msgs": [] +} +``` + +我们可以看到可以帮助我们识别确切交易的信息存储在`transaction_id`字段中。我们需要从中获取`lt`字段,以了解哪个交易先发生,哪个后发生。 + +关于coin转移的信息在`in_msg`字段中。我们需要从中获取`value`和`message`。 + +现在我们准备好创建支付处理程序了。 + +### 从代码中发送 API 请求 + +让我们从导入所需的库和之前的两个文件`config.py`和`db.py`开始。 + +```python +import requests +import asyncio + +# Aiogram +from aiogram import Bot +from aiogram.types import ParseMode + +# We also need config and database here +import config +import db +``` + +让我们考虑如何可以实现支付处理。 + +我们可以每隔几秒调用API,并检查我们的钱包地址是否有任何新交易。 + +为此,我们需要知道最后处理的交易是什么。最简单的方法是只将该交易的信息保存在某个文件中,并在我们处理新交易时更新它。 + +我们需要将哪些交易信息存储在文件中?实际上,我们只需要存储`lt`值——逻辑时间。有了这个值,我们就能知道需要处理哪些交易。 + +所以我们需要定义一个新的异步函数;让我们称之为`start`。为什么这个函数需要是异步的?因为Telegram机器人的Aiogram库也是异步的,稍后使用异步函数会更容易。 + +这是我们的`start`函数应该看起来的样子: + +```python +async def start(): + try: + # Try to load last_lt from file + with open('last_lt.txt', 'r') as f: + last_lt = int(f.read()) + except FileNotFoundError: + # If file not found, set last_lt to 0 + last_lt = 0 + + # We need the Bot instance here to send deposit notifications to users + bot = Bot(token=config.BOT_TOKEN) + + while True: + # Here we will call API every few seconds and fetch new transactions. + ... +``` + +现在让我们编写while循环的主体。我们需要每隔几秒在这里调用TON Center API。 + +```python +while True: + # 2 Seconds delay between checks + await asyncio.sleep(2) + + # API call to TON Center that returns last 100 transactions of our wallet + resp = requests.get(f'{config.API_BASE_URL}/api/v2/getTransactions?' + f'address={config.DEPOSIT_ADDRESS}&limit=100&' + f'archival=true&api_key={config.API_KEY}').json() + + # If call was not successful, try again + if not resp['ok']: + continue + + ... +``` + +在使用`requests.get`调用后,我们有一个变量`resp`包含了API的响应。`resp`是一个对象,`resp['result']`是一个列表,包含了我们地址的最后100笔交易。 + +现在我们只需遍历这些交易,找到新的交易即可。 + +```python +while True: + ... + + # Iterating over transactions + for tx in resp['result']: + # LT is Logical Time and Hash is hash of our transaction + lt, hash = int(tx['transaction_id']['lt']), tx['transaction_id']['hash'] + + # If this transaction's logical time is lower than our last_lt, + # we already processed it, so skip it + + if lt <= last_lt: + continue + + # at this moment, `tx` is some new transaction that we haven't processed yet + ... +``` + +我们如何处理一笔新的交易呢?我们需要: + +- 理解哪个用户发送了它 +- 增加该用户的余额 +- 通知用户他们的存款 + +下面是将完成所有这些操作的代码: + +```python +while True: + ... + + for tx in resp['result']: + ... + # at this moment, `tx` is some new transaction that we haven't processed yet + + value = int(tx['in_msg']['value']) + if value > 0: + uid = tx['in_msg']['message'] + + if not uid.isdigit(): + continue + + uid = int(uid) + + if not db.check_user(uid): + continue + + db.add_balance(uid, value) + + await bot.send_message(uid, 'Deposit confirmed!\n' + f'*+{value / 1e9:.2f} TON*', + parse_mode=ParseMode.MARKDOWN) +``` + +让我们看看它做了什么。 + +所有有关coin转移的信息都在`tx['in_msg']`中。我们只需要其中的'value'和'message'字段。 + +首先,我们检查值是否大于零,如果是,才继续。 + +然后我们期望转移有一个评论(`tx['in_msg']['message']`),以有我们机器人的用户ID,所以我们验证它是否是一个有效的数字,以及该UID是否存在于我们的数据库中。 + +经过这些简单的检查,我们有了一个变量`value`,它是存款金额,和一个变量`uid`,它是进行此次存款的用户ID。所以我们可以直接给他们的账户增加资金,并发送通知消息。 + +同时注意值默认是以nanotons为单位的,所以我们需要将其除以10亿。我们在通知消息中这样做: +`{value / 1e9:.2f}` +这里我们将值除以`1e9`(10亿),并保留小数点后两位数字,以便以用户友好的格式显示给用户。 + +太棒了!程序现在可以处理新交易并通知用户存款情况。但我们不应忘记之前我们使用过的`lt`,我们必须更新最后的`lt`,因为处理了一个更新的交易。 + +这很简单: + +```python +while True: + ... + for tx in resp['result']: + ... + # we have processed this tx + + # lt variable here contains LT of the last processed transaction + last_lt = lt + with open('last_lt.txt', 'w') as f: + f.write(str(last_lt)) +``` + +`ton.py`文件的内容就这些了! +我们的机器人现在已完成3/4;我们只需要在机器人自身创建一个包含几个按钮的用户界面。 + +## Telegram 机器人 + +### 初始化 + +打开`bot.py`文件并导入我们所需的所有模块。 + +```python +# Logging module +import logging + +# Aiogram imports +from aiogram import Bot, Dispatcher, types +from aiogram.dispatcher.filters import Text +from aiogram.types import ParseMode, ReplyKeyboardMarkup, KeyboardButton, \ + InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.utils import executor + +# Local modules to work with the Database and TON Network +import config +import ton +import db +``` + +让我们设置日志记录,以便我们以后可以看到发生的事情以便调试。 + +```python +logging.basicConfig(level=logging.INFO) +``` + +现在我们需要使用Aiogram初始化机器人对象及其调度器。 + +```python +bot = Bot(token=config.BOT_TOKEN) +dp = Dispatcher(bot) +``` + +这里我们使用了教程开始时我们创建的配置中的`BOT_TOKEN`。 + +我们初始化了机器人,但它仍然是空的。我们必须添加一些与用户交互的功能。 + +### 消息处理器 + +#### /start 命令 + +我们首先处理`/start`和`/help`命令。当用户第一次启动机器人、重新启动它或使用`/help`命令时,将调用此函数。 + +```python +@dp.message_handler(commands=['start', 'help']) +async def welcome_handler(message: types.Message): + uid = message.from_user.id # Not neccessary, just to make code shorter + + # If user doesn't exist in database, insert it + if not db.check_user(uid): + db.add_user(uid) + + # Keyboard with two main buttons: Deposit and Balance + keyboard = ReplyKeyboardMarkup(resize_keyboard=True) + keyboard.row(KeyboardButton('Deposit')) + keyboard.row(KeyboardButton('Balance')) + + # Send welcome text and include the keyboard + await message.answer('Hi!\nI am example bot ' + 'made for [this article](/develop/dapps/payment-processing/accept-payments-in-a-telegram-bot-2).\n' + 'My goal is to show how simple it is to receive ' + 'payments in Toncoin with Python.\n\n' + 'Use keyboard to test my functionality.', + reply_markup=keyboard, + parse_mode=ParseMode.MARKDOWN) +``` + +欢迎消息可以是你想要的任何内容。键盘按钮可以是任何文本,但在这个示例中,它们被标记为我们的机器人最清晰的方式:`Deposit`和`Balance`。 + +#### 余额(Balance)按钮 + +现在用户可以启动机器人并看到带有两个按钮的键盘。但在调用其中一个后,用户不会收到任何响应,因为我们还没有为它们创建任何功能。 + +所以让我们添加一个请求余额的功能。 + +```python +@dp.message_handler(commands='balance') +@dp.message_handler(Text(equals='balance', ignore_case=True)) +async def balance_handler(message: types.Message): + uid = message.from_user.id + + # Get user balance from database + # Also don't forget that 1 TON = 1e9 (billion) Nanoton + user_balance = db.get_balance(uid) / 1e9 + + # Format balance and send to user + await message.answer(f'Your balance: *{user_balance:.2f} TON*', + parse_mode=ParseMode.MARKDOWN) +``` + +这非常简单。我们只需从数据库获取余额并向用户发送消息。 + +#### 存款(Deposit)按钮 + +那第二个`Deposit`按钮呢?这是它的函数: + +```python +@dp.message_handler(commands='deposit') +@dp.message_handler(Text(equals='deposit', ignore_case=True)) +async def deposit_handler(message: types.Message): + uid = message.from_user.id + + # Keyboard with deposit URL + keyboard = InlineKeyboardMarkup() + button = InlineKeyboardButton('Deposit', + url=f'ton://transfer/{config.DEPOSIT_ADDRESS}&text={uid}') + keyboard.add(button) + + # Send text that explains how to make a deposit into bot to user + await message.answer('It is very easy to top up your balance here.\n' + 'Simply send any amount of TON to this address:\n\n' + f'`{config.DEPOSIT_ADDRESS}`\n\n' + f'And include the following comment: `{uid}`\n\n' + 'You can also deposit by clicking the button below.', + reply_markup=keyboard, + parse_mode=ParseMode.MARKDOWN) +``` + +这里我们要做的也很容易理解。 + +还记得在`ton.py`文件中,我们是如何通过评论确定哪个用户进行了存款吗?现在在机器人中,我们需要请求用户发送包含他们UID的交易。 + +### 启动机器人 + +现在在`bot.py`中我们要做的最后一件事是启动机器人本身,同时也运行`ton.py`中的`start`函数。 + +```python +if __name__ == '__main__': + # Create Aiogram executor for our bot + ex = executor.Executor(dp) + + # Launch the deposit waiter with our executor + ex.loop.create_task(ton.start()) + + # Launch the bot + ex.start_polling() +``` + +此时,我们已经编写了我们机器人所需的所有代码。如果你按照教程正确完成,当你使用`python my-bot/bot.py`命令在终端运行时,它应该会工作。 + +如果你的机器人不能正确工作,请与[这个库](https://github.com/Gusarich/ton-bot-example)的代码进行对比。 + +## 参考资料 + +- 作为[ton-footsteps/8](https://github.com/ton-society/ton-footsteps/issues/8)的一部分 +- 由Gusarich提供([Telegram @Gusarich](https://t.me/Gusarich), [Gusarich on GitHub](https://github.com/Gusarich)) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js.md new file mode 100644 index 0000000000..52a5eeaee3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot-js.md @@ -0,0 +1,481 @@ +--- +description: 在本教程结束时,你将编写一个美观的机器人,能够直接用TON接受你的产品的支付。 +--- + +# 出售饺子的机器人 + +在本文中,我们将创建一个简单的Telegram机器人,用于接收TON支付。 + +## 🦄 外观 + +在教程结束时,你将编写一个美观的机器人,能够直接用TON接受你的产品的支付。 + +机器人将如下所示: + +![bot preview](/img/tutorials/js-bot-preview.jpg) + +## 📖 你将学到什么 + +你将学会如何: + +- 使用grammY在NodeJS中创建一个Telegram机器人 +- 使用公共TON Center API + +> 我们为什么使用grammY? +> 因为grammY是一个现代化、年轻的、高级框架,适用于在JS/TS/Deno上快速舒适地开发telegram机器人,此外,grammY拥有优秀的[文档](https://grammy.dev)和一个能够始终帮助你的活跃社群。 + +## ✍️ 开始之前你需要 + +如果还没有安装[NodeJS](https://nodejs.org/en/download/),请先安装。 + +你还需要以下库: + +- grammy +- ton +- dotenv + +你可以在终端中用一条命令安装它们。 + +```bash npm2yarn +npm install ton dotenv grammy @grammyjs/conversations +``` + +## 🚀 开始吧! + +我们项目的结构将如下所示: + +``` +src + ├── bot + ├── start.js + ├── payment.js + ├── services + ├── ton.js + ├── app.js +.env +``` + +- `bot/start.js` & `bot/payment.js` - 用于telegram机器人的处理程序文件 +- `src/ton.js` - 与TON相关的业务逻辑文件 +- `app.js` - 用于初始化并启动机器人的文件 + +现在让我们开始编写代码吧! + +## 配置 + +我们从`.env`开始。我们只需要在其中设置一些参数。 + +**.env** + +``` +BOT_TOKEN= +TONCENTER_TOKEN= +NETWORK= +OWNER_WALLET= +``` + +这里你需要填写前四行的值: + +- `BOT_TOKEN`是你的Telegram机器人令牌,可以在[创建机器人](https://t.me/BotFather)后获得。 +- `OWNER_WALLET`是你的项目钱包地址,将接受所有支付。你可以简单地创建一个新的TON钱包并复制其地址。 +- `API_KEY`是你从 TON Center 获得的API密钥,分别针对主网和测试网,可以通过[@tonapibot](https://t.me/tonapibot)/[@tontestnetapibot](https://t.me/tontestnetapibot)获得。 +- `NETWORK`是关于你的机器人将运行在哪个网络上 - 测试网或主网 + +配置文件就这些了,我们可以继续前进! + +## TON Center API + +在`src/services/ton.js`文件中,我们将声明一些函数,用于验证交易的存在并生成快速跳转到钱包应用进行支付的链接。 + +### 获取最新的钱包交易 + +我们的任务是从特定钱包中检查我们需要的交易是否存在。 + +我们将这样解决它: + +1. 我们将接收到发往我们钱包的最后一批交易。为什么是我们的?在这种情况下,我们不必担心用户的钱包地址是什么,我们不必确认它是他的钱包,我们也不必将这个钱包存储在任何地方。 +2. 排序并只保留入账交易 +3. 我们将检查所有交易,每次都会校验注释和金额是否与我们拥有的数据相等 +4. 庆祝我们的问题解决🎉 + +#### 获取最新交易 + +如果我们使用TON Center API,那么我们可以参考他们的[文档](https://toncenter.com/api/v2/),找到一个理想解决我们问题的方法 - [getTransactions](https://toncenter.com/api/v2/#/accounts/get_transactions_getTransactions_get) + +我们只需要一个参数就能获取交易 - 接受支付的钱包地址,但我们也会使用limit参数来限制交易发放到100条。 + +让我们尝试调用`EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N`地址的测试请求(顺带一提,这是TON基金会的地址) + +```bash +curl -X 'GET' \ + 'https://toncenter.com/api/v2/getTransactions?address=EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N&limit=100' \ + -H 'accept: application/json' +``` + +很好,现在我们手头有了一份交易列表在["result"]中,现在让我们仔细看看1笔交易 + +```json +{ + "@type": "raw.transaction", + "utime": 1667148685, + "data": "*data here*", + "transaction_id": { + "@type": "internal.transactionId", + "lt": "32450206000003", + "hash": "rBHOq/T3SoqWta8IXL8THxYqTi2tOkBB8+9NK0uKWok=" + }, + "fee": "106508", + "storage_fee": "6508", + "other_fee": "100000", + "in_msg": { + "@type": "raw.message", + "source": "EQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBXwtG", + "destination": "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N", + "value": "1000000", + "fwd_fee": "666672", + "ihr_fee": "0", + "created_lt": "32450206000002", + "body_hash": "Fl+2CzHNgilBE4RKhfysLU8VL8ZxYWciCRDva2E19QQ=", + "msg_data": { + "@type": "msg.dataText", + "text": "aGVsbG8g8J+Riw==" + }, + "message": "hello 👋" + }, + "out_msgs": [] + } +``` + +从这个json文件中,我们可以了解一些对我们有用的信息: + +- 这是一笔入账交易,因为"out_msgs"字段为空 +- 我们还可以获取交易的评论、发送者和交易金额 + +现在我们准备好创建一个交易检查器了 + +### 使用 TON + +让我们先导入所需的TON库 + +```js +import { HttpApi, fromNano, toNano } from "ton"; +``` + +让我们考虑如何检查用户是否发送了我们需要的交易。 + +一切都异常简单。我们只需排序我们钱包的入账交易,然后遍历最后100笔交易,如果找到一笔符合相同注释和金额的交易,那么我们就找到了我们需要的交易! + +首先,让我们初始化http客户端,以方便使用TON + +```js +export async function verifyTransactionExistance(toWallet, amount, comment) { + const endpoint = + process.env.NETWORK === "mainnet" + ? "https://toncenter.com/api/v2/jsonRPC" + : "https://testnet.toncenter.com/api/v2/jsonRPC"; + const httpClient = new HttpApi( + endpoint, + {}, + { apiKey: process.env.TONCENTER_TOKEN } + ); +``` + +这里我们根据配置中选择的网络简单地生成endpoint url。然后我们初始化http客户端。 + +所以,现在我们可以从所有者的钱包中获取最后100笔交易 + +```js +const transactions = await httpClient.getTransactions(toWallet, { + limit: 100, + }); +``` + +并过滤,仅保留入账交易(如果交易的out_msgs为空,我们保留它) + +```js +let incomingTransactions = transactions.filter( + (tx) => Object.keys(tx.out_msgs).length === 0 + ); +``` + +现在我们只需遍历所有交易,只要comment和交易值匹配,我们就返回true。 + +```js + for (let i = 0; i < incomingTransactions.length; i++) { + let tx = incomingTransactions[i]; + // Skip the transaction if there is no comment in it + if (!tx.in_msg.msg_data.text) { + continue; + } + + // Convert transaction value from nano + let txValue = fromNano(tx.in_msg.value); + // Get transaction comment + let txComment = tx.in_msg.message + + if (txComment === comment && txValue === value.toString()) { + return true; + } + } + + return false; +``` + +注意,值默认是以nanotons为单位,所以我们需要将其除以10亿,或者我们可以直接使用TON库中的`fromNano`方法。`verifyTransactionExistance`函数就是这些了! + +现在我们可以创建生成快速跳转到钱包应用进行支付的链接的函数了。 + +```js +export function generatePaymentLink(toWallet, amount, comment, app) { + if (app === "tonhub") { + return `https://tonhub.com/transfer/${toWallet}?amount=${toNano( + amount + )}&text=${comment}`; + } + return `https://app.tonkeeper.com/transfer/${toWallet}?amount=${toNano( + amount + )}&text=${comment}`; +} +``` + +我们所需的只是将交易参数代入URL中。不要忘记将交易值转换为nano。 + +## Telegram 机器人 + +### 初始化 + +打开`app.js`文件并导入我们需要的所有处理程序和模块。 + +```js +import dotenv from "dotenv"; +import { Bot, session } from "grammy"; +import { conversations, createConversation } from "@grammyjs/conversations"; + +import { + startPaymentProcess, + checkTransaction, +} from "./bot/handlers/payment.js"; +import handleStart from "./bot/handlers/start.js"; +``` + +让我们设置dotenv模块,以便舒适地使用我们在.env文件中设置的环境变量。 + +```js +dotenv.config(); +``` + +之后我们创建一个将运行我们项目的函数。为了防止出现任何错误时我们的机器人停止,我们添加了这段代码。 + +```js +async function runApp() { + console.log("Starting app..."); + + // Handler of all errors, in order to prevent the bot from stopping + process.on("uncaughtException", function (exception) { + console.log(exception); + }); +``` + +现在初始化机器人和必要的插件。 + +```js + // Initialize the bot + const bot = new Bot(process.env.BOT_TOKEN); + + // Set the initial data of our session + bot.use(session({ initial: () => ({ amount: 0, comment: "" }) })); + // Install the conversation plugin + bot.use(conversations()); + + bot.use(createConversation(startPaymentProcess)); +``` + +这里我们使用了教程开始时我们创建的配置中的`BOT_TOKEN`。 + +我们初始化了机器人,但它还是空的。我们必须添加一些用于与用户互动的功能。 + +```js + // Register all handelrs + bot.command("start", handleStart); + bot.callbackQuery("buy", async (ctx) => { + await ctx.conversation.enter("startPaymentProcess"); + }); + bot.callbackQuery("check_transaction", checkTransaction); +``` + +对于命令/start,将执行handleStart函数。如果用户点击callback_data等于"buy"的按钮,我们将启动我们刚刚注册的"对话"。当我们点击callback_data等于"check_transaction"的按钮时,将执行checkTransaction函数。 + +我们所剩的就是启动我们的机器人并输出有关成功启动的日志。 + +```js + // Start bot + await bot.init(); + bot.start(); + console.info(`Bot @${bot.botInfo.username} is up and running`); +``` + +### 消息处理 + +#### /start 命令 + +我们从处理`/start`命令开始。当用户首次启动机器人或重新启动它时,将调用此函数。 + +```js +import { InlineKeyboard } from "grammy"; + +export default async function handleStart(ctx) { + const menu = new InlineKeyboard() + .text("Buy dumplings🥟", "buy") + .row() + .url("Article with a detailed explanation of the bot's work", "/develop/dapps/payment-processing/accept-payments-in-a-telegram-bot-js/"); + + await ctx.reply( + `Hello stranger! +Welcome to the best Dumplings Shop in the world and concurrently an example of accepting payments in TON`, + { reply_markup: menu, parse_mode: "HTML" } + ); +} +``` + +这里我们首先从grammy模块导入InlineKeyboard。之后,在处理程序中我们创建了内联键盘,提供购买饺子的选项和文章链接(这里有点递归😁)。.row()代表将下一个按钮转移到新行。 +之后,我们带着创建的键盘发送欢迎消息,文本中(重要的是,我在我的消息中使用HTML标记来装饰它) +欢迎消息可以是任何你想要的内容。 + +#### 支付过程 + +像往常一样,我们将从必要的导入开始我们的文件。 + +```js +import { InlineKeyboard } from "grammy"; + +import { + generatePaymentLink, + verifyTransactionExistance, +} from "../../services/ton.js"; +``` + +之后,我们将创建一个startPaymentProcess处理程序,我们已经在app.js中注册了它以在按下某个按钮时执行。 + +在Telegram中,当你点击内联按钮时,会出现一个旋转的手表,为了移除它,我们响应回调。 + +```js + await ctx.answerCallbackQuery(); +``` + +之后,我们需要向用户发送一张饺子图片,询问他想要购买的饺子数量。并等待他输入这个数字。 + +```js + await ctx.replyWithPhoto( + "https://telegra.ph/file/bad2fd69547432e16356f.jpg", + { + caption: + "Send the number of portions of yummy dumplings you want buy\nP.S. Current price for 1 portion: 3 TON", + } + ); + + // Wait until the user enters the number + const count = await conversation.form.number(); +``` + +现在我们计算订单的总金额并生成一个随机字符串,我们将用该字符串来评论交易,并添加饺子后缀。 + +```js + // Get the total cost: multiply the number of portions by the price of the 1 portion + const amount = count * 3; + // Generate random comment + const comment = Math.random().toString(36).substring(2, 8) + "dumplings"; +``` + +我们将结果数据保存到会话中,以便我们可以在下一个处理程序中获取这些数据。 + +```js + conversation.session.amount = amount; + conversation.session.comment = comment; +``` + +我们生成快速支付的链接并创建一个内联键盘。 + +```js +const tonhubPaymentLink = generatePaymentLink( + process.env.OWNER_WALLET, + amount, + comment, + "tonhub" + ); + const tonkeeperPaymentLink = generatePaymentLink( + process.env.OWNER_WALLET, + amount, + comment, + "tonkeeper" + ); + + const menu = new InlineKeyboard() + .url("Click to pay in TonHub", tonhubPaymentLink) + .row() + .url("Click to pay in Tonkeeper", tonkeeperPaymentLink) + .row() + .text(`I sent ${amount} TON`, "check_transaction"); +``` + +我们发送带有键盘的消息,在其中我们请求用户将交易发送到我们的钱包地址并附上随机生成的评论。 + +```js + await ctx.reply( + ` +Fine, all you have to do is transfer ${amount} TON to the wallet ${process.env.OWNER_WALLET} with the comment ${comment}. + +WARNING: I am currently working on ${process.env.NETWORK} + +P.S. You can conveniently make a transfer by clicking on the appropriate button below and confirm the transaction in the offer`, + { reply_markup: menu, parse_mode: "HTML" } + ); +} +``` + +现在我们所需要做的就是创建一个检查交易是否存在的处理程序。 + +```js +export async function checkTransaction(ctx) { + await ctx.answerCallbackQuery({ + text: "Wait a bit, I need to check the availability of your transaction", + }); + + if ( + await verifyTransactionExistance( + process.env.OWNER_WALLET, + ctx.session.amount, + ctx.session.comment + ) + ) { + const menu = new InlineKeyboard().text("Buy more dumplings🥟", "buy"); + + await ctx.reply("Thank you so much. Enjoy your meal!", { + reply_markup: menu, + }); + + // Reset the session data + ctx.session.amount = 0; + ctx.session.comment = ""; + } else { + await ctx.reply("I didn't receive your transaction, wait a bit"); + } +} +``` + +这里我们所做的就是检查交易是否存在,如果存在,我们就告诉用户这个消息并重置会话中的数据。 + +### 启动机器人 + +要启动,请使用这个命令: + +```bash npm2yarn +npm run app +``` + +如果你的机器人不能正确工作,与[此库](https://github.com/coalus/DumplingShopBot)的代码进行对比。如果无法解决,请随时写信给我。我的Telegram账号见下方。 + +## 参考资料 + +- 作为[ton-footsteps/58](https://github.com/ton-society/ton-footsteps/issues/58)的一部分 +- 作者 Coalus([Telegram @coalus](https://t.me/coalus), [Coalus on GitHub](https://github.com/coalus)) +- [机器人源码](https://github.com/coalus/DumplingShopBot) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot.md new file mode 100644 index 0000000000..69e3e23ed3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/telegram-bot-examples/accept-payments-in-a-telegram-bot.md @@ -0,0 +1,865 @@ +--- +description: 在这篇文章中,我们将引导你完成在 Telegram 机器人中接受付款的过程。 +--- + +# 使用 TON 的商店机器人 + +在这篇文章中,我们将引导你完成在 Telegram 机器人中接受付款的过程。 + +## 📖 你将学到什么 + +在这篇文章中,你将学习如何: + +- 使用 Python + Aiogram 创建一个 Telegram 机器人 +- 使用公开的 TON API(TON Center) +- 使用 SQlite 数据库 + +最后:通过前面步骤的知识,在 Telegram 机器人中接受付款。 + +## 📚 在我们开始之前 + +确保你已经安装了最新版本的 Python,并且已经安装了以下包: + +- aiogram +- requests +- sqlite3 + +## 🚀 我们开始吧! + +我们将按照以下顺序操作: + +1. 使用 SQlite 数据库 +2. 使用公开的 TON API(TON Center) +3. 使用 Python + Aiogram 创建一个 Telegram 机器人 +4. 盈利! + +让我们在项目目录中创建以下四个文件: + +``` +telegram-bot +├── config.json +├── main.py +├── api.py +└── db.py +``` + +## 配置 + +在 `config.json` 中,我们将存储我们的机器人令牌和我们的公开 TON API 密钥。 + +```json +{ + "BOT_TOKEN": "Your bot token", + "MAINNET_API_TOKEN": "Your mainnet api token", + "TESTNET_API_TOKEN": "Your testnet api token", + "MAINNET_WALLET": "Your mainnet wallet", + "TESTNET_WALLET": "Your testnet wallet", + "WORK_MODE": "testnet" +} +``` + +在 `config.json` 中,我们决定我们将使用哪个网络:`testnet` 或 `mainnet`。 + +## 数据库 + +### 创建数据库 + +这个示例使用本地 Sqlite 数据库。 + +创建 `db.py`。 + +开始使用数据库,我们需要导入 sqlite3 模块和一些用于处理时间的模块。 + +```python +import sqlite3 +import datetime +import pytz +``` + +- `sqlite3`—用于操作 sqlite 数据库的模块 +- `datetime`—用于处理时间的模块 +- `pytz`—用于处理时区的模块 + +接下来,我们需要创建一个数据库的连接和一个用于操作它的游标: + +```python +locCon = sqlite3.connect('local.db', check_same_thread=False) +cur = locCon.cursor() +``` + +如果数据库不存在,将会自动创建。 + +现在我们可以创建表格了。我们有两个表格。 + +#### 交易: + +```sql +CREATE TABLE transactions ( + source VARCHAR (48) NOT NULL, + hash VARCHAR (50) UNIQUE + NOT NULL, + value INTEGER NOT NULL, + comment VARCHAR (50) +); +``` + +- `source`—付款人的钱包地址 +- `hash`—交易哈希 +- `value`—交易价值 +- `comment`—交易备注 + +#### 用户: + +```sql +CREATE TABLE users ( + id INTEGER UNIQUE + NOT NULL, + username VARCHAR (33), + first_name VARCHAR (300), + wallet VARCHAR (50) DEFAULT none +); +``` + +- `id`—Telegram 用户 ID +- `username`—Telegram 用户名 +- `first_name`—Telegram 用户的名字 +- `wallet`—用户钱包地址 + +在 `users` 表中,我们存储用户 :) 他们的 Telegram ID、@username、 +名字和钱包。第一次成功付款时,钱包将被添加到数据库中。 + +`transactions` 表存储已验证的交易。 +要验证交易,我们需要哈希、来源、值和备注。 + +要创建这些表格,我们需要运行以下函数: + +```python +cur.execute('''CREATE TABLE IF NOT EXISTS transactions ( + source VARCHAR (48) NOT NULL, + hash VARCHAR (50) UNIQUE + NOT NULL, + value INTEGER NOT NULL, + comment VARCHAR (50) +)''') +locCon.commit() + +cur.execute('''CREATE TABLE IF NOT EXISTS users ( + id INTEGER UNIQUE + NOT NULL, + username VARCHAR (33), + first_name VARCHAR (300), + wallet VARCHAR (50) DEFAULT none +)''') +locCon.commit() +``` + +如果这些表格还没有被创建,这段代码将会创建它们。 + +### 使用数据库 + +让我们分析一种情况: +用户进行了一笔交易。我们如何验证它?我们如何确保同一笔交易不被二次确认? + +交易中有一个 body_hash,通过它我们可以轻松地了解数据库中是否存在该交易。 + +我们只添加我们确定的交易到数据库。`check_transaction` 函数检查数据库中是否存在找到的交易。 + +`add_v_transaction` 将交易添加到交易表。 + +```python +def add_v_transaction(source, hash, value, comment): + cur.execute("INSERT INTO transactions (source, hash, value, comment) VALUES (?, ?, ?, ?)", + (source, hash, value, comment)) + locCon.commit() +``` + +```python +def check_transaction(hash): + cur.execute(f"SELECT hash FROM transactions WHERE hash = '{hash}'") + result = cur.fetchone() + if result: + return True + return False +``` + +`check_user` 检查用户是否在数据库中,并且如果不在,则添加他。 + +```python +def check_user(user_id, username, first_name): + cur.execute(f"SELECT id FROM users WHERE id = '{user_id}'") + result = cur.fetchone() + + if not result: + cur.execute("INSERT INTO users (id, username, first_name) VALUES (?, ?, ?)", + (user_id, username, first_name)) + locCon.commit() + return False + return True +``` + +用户可以在表中存储一个钱包。它是在第一次成功购买时添加的。`v_wallet` 函数检查用户是否有关联的钱包。如果有,则返回它。如果没有,则添加。 + +```python +def v_wallet(user_id, wallet): + cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'") + result = cur.fetchone() + if result[0] == "none": + cur.execute( + f"UPDATE users SET wallet = '{wallet}' WHERE id = '{user_id}'") + locCon.commit() + return True + else: + return result[0] +``` + +`get_user_wallet` 简单地返回用户的钱包。 + +```python +def get_user_wallet(user_id): + cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'") + result = cur.fetchone() + return result[0] +``` + +`get_user_payments` 返回用户的支付列表。 +这个函数检查用户是否有钱包。如果有,则返回支付列表。 + +```python +def get_user_payments(user_id): + wallet = get_user_wallet(user_id) + + if wallet == "none": + return "You have no wallet" + else: + cur.execute(f"SELECT * FROM transactions WHERE source = '{wallet}'") + result = cur.fetchall() + tdict = {} + tlist = [] + try: + for transaction in result: + tdict = { + "value": transaction[2], + "comment": transaction[3], + } + tlist.append(tdict) + return tlist + + except: + return False +``` + +## API + +*我们有能力使用一些网络成员提供的第三方 API 与区块链进行交互。通过这些服务,开发者可以跳过运行自己的节点和自定义 API 的步骤。* + +### 需要的请求 + +实际上,我们需要确认用户已经向我们转账了所需金额吗? + +我们只需要查看我们钱包的最新进账转账,并在其中找到一笔来自正确地址、正确金额的交易(可能还有一个独特的备注)。 +为了所有这一切,TON Center 有一个 `getTransactions` 方法。 + +### getTransactions + +默认情况下,如果我们使用它,我们将获得最后 10 条交易。然而,我们也可以表示我们需要更多,但这会略微增加响应时间。而且,很有可能,你不需要那么多。 + +如果您想要更多,那么每笔交易都有 `lt` 和 `hash`。您可以查看例如 30 条交易,如果没在其中找到正确的一笔,那么取最后一笔的 `lt` 和 `hash` 添加到请求中。 + +这样您就可以得到下一个 30 条交易,以此类推。 + +例如,测试网络中有一个钱包 `EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5`,它有一些交易: + +使用[查询](https://testnet.toncenter.com/api/v2/getTransactions?address=EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5\&limit=2\&to_lt=0\&archival=true) 我们将得到包含两笔交易的响应(现在不需要的一些信息已经被隐藏,完整答案可以在上面的链接中看到)。 + +```json +{ + "ok": true, + "result": [ + { + "transaction_id": { + // highlight-next-line + "lt": "1944556000003", + // highlight-next-line + "hash": "swpaG6pTBXwYI2024NAisIFp59Fw3k1DRQ5fa5SuKAE=" + }, + "in_msg": { + "source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R", + "destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5", + "value": "1000000000", + "body_hash": "kBfGYBTkBaooeZ+NTVR0EiVGSybxQdb/ifXCRX5O7e0=", + "message": "Sea breeze 🌊" + }, + "out_msgs": [] + }, + { + "transaction_id": { + // highlight-next-line + "lt": "1943166000003", + // highlight-next-line + "hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk=" + }, + "in_msg": { + "source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R", + "destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5", + "value": "1000000000", + "body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=", + "message": "Spring forest 🌲" + }, + "out_msgs": [] + } + ] +} +``` + +我们从这个地址收到了最后两笔交易。当添加 `lt` 和 `hash` 到查询中时,我们将再次收到两笔交易。然而,第二笔将成为下一笔连续的交易。也就是说,我们将获得这个地址的第二笔和第三笔交易。 + +```json +{ + "ok": true, + "result": [ + { + "transaction_id": { + "lt": "1943166000003", + "hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk=" + }, + "in_msg": { + "source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R", + "destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5", + "value": "1000000000", + "body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=", + "message": "Spring forest 🌲" + }, + "out_msgs": [] + }, + { + "transaction_id": { + "lt": "1845458000003", + "hash": "k5U9AwIRNGhC10hHJ3MBOPT//bxAgW5d9flFiwr1Sao=" + }, + "in_msg": { + "source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R", + "destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5", + "value": "1000000000", + "body_hash": "XpTXquHXP64qN6ihHe7Tokkpy88tiL+5DeqIrvrNCyo=", + "message": "Second" + }, + "out_msgs": [] + } + ] +} +``` + +请求将看起来像[这样。](https://testnet.toncenter.com/api/v2/getTransactions?address=EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5\&limit=2\<=1943166000003\&hash=hxIQqn7lYD%2Fc%2FfNS7W%2FiVsg2kx0p%2FkNIGF6Ld0QEIxk%3D\&to_lt=0\&archival=true) + +我们还需要一个方法 `detectAddress`。 + +这是测试网上的 Tonkeeper 钱包地址的一个例子:`kQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aCTb`。如果我们在浏览器中查找交易,代替上述地址,有:`EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R`。 + +这个方法返回给我们“正确”的地址。 + +```json +{ + "ok": true, + "result": { + "raw_form": "0:b3409241010f85ac415cbf13b9b0dc6157d09a39d2bd0827eadb20819f067868", + "bounceable": { + "b64": "EQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R", + // highlight-next-line + "b64url": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R" + }, + "non_bounceable": { + "b64": "UQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU", + "b64url": "UQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU" + } + } +} +``` + +我们需要 `b64url`。 + +这个方法让我们能够验证用户的地址。 + +大部分而言,这就是我们所需要的。 + +### API 请求及其处理方法 + +让我们回到 IDE。创建文件 `api.py`。 + +导入所需的库。 + +```python +import requests +import json +# We import our db module, as it will be convenient to add from here +# transactions to the database +import db +``` + +- `requests`—用来向 API 发送请求 +- `json`—用来处理 json +- `db`—用来处理我们的 sqlite 数据库 + +让我们创建两个变量来存储请求的开头。 + +```python +# This is the beginning of our requests +MAINNET_API_BASE = "https://toncenter.com/api/v2/" +TESTNET_API_BASE = "https://testnet.toncenter.com/api/v2/" +``` + +从 config.json 文件中获取所有 API 令牌和钱包。 + +```python +# Find out which network we are working on +with open('config.json', 'r') as f: + config_json = json.load(f) + MAINNET_API_TOKEN = config_json['MAINNET_API_TOKEN'] + TESTNET_API_TOKEN = config_json['TESTNET_API_TOKEN'] + MAINNET_WALLET = config_json['MAINNET_WALLET'] + TESTNET_WALLET = config_json['TESTNET_WALLET'] + WORK_MODE = config_json['WORK_MODE'] +``` + +根据网络,我们取所需的数据。 + +```python +if WORK_MODE == "mainnet": + API_BASE = MAINNET_API_BASE + API_TOKEN = MAINNET_API_TOKEN + WALLET = MAINNET_WALLET +else: + API_BASE = TESTNET_API_BASE + API_TOKEN = TESTNET_API_TOKEN + WALLET = TESTNET_WALLET +``` + +我们的第一个请求函数 `detectAddress`。 + +```python +def detect_address(address): + url = f"{API_BASE}detectAddress?address={address}&api_key={API_TOKEN}" + r = requests.get(url) + response = json.loads(r.text) + try: + return response['result']['bounceable']['b64url'] + except: + return False +``` + +在输入中,我们有预计的地址,输出要么是我们需要的“正确”地址,以便进行进一步的工作,要么是 False。 + +你可能会注意到请求末尾出现了 API 密钥。它是为了移除对 API 请求数量的限制。没有它,我们被限制为每秒一个请求。 + +这里是 `getTransactions` 的下一个函数: + +```python +def get_address_transactions(): + url = f"{API_BASE}getTransactions?address={WALLET}&limit=30&archival=true&api_key={API_TOKEN}" + r = requests.get(url) + response = json.loads(r.text) + return response['result'] +``` + +此函数返回最后 30 次对我们 `WALLET` 的交易。 + +这里可以看到 `archival=true`。这是因为我们只需要从具有完整区块链历史记录的节点获取交易。 + +在输出中,我们获得一个交易列表—[{0},{1},…,{29}]。简而言之,是字典列表。 + +最后一个函数: + +```python +def find_transaction(user_wallet, value, comment): + # Get the last 30 transactions + transactions = get_address_transactions() + for transaction in transactions: + # Select the incoming "message" - transaction + msg = transaction['in_msg'] + if msg['source'] == user_wallet and msg['value'] == value and msg['message'] == comment: + # If all the data match, we check that this transaction + # we have not verified before + t = db.check_transaction(msg['body_hash']) + if t == False: + # If not, we write in the table to the verified + # and return True + db.add_v_transaction( + msg['source'], msg['body_hash'], msg['value'], msg['message']) + print("find transaction") + print( + f"transaction from: {msg['source']} \nValue: {msg['value']} \nComment: {msg['message']}") + return True + # If this transaction is already verified, we check the rest, we can find the right one + else: + pass + # If the last 30 transactions do not contain the required one, return False + # Here you can add code to see the next 29 transactions + # However, within the scope of the Example, this would be redundant. + return False +``` + +输入是“正确”的钱包地址、金额和评论。如果找到预期的进账交易,输出为 True;否则为 False。 + +## Telegram 机器人 + +首先,让我们为机器人创建基础。 + +### 导入 + +在这部分,我们将导入所需的库。 + +来自 `aiogram`,我们需要 `Bot`、`Dispatcher`、`types` 和 `executor`。 + +```python +from aiogram import Bot, Dispatcher, executor, types +``` + +`MemoryStorage` 是用于临时存储信息的。 + +`FSMContext`, `State`, 和 `StatesGroup` 用于与状态机工作。 + +```python +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +``` + +`json` 用来处理 json 文件。`logging` 用来记录错误。 + +```python +import json +import logging +``` + +`api` 和 `db` 是我们自己的文件,稍后我们将填充内容。 + +```python +import db +import api +``` + +### 配置设置 + +建议您将如 `BOT_TOKEN` 和接收付款的钱包等数据存储在一个名为 `config.json` 的单独文件中,以便于使用。 + +```json +{ + "BOT_TOKEN": "Your bot token", + "MAINNET_API_TOKEN": "Your mainnet api token", + "TESTNET_API_TOKEN": "Your testnet api token", + "MAINNET_WALLET": "Your mainnet wallet", + "TESTNET_WALLET": "Your testnet wallet", + "WORK_MODE": "testnet" +} +``` + +#### 机器人令牌 + +`BOT_TOKEN` 是你的 Telegram 机器人令牌,来自 [@BotFather](https://t.me/BotFather) + +#### 工作模式 + +在 `WORK_MODE` 键中,我们将定义机器人的工作模式—在测试网或主网;分别为 `testnet` 或 `mainnet`。 + +#### API 令牌 + +`*_API_TOKEN` 的 API 令牌可以在 [TON Center](https://toncenter.com/) 机器人处获取: + +- 对于主网 — [@tonapibot](https://t.me/tonapibot) +- 对于测试网 — [@tontestnetapibot](https://t.me/tontestnetapibot) + +#### 将配置连接到我们的机器人 + +接下来,我们完成机器人设置。 + +从 `config.json` 获取机器人工作所需的令牌: + +```python +with open('config.json', 'r') as f: + config_json = json.load(f) + # highlight-next-line + BOT_TOKEN = config_json['BOT_TOKEN'] + # put wallets here to receive payments + MAINNET_WALLET = config_json['MAINNET_WALLET'] + TESTNET_WALLET = config_json['TESTNET_WALLET'] + WORK_MODE = config_json['WORK_MODE'] + +if WORK_MODE == "mainnet": + WALLET = MAINNET_WALLET +else: + # By default, the bot will run on the testnet + WALLET = TESTNET_WALLET +``` + +### 日志记录和机器人设置 + +```python +logging.basicConfig(level=logging.INFO) +bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML) +dp = Dispatcher(bot, storage=MemoryStorage()) +``` + +### 状态 + +我们需要使用状态将机器人工作流程划分为阶段。我们可以将每个阶段专门用于特定任务。 + +```python +class DataInput (StatesGroup): + firstState = State() + secondState = State() + WalletState = State() + PayState = State() +``` + +详情和示例请参见 [Aiogram 文档](https://docs.aiogram.dev/en/latest/)。 + +### 消息处理器(Message handlers) + +这是我们将编写机器人交互逻辑的部分。 + +我们将使用两种类型的处理器: + +- `message_handler` 用于处理用户消息。 +- `callback_query_handler` 用于处理来自内联键盘的回调。 + +如果我们想处理用户的消息,我们将使用 `message_handler` 并在函数上方放置 `@dp.message_handler` 装饰器。在这种情况下,当用户向机器人发送消息时,将调用该函数。 + +在装饰器中,我们可以指定将在何种条件下调用该函数。例如,如果我们想要在用户发送文本 `/start` 的消息时调用函数,那么我们将编写以下内容: + +``` +@dp.message_handler(commands=['start']) +``` + +处理器需要分配给一个异步函数。在这种情况下,我们将使用 `async def` 语法。`async def` 语法用于定义将异步调用的函数。 + +#### /start + +让我们从 `/start` 命令处理器开始。 + +```python +@dp.message_handler(commands=['start'], state='*') +async def cmd_start(message: types.Message): + await message.answer(f"WORKMODE: {WORK_MODE}") + # check if user is in database. if not, add him + isOld = db.check_user( + message.from_user.id, message.from_user.username, message.from_user.first_name) + # if user already in database, we can address him differently + if isOld == False: + await message.answer(f"You are new here, {message.from_user.first_name}!") + await message.answer(f"to buy air send /buy") + else: + await message.answer(f"Welcome once again, {message.from_user.first_name}!") + await message.answer(f"to buy more air send /buy") + await DataInput.firstState.set() +``` + +在此处理器的装饰器中,我们看到 `state='*'`。这意味着无论机器人的状态如何,该处理器都将被调用。如果我们希望处理器仅在机器人处于特定状态时调用,我们将编写 `state=DataInput.firstState`。在这种情况下,处理器仅在机器人处于 `firstState` 状态时被调用。 + +用户发送 `/start` 命令后,机器人将使用 `db.check_user` 函数检查用户是否在数据库中。如果不是,它将添加他。此函数还将返回布尔值,我们可以使用它以不同的方式对待用户。之后,机器人将设置状态为 `firstState`。 + +#### /cancel + +接下来是 /cancel 命令处理器。它需要返回到 `firstState` 状态。 + +```python +@dp.message_handler(commands=['cancel'], state="*") +async def cmd_cancel(message: types.Message): + await message.answer("Canceled") + await message.answer("/start to restart") + await DataInput.firstState.set() +``` + +#### /buy + +当然还有 `/buy` 命令处理器。在这个示例中我们将出售不同类型的空气。我们将使用reply keyboard来选择air types。 + +```python +# /buy command handler +@dp.message_handler(commands=['buy'], state=DataInput.firstState) +async def cmd_buy(message: types.Message): + # reply keyboard with air types + keyboard = types.ReplyKeyboardMarkup( + resize_keyboard=True, one_time_keyboard=True) + keyboard.add(types.KeyboardButton('Just pure 🌫')) + keyboard.add(types.KeyboardButton('Spring forest 🌲')) + keyboard.add(types.KeyboardButton('Sea breeze 🌊')) + keyboard.add(types.KeyboardButton('Fresh asphalt 🛣')) + await message.answer(f"Choose your air: (or /cancel)", reply_markup=keyboard) + await DataInput.secondState.set() +``` + +所以,当用户发送 `/buy` 命令时,机器人发送一个reply keyboard给他,上面有air type。用户选择air type后,机器人将设置状态为 `secondState`。 + +此处理器将仅在 `secondState` 被设置时工作,并将等待用户发送air type的消息。在这种情况下,我们需要存储用户选择的air type,因此我们将 FSMContext 作为参数传递给函数。 + +FSMContext 用于在机器人的内存中存储数据。我们可以在其中存储任何数据,但这个内存不是持久的,所以如果机器人重启,数据将会丢失。但它很适合存储临时数据。 + +```python +# handle air type +@dp.message_handler(state=DataInput.secondState) +async def air_type(message: types.Message, state: FSMContext): + if message.text == "Just pure 🌫": + await state.update_data(air_type="Just pure 🌫") + elif message.text == "Fresh asphalt 🛣": + await state.update_data(air_type="Fresh asphalt 🛣") + elif message.text == "Spring forest 🌲": + await state.update_data(air_type="Spring forest 🌲") + elif message.text == "Sea breeze 🌊": + await state.update_data(air_type="Sea breeze 🌊") + else: + await message.answer("Wrong air type") + await DataInput.secondState.set() + return + await DataInput.WalletState.set() + await message.answer(f"Send your wallet address") +``` + +使用... + +```python +await state.update_data(air_type="Just pure 🌫") +``` + +...在 FSMContext 中存储air type之后,我们设置状态为 `WalletState` 并要求用户发送他的钱包地址。 + +此处理器将仅在 `WalletState` 被设置时工作,并将等待用户发送钱包地址的消息。 + +下一个处理器看起来可能非常复杂,但实际上并不难。首先,我们使用 `len(message.text) == 48` 检查消息是否是有效的钱包地址,因为钱包地址长 48 个字符。之后,我们使用 `api.detect_address` 函数检查地址是否有效。如你从 API 部分记得的那样,这个函数还返回 "正确" 地址,它将被存储在数据库中。 + +之后,我们使用 `await state.get_data()` 从 FSMContext 获取air type并将其存储在 `user_data` 变量中。 + +现在我们有了付款过程所需的所有数据。我们只需要生成一个付款链接并发送给用户。让我们使用inline keyboard。 + +在此示例中,将为付款创建三个按钮: + +- 官方 TON Wallet +- Tonhub +- Tonkeeper + +对于钱包的特殊按钮的优点是,如果用户尚未拥有钱包,则网站将提示他安装一个。 + +你可以随意使用你想要的内容。 + +我们还需要一个用户付款后按下的按钮,这样我们就可以检查支付是否成功。 + +```python +@dp.message_handler(state=DataInput.WalletState) +async def user_wallet(message: types.Message, state: FSMContext): + if len(message.text) == 48: + res = api.detect_address(message.text) + if res == False: + await message.answer("Wrong wallet address") + await DataInput.WalletState.set() + return + else: + user_data = await state.get_data() + air_type = user_data['air_type'] + # inline button "check transaction" + keyboard2 = types.InlineKeyboardMarkup(row_width=1) + keyboard2.add(types.InlineKeyboardButton( + text="Check transaction", callback_data="check")) + keyboard1 = types.InlineKeyboardMarkup(row_width=1) + keyboard1.add(types.InlineKeyboardButton( + text="Ton Wallet", url=f"ton://transfer/{WALLET}?amount=1000000000&text={air_type}")) + keyboard1.add(types.InlineKeyboardButton( + text="Tonkeeper", url=f"https://app.tonkeeper.com/transfer/{WALLET}?amount=1000000000&text={air_type}")) + keyboard1.add(types.InlineKeyboardButton( + text="Tonhub", url=f"https://tonhub.com/transfer/{WALLET}?amount=1000000000&text={air_type}")) + await message.answer(f"You choose {air_type}") + await message.answer(f"Send 1 toncoin to address \n{WALLET} \nwith comment \n{air_type} \nfrom your wallet ({message.text})", reply_markup=keyboard1) + await message.answer(f"Click the button after payment", reply_markup=keyboard2) + await DataInput.PayState.set() + await state.update_data(wallet=res) + await state.update_data(value_nano="1000000000") + else: + await message.answer("Wrong wallet address") + await DataInput.WalletState.set() +``` + +#### /me + +我们需要的最后一个消息处理器是 `/me` 命令。它显示用户的支付信息。 + +```python +# /me command handler +@dp.message_handler(commands=['me'], state="*") +async def cmd_me(message: types.Message): + await message.answer(f"Your transactions") + # db.get_user_payments returns list of transactions for user + transactions = db.get_user_payments(message.from_user.id) + if transactions == False: + await message.answer(f"You have no transactions") + else: + for transaction in transactions: + # we need to remember that blockchain stores value in nanotons. 1 toncoin = 1000000000 in blockchain + await message.answer(f"{int(transaction['value'])/1000000000} - {transaction['comment']}") +``` + +### 回调处理器(Callback handlers) + +我们可以在按钮中设置回调数据,当用户按下按钮时,这些数据将被发送给机器人。在用户交易后按下的按钮中,我们设置回调数据为 "check"。因此,我们需要处理这个回调。 + +回调处理器与消息处理器非常相似,但它们有 `types.CallbackQuery` 作为参数,而不是 `message`。函数装饰器也有所不同。 + +```python +@dp.callback_query_handler(lambda call: call.data == "check", state=DataInput.PayState) +async def check_transaction(call: types.CallbackQuery, state: FSMContext): + # send notification + user_data = await state.get_data() + source = user_data['wallet'] + value = user_data['value_nano'] + comment = user_data['air_type'] + result = api.find_transaction(source, value, comment) + if result == False: + await call.answer("Wait a bit, try again in 10 seconds. You can also check the status of the transaction through the explorer (tonscan.org/)", show_alert=True) + else: + db.v_wallet(call.from_user.id, source) + await call.message.edit_text("Transaction is confirmed \n/start to restart") + await state.finish() + await DataInput.firstState.set() +``` + +在此处理器中,我们从 FSMContext 获取用户数据并使用 `api.find_transaction` 函数检查交易是否成功。如果成功,我们将钱包地址存储在数据库中,并向用户发送通知。此后,用户可以使用 `/me` 命令查找他的交易。 + +### main.py 的最后一部分 + +最后,别忘了: + +```python +if __name__ == '__main__': + executor.start_polling(dp, skip_updates=True) +``` + +这部分需要启动机器人。 +在 `skip_updates=True` 中,我们指定我们不想处理旧消息。但如果您想处理所有消息,可以将其设置为 `False`。 + +:::info + +`main.py` 的所有代码可以在[这里](https://github.com/LevZed/ton-payments-in-telegram-bot/blob/main/bot/main.py)找到。 + +::: + +## 机器人动起来 + +我们终于做到了!现在你应该有一个工作中的机器人。你可以测试它! + +运行机器人的步骤: + +1. 填写 `config.json` 文件。 +2. 运行 `main.py`。 + +所有文件必须在同一个文件夹中。要启动机器人,您需要运行 `main.py` 文件。您可以在 IDE 或终端中这样做: + +``` +python main.py +``` + +如果您遇到任何错误,可以在终端中检查。也许您在代码中漏掉了一些东西。 + +工作中的机器人示例[@AirDealerBot](https://t.me/AirDealerBot) + +![bot](/img/tutorials/apiatb-bot.png) + +## 参考资料 + +- 作为 [ton-footsteps/8](https://github.com/ton-society/ton-footsteps/issues/8) 的一部分 +- 由 Lev 制作([Telegram @Revuza](https://t.me/revuza), [LevZed on GitHub](https://github.com/LevZed)) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/web3-game-example.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/web3-game-example.md new file mode 100644 index 0000000000..b673dfd7da --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/web3-game-example.md @@ -0,0 +1,304 @@ +# TON 区块链适用于游戏 + +## 教程内容 + +在本教程中,我们将探讨如何将 TON 区块链添加到游戏中。作为示例,我们将使用 Phaser 编写的 Flappy Bird 克隆游戏,并逐步添加 GameFi 功能。在教程中,我们将使用短代码片段和伪代码来增加可读性。同时,我们还将提供指向真实代码块的链接,以帮助您更好地理解。完整的实现可以在[演示库](https://github.com/ton-community/flappy-bird)中找到。 + +![没有 GameFi 功能的 Flappy Bird 游戏](/img/tutorials/gamefi-flappy/no-gamefi-yet.png) + +我们将实现以下功能: + +- 成就奖励。让我们用 [SBTs](https://docs.ton.org/learn/glossary#sbt) 奖励我们的用户。成就系统是增加用户参与度的绝佳工具。 +- 游戏货币。在 TON 区块链上,启动自己的代币(jetton)很容易。代币可以用来创建游戏内经济。我们的用户将能够赚取游戏币并在之后消费它们。 +- 游戏商店。我们将为用户提供使用游戏货币或 TON 代币购买游戏内物品的可能性。 + +## 准备工作 + +### 安装 GameFi SDK + +首先,我们将设置游戏环境。为此,我们需要安装 `assets-sdk`。该包旨在准备开发者集成区块链到游戏中所需的一切。该库可以从 CLI 或 Node.js 脚本中使用。在本教程中,我们选择 CLI 方法。 + +```sh +npm install -g @ton-community/assets-sdk@beta +``` + +### 创建主钱包 + +接下来,我们需要创建一个主钱包。主钱包是我们将用来铸造 jetton、收藏品、NFT、SBT 和接收支付的钱包。 + +```sh +assets-cli setup-env +``` + +您将被问及几个问题: + +| 字段 | 提示 | +| :------ | :--------------------------------------------------------------------------------------------------------- | +| 网络 | 选择 `testnet`,因为它是测试游戏。 | +| 类型 | 选择 `highload-v2` 类型的钱包,因为它是用作主钱包的最佳、最高性能选项。 | +| 存储 | 用于存储 `NFT`/`SBT` 文件的存储。可以选择 `Amazon S3`(集中式)或 `Pinata`(去中心化)。 对于本教程,让我们使用 `Pinata`,因为去中心化存储对 Web3 游戏更具说明性。 | +| IPFS 网关 | 从中加载资产元数据的服务:`pinata`、`ipfs.io` 或输入其他服务 URL。 | + +脚本输出您可以打开的链接,以查看创建的钱包状态。 + +![新钱包处于 Nonexist 状态](/img/tutorials/gamefi-flappy/wallet-nonexist-status.png) + +如您所见,钱包实际上还没有创建。要想钱包真正创建,我们需要往里面存一些资金。在现实世界场景中,您可以使用任何喜欢的方式通过钱包地址存入钱包。在我们的案例中,我们将使用 [Testgiver TON Bot](https://t.me/testgiver_ton_bot)。请打开它领取 5 个测试 TON 代币。 + +稍后您将看到钱包中有 5 个 TON,并且其状态变为 `Uninit`。钱包准备就绪。首次使用后,其状态会变为 `Active`。 + +![充值后的钱包状态](/img/tutorials/gamefi-flappy/wallet-nonexist-status.png) + +### 铸造游戏货币 + +我们打算创建游戏货币,以奖励用户: + +```sh +assets-cli deploy-jetton +``` + +您将被问及几个问题: + +| 字段 | 提示 | +| :-- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 名称 | 代币名称,例如 `Flappy Jetton`。 | +| 描述 | 代币描述,例如:来自 Flappy Bird 宇宙的生动数字代币。 | +| 图片 | 下载预备好的 [jetton 标志](https://raw.githubusercontent.com/ton-community/flappy-bird/ca4b6335879312a9b94f0e89384b04cea91246b1/scripts/tokens/flap/image.png) 并指定文件路径。当然,您也可以使用任何图片。 | +| 符号 | `FLAP` 或输入您想使用的任何缩写。 | +| 小数位 | 货币小数点后将有多少个零。在我们的案例中,让它为 `0`。 | + +脚本输出您可以打开的链接,以查看创建的 jetton 状态。它将具有 `Active` 状态。钱包状态将从 `Uninit` 变为 `Active`。 + +![游戏货币 / jetton](/img/tutorials/gamefi-flappy/jetton-active-status.png) + +### 为 SBT 创建收藏品 + +仅作为示例,演示游戏中我们将奖励用户玩第一次和第五次游戏。因此,我们将铸造两个收藏品,以便在用户达到相关条件(第一次和第五次玩游戏)时将 SBT 放入其中: + +```sh +assets-cli deploy-nft-collection +``` + +| 字段 | 第一次游戏 | 第五次游戏 | +| :- | :----------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | +| 类型 | `sbt` | `sbt` | +| 名称 | Flappy First Flight | Flappy High Fiver | +| 描述 | 纪念您在 Flappy Bird 游戏中的首次旅行! | 以 Flappy High Fiver NFT 庆祝您的持续游戏! | +| 图片 | 您可以在此处下载[图片](https://raw.githubusercontent.com/ton-community/flappy-bird/article-v1/scripts/tokens/first-time/image.png) | 您可以在此处下载[图片](https://raw.githubusercontent.com/ton-community/flappy-bird/article-v1/scripts/tokens/five-times/image.png) | + +我们已经做好充分准备。接下来,让我们进入逻辑实现。 + +## 连接钱包 + +一切从用户连接其钱包开始。因此,让我们添加钱包连接集成。要从客户端操作区块链,我们需要为 Phaser 安装 GameFi SDK: + +```sh +npm install --save @ton/phaser-sdk@beta +``` + +现在,让我们设置 GameFi SDK 并创建它的实例: + +```typescript +import { GameFi } from '@ton/phaser-sdk' + +const gameFi = await GameFi.create({ + network: 'testnet' + connector: { + // if tonconnect-manifest.json is placed in the root you can skip this option + manifestUrl: '/assets/tonconnect-manifest.json', + actionsConfiguration: { + // address of your Telegram Mini App to return to after the wallet is connected + // url you provided to BothFather during the app creation process + // to read more please read https://github.com/ton-community/flappy-bird#telegram-bot--telegram-web-app + twaReturnUrl: URL_YOU_ASSIGNED_TO_YOUR_APP + }, + contentResolver: { + // some NFT marketplaces don't support CORS, so we need to use a proxy + // you are able to use any format of the URL, %URL% will be replaced with the actual URL + urlProxy: `${YOUR_BACKEND_URL}/${PROXY_URL}?url=%URL%` + }, + // where in-game purchases come to + merchant: { + // in-game jetton purchases (FLAP) + // use address you got running `assets-cli deploy-jetton` + jettonAddress: FLAP_ADDRESS, + // in-game TON purchases + // use master wallet address you got running `assets-cli setup-env` + tonAddress: MASTER_WALLET_ADDRESS + } + }, + +}) +``` + +> 要了解更多关于初始化选项,请阅读[库文档](https://github.com/ton-org/game-engines-sdk)。 + +> 要了解什么是 `tonconnect-manifest.json`,请查看 ton-connect [manifest描述](https://docs.ton.org/develop/dapps/ton-connect/manifest)。 + +现在我们准备创建一个连接钱包按钮。让我们在 Phaser 中创建一个 UI 场景,该场景将包含连接按钮: + +```typescript +class UiScene extends Phaser.Scene { + // receive gameFi instance via constructor + private gameFi: GameFi; + + create() { + this.button = this.gameFi.createConnectButton({ + scene: this, + // you can calculate the position for the button in your UI scene + x: 0, + y: 0, + button: { + onError: (error) => { + console.error(error) + } + // other options, read the docs + } + }) + } +} +``` + +> 阅读如何创建[连接按钮](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/connect-wallet-ui.ts#L82)和 [UI 场景](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/connect-wallet-ui.ts#L45)。 + +要监控用户何时连接或断开其钱包,让我们使用以下代码片段: + +```typescript +function onWalletChange(wallet: Wallet | null) { + if (wallet) { + // wallet is ready to use + } else { + // wallet is disconnected + } +} +const unsubscribe = gameFi.onWalletChange(onWalletChange) +``` + +> 要了解更多复杂场景,请查看[钱包连接流程](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/index.ts#L16)的完整实现。 + +阅读如何实现[游戏 UI 管理](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/index.ts#L50)。 + +现在我们已经连接了用户钱包,可以继续进行了。 + +![连接钱包按钮](/img/tutorials/gamefi-flappy/wallet-connect-button.png) +![确认钱包连接](/img/tutorials/gamefi-flappy/wallet-connect-confirmation.png) +![钱包已连接](/img/tutorials/gamefi-flappy/wallet-connected.png) + +## 实现成就和奖励 + +为了实现成就和奖励系统,我们需要准备一个端点,每个用户尝试时都会请求该端点。 + +### `/played` 端点 + +我们需要创建一个 `/played` 端点,该端点必须完成以下操作: + +- 接收带有用户钱包地址和 Mini App 启动时传递给 Mini App 的 Telegram 初始数据的正文。需要解析初始数据以提取认证数据,并确保用户只代表其自身发送请求。 +- 该端点必须计算并存储用户玩的游戏数。 +- 该端点必须检查是否是用户的第一次或第五次游戏,如果是,便使用相关的 SBT 奖励用户。 +- 该端点必须为每次游戏奖励用户 1 FLAP。 + +> 阅读[/played 端点](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/server/src/index.ts#L197)的代码。 + +### 请求 `/played` 端点 + +每次小鸟撞到管道或掉落时,客户端代码必须调用 `/played` 端点并传递正确的正文: + +```typescript +async function submitPlayed(endpoint: string, walletAddress: string) { + return await (await fetch(endpoint + '/played', { + body: JSON.stringify({ + tg_data: (window as any).Telegram.WebApp.initData, + wallet: walletAddress + }), + headers: { + 'content-type': 'application/json' + }, + method: 'POST' + })).json() +} + +const playedInfo = await submitPlayed('http://localhost:3001', wallet.account.address); +``` + +> 阅读[submitPlayer 函数](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/game-scene.ts#L10)的代码。 + +让我们玩第一次,确保我们将获得 FLAP 代币和 SBT 的奖励。点击 Play 按钮,穿过一个或两个管道,然后撞到一个管道上。好的,一切都在工作! + +![被奖励的代币和 SBT](/img/tutorials/gamefi-flappy/sbt-rewarded.png) + +再次玩 4 次以获得第二个 SBT,然后打开您的钱包,TON Space。这里是您的收藏品: + +![钱包中的成就 SBT](/img/tutorials/gamefi-flappy/sbts-in-wallet.png) + +## 实现游戏商店 + +要拥有游戏内商店,我们需要两个组件。第一个是提供关于用户购买的信息的端点。第二个是全局循环,以监视用户交易并为其所有者分配游戏属性。 + +### `/purchases` 端点 + +该端点执行以下操作: + +- 接收带有 Telegram Mini Apps 初始数据的 `auth` get 参数。 +- 该端点获取用户购买的物品并以物品列表的形式做出响应。 + +> 阅读[/purchases](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/server/src/index.ts#L303)端点的代码。 + +### 购买循环 + +要知道用户何时进行支付,我们需要监视主钱包的交易记录。每笔交易都必须包含消息 `userId`:`itemId`。我们将记住最后处理的交易,只获取新的交易,使用 `userId` 和 `itemId` 为用户分配他们购买的属性,重写最后一笔交易的哈希。这将在无限循环中工作。 + +> 阅读[购买循环](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/server/src/index.ts#L110)的代码。 + +### 客户端的商店 + +在客户端,我们有进入商店的按钮。 + +![进入商店按钮](/img/tutorials/gamefi-flappy/shop-enter-button.png) + +当用户点击按钮时,将打开商店场景。商店场景包含用户可以购买的物品列表。每个物品都有价格和购买按钮。当用户点击购买按钮时,将进行购买。 + +打开商店会触发购买商品的加载,并每 10 秒更新一次: + +```typescript +// inside of fetchPurchases function +await fetch('http://localhost:3000/purchases?auth=' + encodeURIComponent((window as any).Telegram.WebApp.initData)) +// watch for purchases +setTimeout(() => { fetchPurchases() }, 10000) +``` + +> 阅读[showShop 函数](https://github.com/ton-community/flappy-bird/blob/article-v1/workspaces/client/src/ui.ts#L191)的代码。 + +现在我们需要实现购买本身。为此,我们首先将创建 GameFi SDK 实例,然后使用 `buyWithJetton` 方法: + +```typescript +gameFi.buyWithJetton({ + amount: BigInt(price), + forwardAmount: BigInt(1), + forwardPayload: (window as any).Telegram.WebApp.initDataUnsafe.user.id + ':' + itemId +}); +``` + +![要购买的游戏道具](/img/tutorials/gamefi-flappy/purchase-item.png) +![购买确认](/img/tutorials/gamefi-flappy/purchase-confirmation.png) +![道具准备使用](/img/tutorials/gamefi-flappy/purchase-done.png) + +也可以用 TON 代币支付: + +```typescript +import { toNano } from '@ton/phaser-sdk' + +gameFi.buyWithTon({ + amount: toNano(0.5), + comment: (window as any).Telegram.WebApp.initDataUnsafe.user.id + ':' + 1 +}); +``` + +## 后记 + +本教程到此结束!我们考虑了基本的 GameFi 功能,但 SDK 提供了更多功能,如玩家之间的转账、操作 NFT 和收藏品的工具等。将来我们会提供更多功能。 + +要了解所有可用的 GameFi 功能,请阅读 [ton-org/game-engines-sdk](https://github.com/ton-org/game-engines-sdk) 和 [@ton-community/assets-sdk](https://github.com/ton-community/assets-sdk) 的文档。 + +所以,请在[讨论区](https://github.com/ton-org/game-engines-sdk/discussions)告诉我们您的想法! + +完整的实现可在 [flappy-bird](https://github.com/ton-community/flappy-bird) 库中找到。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/zero-knowledge-proofs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/zero-knowledge-proofs.md new file mode 100644 index 0000000000..4bb997c237 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/dapps/tutorials/zero-knowledge-proofs.md @@ -0,0 +1,630 @@ +# 在 TON 上构建一个简单的 ZK 项目 + +## 👋 介绍 + +**零知识**(ZK)证明是一种基本的密码学原语,它允许一方(证明者)向另一方(验证者)证明一个陈述是真实的,而不泄露除了该陈述本身的有效性之外的任何信息。零知识证明是构建隐私保护系统的强大工具,已在多种应用中使用,包括匿名支付、匿名消息系统和无信任桥接。 + +:::tip TVM 升级 2023.07 +在 2023 年 6 月之前,不能在 TON 上验证加密证明。由于配对算法背后复杂计算的普遍性,有必要通过添加 TVM 操作码来增加 TVM 的功能以执行证明验证。该功能已在 [2023 年 6 月更新](https://docs.ton.org/learn/tvm-instructions/tvm-upgrade#bls12-381)中添加,截至本文撰写时仅在测试网上可用。 +::: + +## 🦄 本教程将覆盖 + +1. 零知识密码学的基础知识,特别是 zk-SNARKs(零知识简洁非互动式知识论证) +2. 启动受信任设置仪式(使用 Tau 力量) +3. 编写和编译一个简单的 ZK 电路(使用 Circom 语言) +4. 生成、部署和测试一个 FunC 合约来验证样本 ZK 证明 + +## 🟥🟦 以颜色为重点的 ZK 证明解释 + +在我们深入了解零知识之前,让我们从一个简单的问题开始。假设你想向一个色盲人证明,可以区分不同颜色是可能的。我们将使用一种互动解决方案来解决这个问题。假设色盲人(验证者)找到两张相同的纸,一张为红色 🟥 一张为蓝色 🟦。 + +验证者向你(证明者)展示其中一张纸并要求你记住颜色。然后验证者将那张特定的纸放在背后,保持不变或更换它,并询问你颜色是否有变化。如果你能够分辨出颜色的区别,那么你可以看到颜色(或者你只是幸运地猜对了正确的颜色)。 + +现在,如果验证者完成这个过程 10 次,而你每次都能分辨出颜色的区别,那么验证者对正确颜色的使用有 ~99.90234% 的把握(1 - (1/2)^10)。因此,如果验证者完成这个过程 30 次,那么验证者将有 99.99999990686774% 的把握(1 - (1/2)^30)。 + +尽管如此,这是一个互动式解决方案,让 DApp 要求用户发送 30 笔交易来证明特定数据是不高效的。因此,需要一个非互动式解决方案;这就是 Zk-SNARKs 和 Zk-STARKs 的用武之地。 + +出于本教程的目的,我们只会涵盖 Zk-SNARKs。然而,你可以在 [StarkWare 网站](https://starkware.co/stark/) 上阅读更多关于 Zk-STARKs 如何工作的信息,而关于 Zk-SNARKs 和 Zk-STARKs 之间差异的信息可以在这篇 [Panther Protocol 博客文章](https://blog.pantherprotocol.io/zk-snarks-vs-zk-starks-differences-in-zero-knowledge-technologies/) 上找到。 + +### 🎯 Zk-SNARK: 零知识简洁非互动式知识论证 + +Zk-SNARK 是一个非互动式证明系统,其中证明者可以向验证者展示一个证明,以证明一个陈述是真实的。同时,验证者能够在非常短的时间内验证证明。通常,处理 Zk-SNARK 包括三个主要阶段: + +- 使用 [多方计算(MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) 协议进行受信任设置,以生成证明和验证密钥(使用 Tau 力量) +- 使用证明者密钥、公开输入和私密输入(见证)生成证明 +- 验证证明 + +让我们设置我们的开发环境并开始编码! + +## ⚙ 开发环境设置 + +我们开始这个过程的步骤如下: + +1. 使用 [Blueprint](https://github.com/ton-org/blueprint) 创建一个名为 "simple-zk" 的新项目,执行以下命令后,输入你的合约名称(例如 ZkSimple),然后选择第一个选项(使用一个空合约)。 + +```bash +npm create ton@latest simple-zk +``` + +2. 接下来我们会克隆被调整以支持 FunC 合约的 [snarkjs 库](https://github.com/kroist/snarkjs) + +```bash +git clone https://github.com/kroist/snarkjs.git +cd snarkjs +npm ci +cd ../simple-zk +``` + +3. 然后我们将安装 ZkSNARKs 所需的库 + +```bash +npm add --save-dev snarkjs ffjavascript +npm i -g circom +``` + +4. 接下来我们将下面的部分添加到 package.json 中(请注意,我们将使用的一些操作码在主网版本中尚未可用) + +```json +"overrides": { + "@ton-community/func-js-bin": "0.4.5-tvmbeta.1", + "@ton-community/func-js": "0.6.3-tvmbeta.1" +} +``` + +5. 另外,我们需要更改 @ton-community/sandbox 的版本,以便使用[最新的 TVM 更新](https://t.me/thetontech/56) + +```bash +npm i --save-dev @ton-community/sandbox@0.12.0-tvmbeta.1 +``` + +太好了!现在我们准备好开始在 TON 上编写我们的第一个 ZK 项目了! + +我们当前有两个主要文件夹构成了我们的 ZK 项目: + +- `simple-zk` 文件夹:包含我们的 Blueprint 模板,这将使我们能够编写我们的电路和合约以及测试 +- `snarkjs` 文件夹:包含我们在第 2 步中克隆的 snarkjs 库 + +## Circom 电路 + +首先让我们创建一个文件夹 `simple-zk/circuits` 并在其中创建一个文件并添加以下代码: + +```circom +template Multiplier() { + signal private input a; + signal private input b; + //private input means that this input is not public and will not be revealed in the proof + + signal output c; + + c <== a*b; + } + +component main = Multiplier(); +``` + +上面我们添加了一个简单的乘法器电路。通过使用这个电路,我们可以证明我们知道两个数字相乘的结果是特定的数字(c)而不泄露这些对应的数字(a 和 b)本身。 + +要了解更多关于 circom 语言的信息,请考虑查看[这个网站](https://docs.circom.io/)。 + +接下来我们将创建一个文件夹来存放我们的构建文件,并通过执行以下操作将数据移动到那里(在 `simple-zk` 文件夹中): + +```bash +mkdir -p ./build/circuits +cd ./build/circuits +``` + +### 💪 使用 Powers of TAU 创建受信任设置 + +现在是时候进行受信任设置了。要完成这个过程,我们将使用 [Powers of Tau](https://a16zcrypto.com/posts/article/on-chain-trusted-setup-ceremony/) 方法(可能需要几分钟时间来完成)。让我们开始吧: + +```bash +echo 'prepare phase1' +node ../../../snarkjs/build/cli.cjs powersoftau new bls12-381 14 pot14_0000.ptau -v +echo 'contribute phase1 first' +node ../../../snarkjs/build/cli.cjs powersoftau contribute pot14_0000.ptau pot14_0001.ptau --name="First contribution" -v -e="some random text" +echo 'contribute phase1 second' +node ../../../snarkjs/build/cli.cjs powersoftau contribute pot14_0001.ptau pot14_0002.ptau --name="Second contribution" -v -e="some random text" +echo 'apply a random beacon' +node ../../../snarkjs/build/cli.cjs powersoftau beacon pot14_0002.ptau pot14_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon" +echo 'prepare phase2' +node ../../../snarkjs/build/cli.cjs powersoftau prepare phase2 pot14_beacon.ptau pot14_final.ptau -v +echo 'Verify the final ptau' +node ../../../snarkjs/build/cli.cjs powersoftau verify pot14_final.ptau +``` + +完成上述过程后,它将在 build/circuits 文件夹中创建 pot14_final.ptau 文件,该文件可用于编写未来相关电路。 + +:::caution 约束大小 +如果编写了具有更多约束的更复杂电路,则需要使用更大参数生成您的 PTAU 设置。 +::: + +你可以删除不必要的文件: + +```bash +rm pot14_0000.ptau pot14_0001.ptau pot14_0002.ptau pot14_beacon.ptau +``` + +### 📜 电路编译 + +现在让我们通过在 `build/circuits` 文件夹下运行以下命令来编译电路: + +```bash +circom ../../circuits/test.circom --r1cs circuit.r1cs --wasm circuit.wasm --prime bls12381 --sym circuit.sym +``` + +现在我们的电路被编译到了 `build/circuits/circuit.sym`、`build/circuits/circuit.r1cs` 和 `build/circuits/circuit.wasm` 文件中。 + +:::info altbn-128 和 bls12-381 曲线 +altbn-128 和 bls12-381 椭圆曲线目前被 snarkjs 支持。[altbn-128](https://eips.ethereum.org/EIPS/eip-197) 曲线仅在 Ethereum 上支持。然而,在 TON 上只支持 bls12-381 曲线。 +::: + +让我们通过输入以下命令来检查我们电路的约束大小: + +```bash +node ../../../snarkjs/build/cli.cjs r1cs info circuit.r1cs +``` + +因此,正确的结果应该是: + +```bash +[INFO] snarkJS: Curve: bls12-381 +[INFO] snarkJS: # of Wires: 4 +[INFO] snarkJS: # of Constraints: 1 +[INFO] snarkJS: # of Private Inputs: 2 +[INFO] snarkJS: # of Public Inputs: 0 +[INFO] snarkJS: # of Labels: 4 +[INFO] snarkJS: # of Outputs: 1 +``` + +现在我们可以通过执行以下操作来生成参考 zkey: + +```bash +node ../../../snarkjs/build/cli.cjs zkey new circuit.r1cs pot14_final.ptau circuit_0000.zkey +``` + +然后我们将以下贡献添加到 zkey 中: + +```bash +echo "some random text" | node ../../../snarkjs/build/cli.cjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v +``` + +接下来,让我们导出最终的 zkey: + +```bash +echo "another random text" | node ../../../snarkjs/build/cli.cjs zkey contribute circuit_0001.zkey circuit_final.zkey +``` + +现在我们的最终 zkey 存在于 `build/circuits/circuit_final.zkey` 文件中。然后通过输入以下内容来验证 zkey: + +```bash +node ../../../snarkjs/build/cli.cjs zkey verify circuit.r1cs pot14_final.ptau circuit_final.zkey +``` + +最后,是时候生成验证密钥了: + +```bash +node ../../../snarkjs/build/cli.cjs zkey export verificationkey circuit_final.zkey verification_key.json +``` + +然后我们将删除不必要的文件: + +```bash +rm circuit_0000.zkey circuit_0001.zkey +``` + +在完成上述过程后,`build/circuits` 文件夹应如下显示: + +``` +build +└── circuits + ├── circuit_final.zkey + ├── circuit.r1cs + ├── circuit.sym + ├── circuit.wasm + ├── pot14_final.ptau + └── verification_key.json + +``` + +### ✅ 导出验证器合约 + +本节的最后一步是生成 FunC 验证器合约,我们将在我们的 ZK 项目中使用它。 + +```bash +node ../../../snarkjs/build/cli.cjs zkey export funcverifier circuit_final.zkey ../../contracts/verifier.fc +``` + +然后在 `contracts` 文件夹中生成了 `verifier.fc` 文件。 + +## 🚢 验证器合约部署 + +让我们逐步回顾 `contracts/verifier.fc` 文件,因为它包含了 ZK-SNARKs 的魔法: + +```func +const slice IC0 = "b514a6870a13f33f07bc314cdad5d426c61c50b453316c241852089aada4a73a658d36124c4df0088f2cd8838731b971"s; +const slice IC1 = "8f9fdde28ca907af4acff24f772448a1fa906b1b51ba34f1086c97cd2c3ac7b5e0e143e4161258576d2a996c533d6078"s; + +const slice vk_gamma_2 = "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"s; +const slice vk_delta_2 = "97b0fdbc9553a62a79970134577d1b86f7da8937dd9f4d3d5ad33844eafb47096c99ee36d2eab4d58a1f5b8cc46faa3907e3f7b12cf45449278832eb4d902eed1d5f446e5df9f03e3ce70b6aea1d2497fd12ed91bd1d5b443821223dca2d19c7"s; +const slice vk_alpha_1 = "a3fa7b5f78f70fbd1874ffc2104f55e658211db8a938445b4a07bdedd966ec60090400413d81f0b6e7e9afac958abfea"s; +const slice vk_beta_2 = "b17e1924160eff0f027c872bc13ad3b60b2f5076585c8bce3e5ea86e3e46e9507f40c4600401bf5e88c7d6cceb05e8800712029d2eff22cbf071a5eadf166f266df75ad032648e8e421550f9e9b6c497b890a1609a349fbef9e61802fa7d9af5"s; +``` + +以上是验证器合约必须使用的常量,以实现证明验证。这些参数可以在 `build/circuits/verification_key.json` 文件中找到。 + +```func +slice bls_g1_add(slice x, slice y) asm "BLS_G1_ADD"; +slice bls_g1_neg(slice x) asm "BLS_G1_NEG"; +slice bls_g1_multiexp( + slice x1, int y1, + int n +) asm "BLS_G1_MULTIEXP"; +int bls_pairing(slice x1, slice y1, slice x2, slice y2, slice x3, slice y3, slice x4, slice y4, int n) asm "BLS_PAIRING"; +``` + +以上行是新的 [TVM 操作码](https://docs.ton.org/learn/tvm-instructions/tvm-upgrade-2023-07#bls12-381)(BLS12-381),使得可以在 TON 区块链上进行配对检查。 + +load_data 和 save_data 函数仅用于加载和保存证明验证结果(仅用于测试目的)。 + +```func +() load_data() impure { + + var ds = get_data().begin_parse(); + + ctx_res = ds~load_uint(32); + + ds.end_parse(); +} + +() save_data() impure { + set_data( + begin_cell() + .store_uint(ctx_res, 32) + .end_cell() + ); +} +``` + +接下来,有几个简单的实用函数,用于加载发送到合约的证明数据: + +```func +(slice, slice) load_p1(slice body) impure { + ... +} + +(slice, slice) load_p2(slice body) impure { + ... +} + +(slice, int) load_newint(slice body) impure { + ... +} +``` + +最后一部分是 groth16Verify 函数,该函数需要检查发送到合约的证明的有效性。 + +```func +() groth16Verify( + slice pi_a, + slice pi_b, + slice pi_c, + + int pubInput0 + +) impure { + + slice cpub = bls_g1_multiexp( + + IC1, pubInput0, + + 1 + ); + + + cpub = bls_g1_add(cpub, IC0); + slice pi_a_neg = bls_g1_neg(pi_a); + int a = bls_pairing( + cpub, vk_gamma_2, + pi_a_neg, pi_b, + pi_c, vk_delta_2, + vk_alpha_1, vk_beta_2, + 4); + ;; ctx_res = a; + if (a == 0) { + ctx_res = 0; + } else { + ctx_res = 1; + } + save_data(); +} +``` + +现在需要编辑 `wrappers` 文件夹中的两个文件。需要注意的第一个文件是 `ZkSimple.compile.ts` 文件(如果在第 1 步中为合约设置了其他名称,其名称将不同)。我们将 `verifier.fc` 文件放入必须编译的合约列表中。 + +```ts +import { CompilerConfig } from '@ton-community/blueprint'; + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['contracts/verifier.fc'], // <-- here we put the path to our contract +}; +``` + +需要注意的另一个文件是 `ZkSimple.ts`。我们首先需要将 `verify` 操作码添加到 `Opcodes` 枚举中: + +```ts +export const Opcodes = { + verify: 0x3b3cca17, +}; +``` + +接下来,需要向 `ZkSimple` 类中添加 `sendVerify` 函数。此函数用于将证明发送到合约并进行测试,如下所示: + +```ts +async sendVerify( + provider: ContractProvider, + via: Sender, + opts: { + pi_a: Buffer; + pi_b: Buffer; + pi_c: Buffer; + pubInputs: bigint[]; + value: bigint; + queryID?: number; +} +) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell() + .storeUint(Opcodes.verify, 32) + .storeUint(opts.queryID ?? 0, 64) + .storeRef( + beginCell() + .storeBuffer(opts.pi_a) + .storeRef( + beginCell() + .storeBuffer(opts.pi_b) + .storeRef( + beginCell() + .storeBuffer(opts.pi_c) + .storeRef( + this.cellFromInputList(opts.pubInputs) + ) + ) + ) + ) + .endCell(), + }); +} +``` + +接下来,我们将 `cellFromInputList` 函数添加到 `ZkSimple` 类中。此函数用于从公开输入创建一个cell,它将被发送到合约。 + +```ts + cellFromInputList(list: bigint[]) : Cell { + var builder = beginCell(); + builder.storeUint(list[0], 256); + if (list.length > 1) { + builder.storeRef( + this.cellFromInputList(list.slice(1)) + ); + } + return builder.endCell() +} +``` + +最后,我们将添加到 `ZkSimple` 类中的最后一个函数是 `getRes` 函数。此函数用于接收证明验证结果。 + +```ts + async getRes(provider: ContractProvider) { + const result = await provider.get('get_res', []); + return result.stack.readNumber(); +} +``` + +现在我们可以运行所需的测试来部署合约。为了实现这一点,合约应该能够成功通过部署测试。在 `simple-zk` 文件夹的根目录下运行此命令: + +```bash +npx blueprint test +``` + +## 🧑‍💻 编写验证器的测试 + +让我们打开 `tests` 文件夹中的 `ZkSimple.spec.ts` 文件,并为 `verify` 函数编写一个测试。测试按如下方式进行: + +```ts +describe('ZkSimple', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('ZkSimple'); + }); + + let blockchain: Blockchain; + let zkSimple: SandboxContract; + + beforeEach(async () => { + // deploy contract + }); + + it('should deploy', async () => { + // the check is done inside beforeEach + // blockchain and zkSimple are ready to use + }); + + it('should verify', async () => { + // todo write the test + }); +}); +``` + +首先,我们需要导入我们将在测试中使用的几个包: + +```ts +import * as snarkjs from "snarkjs"; +import path from "path"; +import {buildBls12381, utils} from "ffjavascript"; +const {unstringifyBigInts} = utils; +``` + +- 如果运行测试,结果将是一个 TypeScript 错误,因为我们没有模块 'snarkjs' 和 ffjavascript 的声明文件。这可以通过编辑 `simple-zk` 文件夹根目录中的 `tsconfig.json` 文件来解决。我们需要将该文件中的 ***strict*** 选项更改为 ***false*** +- + +我们还需要导入将用于生成要发送给合约的证明的 `circuit.wasm` 和 `circuit_final.zkey` 文件。 + +```ts +const wasmPath = path.join(__dirname, "../build/circuits", "circuit.wasm"); +const zkeyPath = path.join(__dirname, "../build/circuits", "circuit_final.zkey"); +``` + +让我们填写 `should verify` 测试。我们首先需要生成证明。 + +```ts +it('should verify', async () => { + // proof generation + let input = { + "a": "123", + "b": "456", + } + let {proof, publicSignals} = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath); + let curve = await buildBls12381(); + let proofProc = unstringifyBigInts(proof); + var pi_aS = g1Compressed(curve, proofProc.pi_a); + var pi_bS = g2Compressed(curve, proofProc.pi_b); + var pi_cS = g1Compressed(curve, proofProc.pi_c); + var pi_a = Buffer.from(pi_aS, "hex"); + var pi_b = Buffer.from(pi_bS, "hex"); + var pi_c = Buffer.from(pi_cS, "hex"); + + // todo send the proof to the contract +}); +``` + +为了进行下一个步骤,需要定义 `g1Compressed`、`g2Compressed` 和 `toHexString` 函数。它们将用于将密码学证明转换为合约期望的格式。 + +```ts +function g1Compressed(curve, p1Raw) { + let p1 = curve.G1.fromObject(p1Raw); + + let buff = new Uint8Array(48); + curve.G1.toRprCompressed(buff, 0, p1); + // convert from ffjavascript to blst format + if (buff[0] & 0x80) { + buff[0] |= 32; + } + buff[0] |= 0x80; + return toHexString(buff); +} + +function g2Compressed(curve, p2Raw) { + let p2 = curve.G2.fromObject(p2Raw); + + let buff = new Uint8Array(96); + curve.G2.toRprCompressed(buff, 0, p2); + // convert from ffjavascript to blst format + if (buff[0] & 0x80) { + buff[0] |= 32; + } + buff[0] |= 0x80; + return toHexString(buff); +} + +function toHexString(byteArray) { + return Array.from(byteArray, function (byte: any) { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(""); +} +``` + +现在我们可以将密码学证明发送到合约。我们将使用 sendVerify 函数来完成这个操作。`sendVerify` 函数需要 5 个参数:`pi_a`、`pi_b`、`pi_c`、`pubInputs` 和 `value`。 + +```ts +it('should verify', async () => { + // proof generation + + + // send the proof to the contract + const verifier = await blockchain.treasury('verifier'); + const verifyResult = await zkSimple.sendVerify(verifier.getSender(), { + pi_a: pi_a, + pi_b: pi_b, + pi_c: pi_c, + pubInputs: publicSignals, + value: toNano('0.15'), // 0.15 TON for fee + }); + expect(verifyResult.transactions).toHaveTransaction({ + from: verifier.address, + to: zkSimple.address, + success: true, + }); + + const res = await zkSimple.getRes(); + + expect(res).not.toEqual(0); // check proof result + + return; + +}); +``` + +准备好在 TON 区块链上验证您的第一个证明了吗?开始此过程,请输入以下命令运行 Blueprint 测试: + +```bash +npx blueprint test +``` + +结果应如下所示: + +```bash + PASS tests/ZkSimple.spec.ts + ZkSimple + ✓ should deploy (857 ms) + ✓ should verify (1613 ms) + +Test Suites: 1 passed, 1 total +Tests: 2 passed, 2 total +Snapshots: 0 total +Time: 4.335 s, estimated 5 s +Ran all test suites. +``` + +要查看包含本教程代码的库,请点击[此处](https://github.com/SaberDoTcodeR/zk-ton-doc)找到的链接。 + +## 🏁 结论 + +在本教程中,您学习了以下技能: + +- 零知识的复杂性,特别是 ZK-SNARKs +- 编写和编译 Circom 电路 +- 对多方计算和 Tau 力量的熟悉度增加,这些被用于为电路生成验证密钥 +- 熟悉了Snarkjs 库用于导出电路 FunC 验证器 +- 熟悉了Blueprint用于验证器部署和测试编写 + +注意:上述示例教我们如何构建一个简单的 ZK 用例。尽管如此,可以在各种行业中实现一系列高度复杂的以 ZK 为中心的用例。这些包括: + +- 隐私投票系统 🗳 +- 隐私彩票系统 🎰 +- 隐私拍卖系统 🤝 +- 隐私交易💸(对于 Toncoin 或 Jettons) + +如果您有任何问题或发现错误 - 请随时写信给作者 - [@saber_coder](https://t.me/saber_coder) + +## 📌 参考资料 + +- [TVM 2023 年 6 月升级](https://docs.ton.org/learn/tvm-instructions/tvm-upgrade) +- [SnarkJs](https://github.com/iden3/snarkjs) +- [SnarkJs FunC fork](https://github.com/kroist/snarkjs) +- [TON 上的样例 ZK](https://github.com/SaberDoTcodeR/ton-zk-verifier) +- [Blueprint](https://github.com/ton-org/blueprint) + +## 📖 参阅 + +- [TON 无信任桥接 EVM 合约](https://github.com/ton-blockchain/ton-trustless-bridge-evm-contracts) +- [Tonnel Network:TON 上的隐私协议](http://github.com/saberdotcoder/tonnel-network) +- [TVM 挑战](https://blog.ton.org/tvm-challenge-is-here-with-over-54-000-in-rewards) + +## 📬 关于作者 + +- Saber的链接: [Telegram](https://t.me/saber_coder), [Github](https://github.com/saberdotcoder) 和 [LinkedIn](https://www.linkedin.com/in/szafarpoor/) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/node-maintenance-and-security.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/node-maintenance-and-security.md new file mode 100644 index 0000000000..c1b3d49357 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/node-maintenance-and-security.md @@ -0,0 +1,217 @@ +# 维护与安全 + +## 介绍 + +本指南提供了关于维护和保护TON验证节点的一些基本信息。 + +本文档假设使用\*\*[TON基金会推荐](/participate/run-nodes/full-node)\*\*的配置和工具安装了验证者,但通用概念也适用于其他场景,并且对于精通的系统管理员也很有用。 + +## 维护 + +### 数据库整理 + +TON节点/验证者将其数据库保存在`--db`标志指定的路径下,通常是`/var/ton-work/db`,这个目录由节点创建和管理,但建议每月进行一次数据库整理/清理任务,以移除一些残留物。 + +**重要**:在执行以下步骤之前,您**必须**停止验证者进程,否则很可能会导致数据库损坏。 + +这个过程大约需要5分钟完成,不会造成重大服务中断。 + +#### 切换到root用户 + +```sh +sudo -s +``` + +#### 停止验证者服务 + +```sh +systemctl stop validator +``` + +#### 验证验证者是否停止运行 + +```sh +systemctl status validator +``` + +#### 执行数据库清理 + +```sh +find /var/ton-work/db -name 'LOG.old*' -exec rm {} + +``` + +#### 启动验证者服务 + +```sh +systemctl start validator +``` + +通过分析进程和日志来验证验证者进程是否运行。验证者应该在几分钟内与网络重新同步。 + +### 备份 + +备份验证者的最简单和最有效的方法是复制关键节点配置文件、密钥和mytonctrl设置: + +- 节点配置文件:`/var/ton-work/db/config.json` +- 节点私钥环:`/var/ton-work/db/keyring` +- 节点公钥:`/var/ton-work/keys` +- mytonctrl配置和钱包:`$HOME/.local/share/myton*`,其中$HOME是启动mytonctrl安装的用户的主目录 **或者** `/usr/local/bin/mytoncore`,如果您以root用户安装了mytonctrl的话。 + +这组文件是您从头恢复节点所需的全部内容。 + +#### 快照 + +现代文件系统如ZFS提供快照功能,大多数云提供商也允许他们的客户在快照期间保留整个磁盘以备将来使用。 + +两种方法的问题是,您必须在进行快照之前停止节点,否则很可能导致数据库损坏并带来意想不到的后果。许多云提供商在进行快照之前也要求您关闭机器。 + +这样的停机不应该频繁进行,如果您每周对节点进行一次快照,那么在最坏的情况下,恢复后您将拥有一个一周旧的数据库,节点赶上网络的时间将比使用mytonctrl的“从转储安装”功能(在调用`install.sh`脚本时添加-d标志)进行新安装更长。 + +### 灾难恢复 + +要在新机器上恢复您的节点: + +#### 安装mytonctrl / 节点 + +为了快速初始化节点,在安装脚本调用中添加-d开关。 + +#### 切换到root用户 + +```sh +sudo -s +``` + +#### 停止mytoncore和验证者进程 + +```sh +systemctl stop validator +systemctl stop mytoncore +``` + +#### 应用备份的节点配置文件 + +- 节点配置文件:`/var/ton-work/db/config.json` +- 节点私钥环:`/var/ton-work/db/keyring` +- 节点公钥:`/var/ton-work/keys` + +#### 设置节点IP地址 + +如果您的新节点有不同的IP地址,则必须编辑节点配置文件`/var/ton-work/db/config.json`,并将leaf`.addrs[0].ip`设置为新IP地址的**十进制**表示。您可以使用\*\*[这个](https://github.com/sonofmom/ton-tools/blob/master/node/ip2dec.py)\*\* python脚本将您的IP转换为十进制。 + +#### 确保数据库权限正确 + +```sh +chown -R validator:validator /var/ton-work/db +``` + +#### 应用备份的mytonctrl配置文件 + +用备份的内容替换`$HOME/.local/share/myton*`,其中$HOME是启动mytonctrl安装的用户的主目录,确保您复制的所有文件的所有者是该用户。 + +#### 启动mytoncore和验证者进程 + +```sh +systemctl start validator +systemctl start mytoncore +``` + +## 安全 + +### 主机级安全 + +主机级安全是一个庞大的话题,超出了本文档的范围,但我们建议您永远不要在root用户下安装mytonctrl,使用服务账户以确保权限分离。 + +### 网络级安全 + +TON验证者是高价值资产,应该被保护以抵御外部威胁,您应该采取的第一步是尽可能使您的节点不可见,这意味着锁定所有网络连接。在验证者节点上,只有用于节点操作的UDP端口应该对互联网公开。 + +#### 工具 + +我们将使用\*\*[ufw](https://help.ubuntu.com/community/UFW)**防火墙界面以及**[jq](https://github.com/stedolan/jq)\*\* JSON命令行处理器。 + +#### 管理网络 + +作为节点运营商,您需要保留对机器的完全控制和访问权限,为此,您至少需要一个固定的IP地址或范围。 + +我们还建议您设置一个带有固定 IP 地址的小型“跳板机”VPS,如果您的家庭/办公室没有固定 IP 或者在您丢失主要 IP 地址时,可以作为访问受保护机器的备用方式供您使用。 + +#### 安装ufw和jq1 + +```sh +sudo apt install -y ufw jq +``` + +#### ufw规则集的基本锁定 + +```sh +sudo ufw default deny incoming; sudo ufw default allow outgoing +``` + +#### 禁用自动ICMP回声请求接受 + +```sh +sudo sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/#-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules +``` + +#### 从管理网络启用所有访问 + +```sh +sudo ufw insert 1 allow from +``` + +对每个管理网络/地址重复上述命令。 + +#### 向公众公开节点/验证者UDP端口 + +```sh +sudo ufw allow proto udp from any to any port `sudo jq -r '.addrs[0].port' /var/ton-work/db/config.json` +``` + +#### 仔细检查您的管理网络 + +重要:在启用防火墙之前,请仔细检查您是否添加了正确的管理地址! + +#### 启用ufw防火墙 + +```sh +sudo ufw enable +``` + +#### 检查状态 + +使用以下命令检查防火墙状态: + +```sh + sudo ufw status numbered +``` + +以下是具有两个管理网络/地址的锁定节点的示例输出: + +``` +Status: active + + To Action From + -- ------ ---- +[ 1] Anywhere ALLOW IN /28 +[ 2] Anywhere ALLOW IN /32 +[ 3] /udp ALLOW IN Anywhere +[ 4] /udp (v6) ALLOW IN Anywhere (v6) +``` + +#### 公开LiteServer端口 + +```sh +sudo ufw allow proto tcp from any to any port `sudo jq -r '.liteservers[0].port' /var/ton-work/db/config.json` +``` + +请注意,验证者上不应公开LiteServer端口。 + +#### 更多关于UFW的信息 + +参见Digital Ocean提供的这篇优秀的\*\*[ufw教程](https://www.digitalocean.com/community/tutorials/ufw-essentials-common-firewall-rules-and-commands)\*\*,了解更多ufw的魔法。 + +### IP切换 + +如果您认为您的节点正在遭受攻击,则应考虑更换IP地址。实现切换的方式取决于您的托管提供商;您可能需要预订第二个地址、将**已停止**的VM克隆到另一个实例,或者通过执行\*\*[灾难恢复](#disaster-recovery)\*\*流程设置新的实例。 + +无论如何,请确保您在节点配置文件中\*\*[设置新的IP地址](#set-node-ip)\*\*! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/persistent-states.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/persistent-states.md new file mode 100644 index 0000000000..eea42a007f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/persistent-states.md @@ -0,0 +1,24 @@ +# 持久状态 + +节点定期存储区块链状态的快照。每个状态都在某个主链区块创建,并且具有一定的TTL(生存时间)。区块和TTL的选择使用以下算法: + +只有关键区块可以被选择。一个区块有一个时间戳`ts`。时间段的长度为`2^17`秒(大约1.5天)。时间戳`ts`的区块的时间段为`x = floor(ts / 2^17)`。每个时间段的第一个关键区块被选择用来创建持久状态。 + +时间段`x`的状态的TTL等于`2^(18 + ctz(x))`,其中`ctz(x)`是`x`的二进制表示中的尾随零的数量(即最大的`y`使得`x`可以被`2^y`整除)。 + +这意味着每1.5天会创建一个持久状态,其中一半的状态具有`2^18`秒(3天)的TTL,剩余状态的一半具有`2^19`秒(6天)的TTL,依此类推。 + +2022年有以下长期(至少3个月)的持久状态(未来的时间是大致的): + +| 区块序列号 | 区块时间 | TTL | 过期时间 | +| ---------------------------------------------------------------------------------------------------: | --------------------------------------------------: | ---: | --------------------------------------------------: | +| [8930706](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=8930706) | 2022-02-07 01:31:53 | 777天 | 2024-03-24 18:52:57 | +| [27747086](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=27747086) | 2022-03-27 14:36:58 | 97天 | 2022-07-02 16:47:06 | +| [32638387](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=32638387) | 2022-05-14 20:00:00 | 194天 | 2022-11-24 23:00:00 | +| [34835953](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=34835953) | 2022-07-02 09:00:00 | 97天 | 2022-10-07 10:00:00 | +| [35893070](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=35893070) | 2022-08-19 22:00:00 | 388天 | 2023-09-12 06:00:00 | +| [36907647](https://explorer.toncoin.org/search?workchain=-1\&shard=8000000000000000\&seqno=36907647) | 2022-10-07 11:00:00 | 97天 | 2023-01-12 12:00:00 | + +当节点第一次启动时,它必须下载一个持久状态。这在[validator/manager-init.cpp](https://github.com/ton-blockchain/ton/blob/master/validator/manager-init.cpp)中实现。 + +从初始化区块开始,节点下载所有更新的关键区块。它选择最近的具有仍然存在的持久状态的关键区块(使用上述公式),然后下载相应的主链状态和所有分片的状态(或仅下载此节点所需的分片)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/archive-node.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/archive-node.md new file mode 100644 index 0000000000..09303fd619 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/archive-node.md @@ -0,0 +1,225 @@ +# 运行归档节点 + +## 概述 + +:::caution 需要系统管理员 +运行节点需要具备 Linux/Ubuntu 系统管理的基本知识。 +::: + +存档节点是一种[完整节点](/participate/run-nodes/fullnode)类型,存储区块链的扩展历史数据。 如果您正在创建一个需要访问历史数据的 blockchain 浏览器或类似的应用程序, 建议使用归档节点作为索引器。 + +## 先决条件 + +我们强烈建议使用支持的操作系统安装 mytonctrl: + +- Ubuntu 20.04 +- Ubuntu 22.04 +- Debian 11 + +请使用 [具有 sudo 权限的非 root 用户](/participate/run-nodes/full-node#prerequisites-1) 安装和运行 mytonctrl。 + +## 硬件要求 + +- 16 x 内核 CPU +- 128GB ECC 内存 +- 4TB SSD *OR* 预配置 32+k IOPS 存储器 +- 1 Gbit/s 网络连接 +- 峰值流量为每月 16 TB +- 公共 IP 地址(固定 IP 地址) + +__Note__注意:4 TB 假定使用启用压缩的 zfs 卷 + +## 安装 + +一般来说,运行存档节点需要以下步骤: + +1. 安装 ZFS 并准备卷 +2. 安装 MyTonCtrl +3. 在服务器上运行完整节点并停止验证器进程 +4. 从 https://archival-dump.ton.org 下载和恢复转储数据 +5. 运行完整节点,为存档节点配置数据库规格 + +### 安装 ZFS 并准备卷 + +通常,为您的节点在_专用SSD驱动器_上创建一个单独的ZFS池是个好主意,这将使您更容易管理存储空间并备份您的节点。 + +通常,在专用 SSD 驱动器上为节点创建一个单独的 ZFS 池是个好主意,这样可以方便管理存储空间和备份节点。 + +1. 安装 [zfs](https://ubuntu.com/tutorials/setup-zfs-storage-pool#1-overview) + +```shell +sudo apt install zfsutils-linux +``` + +2. 在专用的 4TB `` 上 [创建池](https://ubuntu.com/tutorials/setup-zfs-storage-pool#3-creating-a-zfs-pool),并将其命名为 `data` + +```shell +sudo zpool create data +``` + +3. 在还原之前,我们强烈建议在父 ZFS 文件系统上启用压缩功能,这将为您节省 [大量空间](https://www.servethehome.com/the-case-for-using-zfs-compression/)。要启用 "数据 "卷的压缩功能,请使用 root 账户输入: + +```shell +sudo zfs set compression=lz4 data +``` + +### 安装 MyTonCtrl + +请使用 [Running Full Node](/participate/run-nodes/full-node) 安装 mytonctrl。 + +### 准备节点 + +#### 准备节点 + +1. 执行还原之前,必须使用 root 账户停止验证器: + +```shell +sudo -s +systemctl stop validator.service +``` + +2. 备份 `ton-work` 配置文件(我们需要 `/var/ton-work/db/config.json`、`/var/ton-work/keys` 和 `/var/ton-work/db/keyring`)。 + +```shell +mv /var/ton-work /var/ton-work.bak +``` + +#### 下载转储 + +1. 在 [@TONBaseChatEn](https://t.me/TONBaseChatEn) Telegram 聊天中请求`user`和`password`凭证以获得下载转储的访问权限。 +2. 下面是一个从 ton.org 服务器下载和恢复转储的命令示例: + +```shell +wget --user --password -c https://archival-dump.ton.org/dumps/latest.zfs.lz | pv | plzip -d -n | zfs recv data/ton-work +``` + +转储大小为__~1.5TB__,因此下载和恢复需要一些时间。 + +准备并运行命令: + +1. 必要时安装工具(`pv`, `plzip`) +2. 将 `` 和 `` 替换为您的凭据 +3. 告诉 `plzip` 在机器允许的范围内使用尽可能多的内核,以加快提取速度 (`-n`) + +#### 安装转储 + +1. 挂载 zfs: + +```shell +zfs set mountpoint=/var/ton-work data/ton-work && zfs mount data/ton-work +``` + +2. 将`db/config.json`、`keys`和`db/keyring`从备份恢复到`/var/ton-work`。 + +```shell +cp /var/ton-work.bak/db/config.json /var/ton-work/db/config.json +cp -r /var/ton-work.bak/keys /var/ton-work/keys +cp -r /var/ton-work.bak/db/keyring /var/ton-work/db/keyring +``` + +3. 请确保`/var/ton-work`和`/var/ton-work/keys`目录的权限提升正确: + +- /var/ton-work/db "目录的所有者应为 "validator "用户: + +```shell +chown -R validator:validator /var/ton-work/db +``` + +- /var/ton-work/keys "目录的所有者应为 "ubuntu "用户: + +```shell +chown -R ubuntu:ubuntu /var/ton-work/keys +``` + +#### 更新配置 + +更新存档节点的节点配置。 + +1. 打开节点配置文件 \`/etc/systemd/system/validator.service + +```shell +nano /etc/systemd/system/validator.service +``` + +2. 在 `ExecStart` 行中添加节点的存储设置: + +```shell +--state-ttl 315360000 --archive-ttl 315360000 --block-ttl 315360000 +``` + +:::info +启动节点并观察日志后,请耐心等待。转储不带 DHT 缓存,因此您的节点需要一些时间才能找到其他节点,然后与它们同步。根据快照的时间,您的节点可能需要几个小时到几天的时间才能赶上网络。这是正常现象。 +::: + +#### 启动节点 + +1. 运行命令启动验证器: + +```shell +systemctl start validator.service +``` + +2. 从 *local user* 打开 `mytonctrl` 并使用 `status` 检查节点状态。 + +## 节点维护 + +节点数据库需要不时清理(我们建议每周清理一次),为此请以 root 身份执行以下步骤: + +1. 停止验证程序(切勿跳过!)。 + +```shell +sudo -s +systemctl stop validator.service +``` + +2. 删除旧日志 + +```shell +find /var/ton-work -name 'LOG.old*' -exec rm {} + +``` + +4. 删除临时文件 + +```shell +rm -r /var/ton-work/db/files/packages/temp.archive.* +``` + +5. 启动验证程序 + +```shell +systemctl start validator.service +``` + +## 故障排除和备份 + +如果由于某种原因,某些程序无法运行或发生故障,你可以随时 [roll back](https://docs.oracle.com/cd/E23824_01/html/821-1448/gbciq.html#gbcxk) 到 ZFS 文件系统上的 @archstate 快照,这是转储的原始状态。 + +1. 停止验证程序(切勿跳过!)。 + +```shell +sudo -s +systemctl stop validator.service +``` + +2. 检查快照名称 + +```shell +zfs list -t snapshot +``` + +3. 回滚到快照 + +```shell +zfs rollback data/ton-work@dumpstate +``` + +如果您的节点运行良好,您可以删除该快照以节省存储空间,但我们确实建议您定期为文件系统拍摄快照,以便回滚,因为验证器节点在某些情况下会损坏数据和 config.json。[zfsnap](https://www.zfsnap.org/docs.html)是一个自动轮换快照的好工具。 + +:::tip 需要帮助吗? +有问题或需要帮助?请在 [TON dev 聊天室](https://t.me/tondev_eng) 中提问,以获得社区的帮助。MyTonCtrl 开发人员也会在那里交流。 +::: + +## 参阅 + +- [TON 节点类型](/participate/nodes/node-types) +- [运行完整节点](/participate/run-nodes/full-node) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/full-node.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/full-node.mdx new file mode 100644 index 0000000000..ba8ebcf2e0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/full-node.mdx @@ -0,0 +1,676 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# 运行全节点 + +要安装和管理您自己的节点,请使用TON基金会开发的**MyTonCtrl**开源工具。大多数TON节点都通过**MyTonCtrl**进行了可靠和测试。 + +[MyTonCtrl](https://github.com/ton-blockchain/mytonctrl)是一个控制台应用程序,它是fift、lite-client和validator-engine-console的方便包装器。它专门开发用于简化在Linux操作系统上的钱包、域和验证者管理任务。 + +我们正在积极寻求关于安装过程的反馈。如果您有任何问题或建议,请[联系我们](https://t.me/SwiftAdviser)。 + +## 必要条件 + +我们强烈建议使用支持的操作系统安装MyTonCtrl: +* Ubuntu 20.04 +* Ubuntu 22.04 +* Debian 11 + +请使用具有**sudo**权限的**非root用户**来安装和运行MyTonCtrl。 + +## 硬件要求 + +这些要求适用于**带验证者的全节点**。如果您想运行没有验证者的全节点(例如[liteserver](/participate/run-nodes/full-node#hardware-requirements-1)),您可以使用较低配置的机器。 + +- 16核CPU +- 64 GB RAM +- 1TB NVME SSD _或者_ 预置64+k IOPS存储 +- 1 Gbit/s 网络连接 +- 固定公网IP地址 +- 高峰期每月16TB流量 + +您需要一台具有**固定IP地址**和**高带宽网络连接**的机器来运行TON区块链全节点。 + +通常,您需要在数据中心使用至少1 Gbit/s的连接的功能强大的服务器,以可靠地适应高峰负载(预计平均负载约为100 Mbit/s)。 + +### 推荐供应商 + +TON基金会推荐以下供应商运行验证者: + +#### AWS + +- **实例类型:** `m5.4xlarge` +- **CPU:** `16 vCPUs` +- **RAM:** `64 GB` +- **存储:** `1 TB NVMe SSD` +- **网络:** `高达10 Gbps` +- **公网IP:** `关联一个弹性IP以获得固定IP地址。` +- **流量:** `每月16 TB` + +#### GCP (谷歌云平台) + +- **机器类型:** `n2-standard-16` +- **CPU:** `16 vCPUs` +- **RAM:** `64 GB` +- **存储:** `1 TB NVMe SSD持久性磁盘` +- **网络:** `16 Gbps` +- **公网IP:** `预留一个静态外部IP地址。` +- **流量:** `每月16 TB` + +#### 阿里巴巴云 + +- **实例类型:** `ecs.g6.4xlarge` +- **CPU:** `16 vCPUs` +- **RAM:** `64 GB` +- **存储:** `1 TB NVMe SSD磁盘` +- **网络:** `高达10 Gbps` +- **公网IP:** `绑定一个弹性IP以获得固定IP地址。` +- **流量:** `每月16 TB` + +#### 腾讯云 + +- **实例类型:** `M5.4XLARGE` +- **CPU:** `16 vCPUs` +- **RAM:** `64 GB` +- **存储:** `1 TB NVMe SSD云磁盘` +- **网络:** `高达10 Gbps` +- **公网IP:** `关联一个弹性IP以获得固定IP地址。` +- **流量:** `每月16 TB` + +#### Vultr + +- **实例类型:** `裸金属 Intel E-2388G` +- **CPU:** `8 + + 核心 / 16 线程` +- **RAM:** `128 GB` +- **存储:** `1.92TB NVMe SSD` +- **网络:** `10 Gbps` +- **公网IP:** `实例包含固定IP地址。` +- **流量:** `每月16 TB` + +#### DigitalOcean + +- **实例类型:** `通用型高级 Intel` +- **CPU:** `16 vCPUs` +- **RAM:** `64 GB` +- **存储:** `1TB NVMe SSD` +- **网络:** `10 Gbps` +- **公网IP:** `实例包含固定IP地址。` +- **流量:** `每月16 TB` + +#### Latitude + +- **实例类型:** `c3.medium.x86` +- **CPU:** `16 核心 / 32 线程` +- **RAM:** `128 GB` +- **存储:** `1.9TB NVMe SSD` +- **网络:** `10 Gbps` +- **公网IP:** `实例包含固定IP地址。` +- **流量:** `每月16 TB` + +:::info +**注意:** 价格、配置和可用性可能会有所变化。在做出任何决定之前,建议始终检查相应云提供商的官方文档和定价页面。 +::: + +## 如何运行节点?(视频) + +[//]: # () + +请查看此视频逐步教程,以快速开始: + + + +## 逐步指导 + +### 必要条件 + +1. 以具有**sudo**权限的**非root**用户身份登录您的服务器。 + +2. 如果您没有非root用户,请以root身份登录并创建一个: + +```bash +sudo adduser +``` + +3. 将您的用户添加到sudo组: + +```bash +sudo usermod -aG sudo +``` + +4. 切换到您的非root用户: + +```bash +su - +``` + + +### 安装MyTonCtrl + +从具有**sudo**权限的**非root**用户帐户下载并运行安装脚本。选择您的Linux发行版: + + + + + + + ```bash + wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh + sudo bash install.sh -m full -d + ``` + + + + + ```bash + wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh + su root -c 'bash install.sh -m full -d' + ``` + + + + +* `-m full` - 全节点安装模式。 +* `-d` - **mytonctrl** 将下载最新区块链状态的[转储](https://dump.ton.org/)。 +这将减少同步时间数倍。 +* `-c ` - 如果您想使用非公共liteservers进行同步。_(不是必需的)_ + +### 运行mytonctrl + +1. 从第1步中用于安装的本地用户账户运行**MyTonCtrl**控制台: + + ```sh + sudo mytonctrl + ``` + +2. 使用`status`命令检查MyTonCtrl状态: + + ```sh + status + ``` + + +以下状态应该显示: + +* **mytoncore状态**:应为绿色。 +* **本地验证者状态**:也应为绿色。 +* **本地验证者未同步**:最初显示一个大数字。一旦新创建的验证者与其他验证者连接,数字将在250k左右。随着同步的进行,这个数字会减少。当它降至20以下时,验证者已同步。 + +**status**命令输出的示例: + +![status](/img/docs/nodes-validator/mytonctrl-status.png) + +:::caution 确保状态输出相同 +对于所有类型的节点,**Local Validator status**部分应该显示。 +如果没有显示,请[查看故障排除部分](/participate/run-nodes/full-node#status-command-displays-without-local-node-section)和[检查节点日志](/participate/run-nodes/full-node#check-the-node-logs)。 +::: + +等到`Local validator out of sync`变为少于20秒。 + +## 成为验证者 + +### 查看钱包列表 + +使用`wl`命令在**MyTonCtrl**控制台中查看可用钱包的列表: + +```sh +wl +``` + +在安装**mytonctrl**期间,将创建**validator_wallet_001**钱包: + +![wallet list](/img/docs/nodes-validator/manual-ubuntu_mytonctrl-wl_ru.png) + + +### 激活钱包 + +1. 向钱包发送必要数量的coins并激活它。 + +最近(_在2023年底_),大致数字为最低质押代币约__340K TON__,最高约__1M TON__。 + +查看[tonscan.com](https://tonscan.com/validation)以了解所需coins数量。 + +阅读更多关于[如何计算最大和最小质押代币](/participate/network-maintenance/staking-incentives#values-of-stakes-max-effective-stake)。 + +2. 使用`vas`命令显示转账历史: + +```sh +vas +``` + +3. 使用`aw`命令激活钱包 + +```sh +aw [wallet name] +``` + +![account history](/img/docs/nodes-validator/manual-ubuntu_mytonctrl-vas-aw_ru.png) + +### 您的验证者现已准备就绪 + +**mytoncore** 将自动加入选举。它将钱包余额分为两部分,并使用它们作为质押代币参加选举。您也可以手动设置质押代币大小: + +```sh +set stake 50000 +``` + +`set stake 50000` — 这将质押代币大小设置为50k coins。如果质押代币被接受并且我们的节点成为验证者,那么质押代币只能在第二次选举中提取(根据选民的规则)。 + +![setting stake](/img/docs/nodes-validator/manual-ubuntu_mytonctrl-set_ru.png) + +## 启用Liteserver模式 + +在全节点中激活端点时,节点将承担**Liteserver**的角色。这种节点类型可以处理并响应来自轻客户端的请求,允许与TON区块链无缝互动。 + +### 硬件要求 + +与[验证者](/participate/run-nodes/full-node#hardware-requirements)相比,liteserver模式需要较少的资源。然而,仍建议使用功能强大的机器运行liteserver。 + +- 至少16核CPU +- 至少64 GB RAM +- 至少1TB GB NVMe SSD _或者_ 预置32+k IOPS存储 +- 1 Gbit/s网络连接 +- 高峰期每月16 TB流量 +- 固定公网IP地址 + +#### 推荐供应商 + +请随意使用[推荐供应商](/participate/run-nodes/full-node#recommended-providers)部分中列出的云提供商。 + +Hetzner和OVH被禁止运行验证者,但您可以使用它们运行liteserver: + +- __Hetzner__:EX101, AX102 +- __OVH__:RISE-4 + +### 安装liteserver + +1. 完成前面的步骤以安装[MyTonCtrl](/participate/run-nodes/full-node#step-by-step-instructions)。 + +2. 创建配置文件 + +```bash +MyTonCtrl> installer clcf + +本地配置文件已创建:/usr/bin/ton/local.config.json +``` + +3. 这个文件将帮助您连接到您的liteserver。将位于指定路径的配置文件复制到您的目录以保存它。 + +```bash +cp /usr/bin/ton/local.config.json ~/config.json +``` + +4. 在您的本地机器上创建一个空的`config.json`文件。 + + +5. 将控制台中的内容复制到您本地机器的`config.json`文件。 + +```bash +cat ~/config.json +``` + +### 检查防火墙设置 + +首先,验证您`config.json`文件中指定的Liteserver端口。这个端口会随着每次新安装`MyTonCtrl`而改变。它位于`port`字段: + +```json +{ + ... + "liteservers": [ + { + "ip": 1605600994, + "port": LITESERVER_PORT + ... + } + ] +} +``` + +如果您使用的是云提供商,您需要在防火墙设置中打开此端口。例如,如果您使用的是AWS,您需要在[安全组](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html)中打开端口。 + +以下是在裸机服务器防火墙中打开端口的示例。 + +#### 在防火墙中打开端口 + +我们将使用`ufw`实用程序([速查表](https://www.cyberciti.biz/faq/ufw-allow-incoming-ssh-connections-from-a-specific-ip-address-subnet-on-ubuntu-debian/))。您可以使用您喜欢的一个。 + +1. 如果未安装`ufw`,请安装: + +```bash +sudo apt update +sudo apt install ufw +``` + +2. 允许ssh连接: + +```bash +sudo ufw allow ssh +``` + +3. 允许`config.json`文件中指定的端口: + +```bash +sudo ufw allow +``` + +4. 检查防火墙状态。运行此命令后,您可以通过检查UFW状态来验证规则是否已成功添加: + +```bash +sudo ufw status +``` + +5. 启用防火墙: + +```bash +sudo ufw enable +``` + +这样,您就可以在服务器的防火墙设置中打开端口。 + +### 与Liteserver互动 + +0. 在您的机器上创建一个空项目,并将`config.js`粘贴到项目目录中。 + +1. 安装库。 + + + + + ```bash + npm i --save ton-core ton-lite-client + ``` + + + + + ```bash + pip install pytonlib + ``` + + + + + ```bash + go get github.com/xssnick/tonutils-go + go get github.com/xssnick/tonutils-go/liteclient + go get github.com/xssnick/tonutils-go/ton + ``` + + + +2. 初始化客户端并请求主链信息以确保liteserver正在运行。 + + + + + 将项目类型更改为`module`在您的`package.json`文件: + + ```js + import { LiteSingleEngine } from 'ton-lite-client/dist/engines/single.js' + import { LiteRoundRobinEngine } from 'ton-lite-client/dist/engines/roundRobin.js' + import { LiteClient } from 'ton-lite-client/dist/client.js' + import config from './config.json' assert {type: 'json'}; + + + function intToIP(int ) { + var part1 = int & 255; + var part2 = ((int >> 8) & 255); + var part3 = ((int >> 16) & 255); + var part4 = ((int >> 24) & 255); + + return part4 + "." + part3 + "." + part2 + "." + part1; + } + + let server = config.liteservers[0]; + + async function main() { + const engines = []; + engines.push(new LiteSingleEngine({ + host: `tcp://${intToIP(server.ip)}:${server.port}`, + publicKey: Buffer.from(server.id.key, 'base64'), + })); + + const engine = new LiteRoundRobinEngine(engines); + const client = new LiteClient({ engine }); + const master = await client.getMasterchainInfo() + console.log('master', master) + + } + + main() + + ``` + + + + + ```python + import asyncio + from pytonlib import TonlibClient + from pathlib import Path + import json + + + async def get_client() -> TonlibClient: + with open('config.json', 'r') as f: + config = json.loads(f.read()) + + keystore_dir = '/tmp/ton_keystore' + Path(keystore_dir).mkdir(parents=True, exist_ok=True) + + client = TonlibClient(ls_index=0, config=config, keystore=keystore_dir, tonlib_timeout=10) + await client.init() + + return client + + + async def test_client(): + client = await get_client() + + print(await client.get_masterchain_info()) + + await client.close() + + + if __name__ == '__main__': + asyncio.run(test_client()) + ``` + + + + + ```go + package main + + import ( + "context" + "encoding/json" + "io/ioutil" + "log" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/ton" + ) + + func main() { + client := liteclient.NewConnectionPool() + + content, err := ioutil.ReadFile("./config.json") + if err != nil { + log.Fatal("Error when opening file: ", err) + } + + config := liteclient.GlobalConfig{} + err = json.Unmarshal(content, &config) + if err != nil { + log.Fatal("Error during Unmarshal(): ", err) + } + + err = client.AddConnectionsFromConfig(context.Background(), &config) + if err != nil { + log.Fatalln("connection err: ", err.Error()) + return + } + + // initialize ton API lite connection wrapper + api := ton.NewAPIClient(client) + + master, err := api.GetMasterchainInfo(context.Background()) + if err != nil { + log.Fatalln("get masterchain info err: ", err.Error()) + return + } + + log.Println(master) +} + + ``` + + + +3. 现在您可以与您自己的liteserver互动。 + +### 参阅 + +* [[YouTube]教程如何启动liteserver](https://youtu.be/p5zPMkSZzPc) + + +## 小贴士和技巧 + +### 可用命令列表 + +- 您可以使用`help`获取可用命令列表: + +![Help command](/img/docs/full-node/help.jpg) + +### 检查日志 + +- 要检查**mytoncrl**日志,请打开本地用户的`~/.local/share/mytoncore/mytoncore.log`,或者Root的`/usr/local/bin/mytoncore/mytoncore.log`。 + +![logs](/img/docs/nodes-validator/manual-ubuntu_mytoncore-log.png) + + +## 故障排除 + +本节包含有关运行节点的最常见问题的答案。 + +### Error 651是什么意思? + +`[Error : 651 : no nodes]`表示您的节点无法在TON区块链内找到另一个节点。 + +有时,这个过程可能需要长达24小时。但是,如果您已经连续几天收到此错误,这意味着您的节点无法通过当前网络连接进行同步。 + +:::tip 解决方案 +您需要检查防火墙设置,包括任何NAT设置(如果存在)。 + +它应该允许在一个特定端口上的传入连接和从任何端口的传出连接。 +::: + +### 验证者控制台未设置 + +如果您遇到 `Validator console is not settings` 错误,这表示您正在以一个非安装时使用的用户身份运行 `MyTonCtrl`。 + +:::tip 解决方案 +以您安装它的用户(非root sudo用户)运行`MyTonCtrl`。 + +```bash +sudo mytonctrl +``` +::: + +### "block is not applied"是什么意思? + +__问:__ 有时我们对各种请求得到`block is not applied`或`block is not ready`的回应 - 这正常吗? + +__答:__ 这是正常的,通常这意味着您试图检索尚未到达您请求的节点的区块。 + +__问:__ 如果比较频繁出现,是否意味着某处存在问题? + +__答:__ 不。您需要检查mytonctrl中的“Local validator out of sync”值。如果它少于60秒,那么一切正常。 + +但是您需要记住,节点不断同步。有时,您可能会试图接收尚未到达您请求的节点的区块。 + +您需要稍作延迟重复请求。 + +### 使用-d标志的同步问题 + +如果您在使用`-d`标志下载`MyTonCtrl`后遇到`out of sync`等于时间戳的问题,可能是转储没有正确安装(或已经过时)。 + +:::tip 解决方案 +推荐的解决方案是再次使用新转储重新安装`MyTonCtrl`。 +::: + +如果同步花费异常长的时间,可能是转储出现了问题。请[联系我们](https://t.me/alexgton)寻求帮助。 + +请以您安装的用户身份运行`mytonctrl`。 + + +### 命令\<...>在3秒后超时的错误 + +此错误意味着本地节点尚未同步(out of sync小于20秒)并且正在使用公共节点。 +公共节点不总是响应,并最终导致超时错误。 + +:::tip 解决方案 +解决问题的办法是等待本地节点同步或多次执行相同命令。 +::: + +### Status命令未显示本地节点部分 + +![](\img\docs\full-node\local-validator-status-absent.png) + +如果节点状态中没有本地节点部分,通常意味着安装过程中出了问题,跳过了创建/分配验证者钱包的步骤。 +还要检查是否指定了验证者钱包。 + +直接检查以下内容: + +```bash +mytonctrl> get validatorWalletName +``` + +如果validatorWalletName为空,则执行以下操作: + +```bash +mytonctrl> set validatorWalletName validator_wallet_001 +``` + + +### 在新服务器上转移验证者 + +:::info +将所有密钥和配置从旧节点转移到工作节点并启动它。如果新节点出现问题,原来的节点仍然保留着所有已设置的内容。 +::: + +最好的方法(当暂时不验证的罚款很小时,可以不间断地进行): + +1. 在新服务器上使用`mytonctrl`进行干净的安装,并等待一切同步。 + +2. 在两台机器上停止`mytoncore`和验证者`服务`,在源和新的上进行备份: + +- 2.1 `/usr/local/bin/mytoncore/...` +- 2.2 `/home/${user}/.local/share/mytoncore/...` +- 2.3 `/var/ton-work/db/config.json` +- 2.4 `/var/ton-work/db/config.json.backup` +- 2.5 `/var/ton-work/db/keyring` +- 2.6 `/var/ton-work/keys` + +3. 从源头转移到新的(替换内容): + +- 3.1 `/usr/local/bin/mytoncore/...` +- 3.2 `/home/${user}/.local/share/mytoncore/...` +- 3.3 `/var/ton-work/db/config.json` +- 3.4 `/var/ton-work/db/keyring` +- 3.5 `/var/ton-work/keys` + +4. 在`/var/ton-work/db/config.json`中编辑`addrs[0].ip`为当前的IP地址,安装后可在备份`/ton-work/db/config.json.backup`中看到 + +5. 检查所有替换文件的权限 + +6. 在新的服务器上启动`mytoncore`和`validator`服务,检查节点是否同步并验证 + +7. 在新的服务器上进行备份: + +```bash +cp var/ton-work/db/config.json var/ton-work/db/config.json.backup +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/liteserver.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/liteserver.mdx new file mode 100644 index 0000000000..787529f0bc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/liteserver.mdx @@ -0,0 +1,7 @@ +# 运行Liteserver + +:::info 已弃用 +此文章已与运行全节点文章合并。 +::: + +[启用Liteserver模式](/participate/run-nodes/full-node#enable-liteserver-mode) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/running-a-local-ton.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/running-a-local-ton.md new file mode 100644 index 0000000000..c5ca9f1c53 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/running-a-local-ton.md @@ -0,0 +1,14 @@ +# 运行本地TON + +## MyLocalTon + +使用 **MyLocalTon**,您甚至可以在笔记本电脑上运行自己的TON区块链。 + +![MyLocalTon](/img/docs/mylocalton.jpeg) + +## 资源 + +MyLocalTon 具有方便的用户界面,并且兼容多个平台。 + +- [MyLocalTon 二进制文件](https://github.com/neodiX42/MyLocalTon/releases) +- [MyLocalTon 源代码](https://github.com/neodiX42/MyLocalTon) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/staking-with-nominator-pools.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/staking-with-nominator-pools.md new file mode 100644 index 0000000000..11df3bcaa2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/nodes/running-nodes/staking-with-nominator-pools.md @@ -0,0 +1,45 @@ +# 使用提名者池进行质押 + +## 概述 + +使用TON智能合约,您可以实现任何您想要的质押和存款机制。 + +然而,在TON区块链中存在“原生质押”——您可以将Toncoin借给验证者进行质押,并分享验证的奖励。 + +将Toncoin借给验证者的人称为**提名者**。 + +一个称为**提名者池**的智能合约,为一个或多个提名者提供了将Toncoin贷给验证者质押的能力,并确保验证者只能用该Toncoin进行验证。此外,智能合约保证了奖励的分配。 + +## 验证者与提名者 + +如果您熟悉加密货币,您一定听说过**验证者**和**提名者**。这些词经常出现在加密货币相关渠道中(我们的频道也不例外)。现在,是时候了解一下它们是什么了——区块链的两个主要参与者。 + +### 验证者 + +首先,让我们谈谈验证者。验证者是网络节点,通过验证(或确认)建议的区块并将其记录在区块链上来帮助维持区块链的运行。 + +要成为验证者,您必须满足两个要求:拥有高性能服务器并获得大量的TON(600,000)以进行质押。在撰写本文时,TON上有227个验证者。 + +### 提名者 + +:::info +提名者池的新版本已发布,更多信息请阅读单一提名者和归属合约页面。 +::: + +很明显,并不是每个人都能负担得起在余额上拥有100,000s的Toncoin——这就是提名者发挥作用的地方。简单来说,提名者是将其TON借给验证者的用户。每次验证者通过验证区块获得奖励时,奖励就会在参与者之间分配。 + +不久前,Ton Whales在TON上运行了第一个质押池,最低存款为50 TON。后来,TON基金会推出了第一个开放的提名者池。现在,用户可以以**10,000 TON**开始,以完全去中心化的方式质押Toncoin。 + +*来自[TON社区帖子](https://t.me/toncoin/543)。* + +## 如何参与? + +- [TON提名者池列表](https://tonvalidators.org/) + +## 源代码 + +- [提名者池智能合约源代码](https://github.com/ton-blockchain/nominator-pool) + +:::info +提名者的理论在[TON白皮书](https://docs.ton.org/ton.pdf)中有描述,章节2.6.3, 2.6.25。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/get-methods.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/get-methods.md new file mode 100644 index 0000000000..1d4cc56bcf --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/get-methods.md @@ -0,0 +1,371 @@ +# Get 方法 + +:::note +在继续之前,建议读者对[FunC编程语言](/develop/func/overview)和TON区块链上的[智能合约开发](/develop/smart-contracts)有基本的了解。这将有助于您更有效地理解这里提供的信息。 +::: + +## 介绍 + +Get方法是智能合约中用于查询特定数据的特殊函数。它们的执行不需要任何费用,并且在区块链之外进行。 + +这些函数对于大多数智能合约来说都非常常见。例如,默认的[钱包合约](/participate/wallets/contracts)有几个get方法,如`seqno()`、`get_subwallet_id()`和`get_public_key()`。它们被钱包、SDK和API用来获取有关钱包的数据。 + +## Get 方法的设计模式 + +### 基本 get 方法设计模式 + +1. **单一数据点检索**:一种基本设计模式是创建返回合约状态中单个数据点的方法。这些方法没有参数,并返回单个值。 + + 示例: + + ```func + int get_balance() method_id { + return get_data().begin_parse().preload_uint(64); + } + ``` + +2. **聚合数据检索**:另一种常见的模式是创建一次返回合约状态中多个数据点的方法。这通常在某些数据点一起使用时采用。这些在[Jetton](#jettons)和[NFT](#nfts)合约中非常常见。 + + 示例: + + ```func + (int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); + } + ``` + +### 高级 get 方法设计模式 + +1. **计算数据检索**:在某些情况下,需要检索的数据并不直接存储在合约的状态中,而是根据状态和输入参数计算得出的。 + + 示例: + + ```func + slice get_wallet_address(slice owner_address) method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); + } + ``` + +2. **条件数据检索**:有时需要检索的数据取决于某些条件,如当前时间。 + + 示例: + + ```func + (int) get_ready_to_be_used() method_id { + int ready? = now() >= 1686459600; + return ready?; + } + ``` + +## 最常见的 get 方法 + +### 标准钱包 + +#### seqno() + +```func +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} +``` + +返回特定钱包中交易的序列号。这个方法主要用于[重放保护](/develop/smart-contracts/tutorials/wallet#replay-protection---seqno)。 + +#### get_subwallet_id() + +```func +int get_subwallet_id() method_id { + return get_data().begin_parse().skip_bits(32).preload_uint(32); +} +``` + +- [什么是Subwallet ID?](/develop/smart-contracts/tutorials/wallet#what-is-subwallet-id) + +#### get_public_key() + +```func +int get_public_key() method_id { + var cs = get_data().begin_parse().skip_bits(64); + return cs.preload_uint(256); +} +``` + +检索与钱包关联的公钥。 + +### Jettons + +#### get_wallet_data() + +```func +(int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); +} +``` + +这个方法返回与Jetton钱包相关的完整数据集: + +- (int) 余额 +- (slice) 持有者地址 +- (slice) Jetton主合约地址 +- (cell) Jetton钱包代码 + +#### get_jetton_data() + +```func +(int, int, slice, cell, cell) get_jetton_data() method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return (total_supply, -1, admin_address, content, jetton_wallet_code); +} +``` + +返回Jetton主合约的数据,包括其总供应量、管理员地址、Jetton内容和钱包代码。 + +#### get_wallet_address(slice owner_address) + +```func +slice get_wallet_address(slice owner_address) method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); +} +``` + +根据所有者的地址,此方法计算并返回所有者的Jetton钱包合约地址。 + +### NFTs + +#### get_nft_data() + +```func +(int, int, slice, slice, cell) get_nft_data() method_id { + (int init?, int index, slice collection_address, slice owner_address, cell content) = load_data(); + return (init?, index, collection_address, owner_address, content); +} +``` + +返回与非同质化代币相关的数据,包括是否已初始化、在集合中的索引、集合地址、所有者地址和个体内容。 + +#### get_collection_data() + +```func +(int, cell, slice) get_collection_data() method_id { + var (owner_address, next_item_index, content, _, _) = load_data(); + slice cs = content.begin_parse(); + return (next_item_index, cs~load_ref(), owner_address); +} +``` + +返回NFT集合的数据,包括下一个要铸造的项目索引、集合内容和所有者地址。 + +#### get_nft_address_by_index(int index) + +```func +slice get_nft_address_by_index(int index) method_id { + var (_, _, _, nft_item_code, _) = load_data(); + cell state_init = calculate_nft_item_state_init(index, nft_item_code); + return calculate_nft_item_address(workchain(), state_init); +} +``` + +给定索引,此方法计算并返回该集合的相应NFT项目合约地址。 + +#### royalty_params() + +```func +(int, int, slice) royalty_params() method_id { + var (_, _, _, _, royalty) = load_data(); + slice rs = royalty.begin_parse(); + return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); +} +``` + +获取NFT的版税参数。这些参数包括原始创作者在NFT被出售时应支付的版税百分比。 + +#### get_nft_content(int index, cell individual_nft_content) + +```func +cell get_nft_content(int index, cell individual_nft_content) method_id { + var (_, _, content, _, _) = load_data(); + slice cs = content.begin_parse(); + cs~load_ref(); + slice common_content = cs~load_ref().begin_parse(); + return (begin_cell() + .store_uint(1, 8) ;; offchain tag + .store_slice(common_content) + .store_ref(individual_nft_content) + .end_cell()); +} +``` + +给定索引和[个体NFT内容](#get_nft_data),此方法获取并返回NFT的常见内容和个体内容。 + +## 如何使用 get 方法 + +### 在流行的浏览器上调用 get 方法 + +#### Tonviewer + +您可以在页面底部的"Methods"标签中调用get方法。 + +- + +#### + +您可以在"Get methods"标签中调用get方法。 + +- + +### 从代码中调用 get 方法 + +我们将使用以下Javascript库和工具来提供以下示例: + +- [ton](https://github.com/ton-core/ton)库 +- + +假设有一个合约,其中有以下get方法: + +```func +(int) get_total() method_id { + return get_data().begin_parse().preload_uint(32); ;; load and return the 32-bit number from the data +} +``` + +这个方法从合约数据中返回一个单一的数字。 + +下面的代码片段可以用来在已知地址的某个合约上调用这个get方法: + +```ts +import { Address, TonClient } from 'ton'; + +async function main() { + // Create Client + const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + }); + + // Call get method + const result = await client.runMethod( + Address.parse('EQD4eA1SdQOivBbTczzElFmfiKu4SXNL4S29TReQwzzr_70k'), + 'get_total' + ); + const total = result.stack.readNumber(); + console.log('Total:', total); +} + +main(); +``` + +这段代码将输出`Total: 123`。数字可能会有所不同,这只是一个例子。 + +### 测试 get 方法 + +对于我们创建的智能合约的测试,我们可以使用[沙盒](https://github.com/ton-community/sandbox),它默认安装在新的Blueprint项目中。 + +首先,您需要在合约包装器中添加一个特殊方法,以执行get方法并返回类型化的结果。假设您的合约叫做_Counter_,您已经实现了更新存储数字的方法。那打开`wrappers/Counter.ts`并添加以下方法: + +```ts +async getTotal(provider: ContractProvider) { + const result = (await provider.get('get_total', [])).stack; + return result.readNumber(); +} +``` + +它执行get方法并获取结果堆栈。堆栈在get方法的情况下基本上就是它所返回的东西。在这个片段中,我们从中读取一个单一的数字。在更复杂的情况下,一次返回多个值时,您可以多次调用`readSomething`类型的方法来从堆栈中解析整个执行结果。 + +最后,我们可以在测试中使用这个方法。导航到`tests/Counter.spec.ts`并添加一个新的测试: + +```ts +it('should return correct number from get method', async () => { + const caller = await blockchain.treasury('caller'); + await counter.sendNumber(caller.getSender(), toNano('0.01'), 123); + expect(await counter.getTotal()).toEqual(123); +}); +``` + +通过在终端运行`npx blueprint test`来检查,如果您做得正确,这个测试应该被标记为通过! + +## 从其他合约调用 get 方法 + +与直觉相反,从其他合约调用get方法在链上是不可能的,主要是由于区块链技术的性质和需要达成共识。 + +首先,从另一个分片链获取数据可能需要时间。这种延迟可能很容易中断合约执行流程,因为区块链操作需要以确定和及时的方式执行。 + +其次,达成验证者之间的共识将是有问题的。为了验证交易的正确性,验证者也需要调用相同的get方法。然而,如果目标合约的状态在这些多次调用之间发生变化,验证者可能会得到不同的交易结果版本。 + +最后,TON中的智能合约被设计为纯函数:对于相同的输入,它们总是产生相同的输出。这一原则使得消息处理过程中的共识变得简单直接。引入运行时获取任意动态变化数据的能力将打破这种确定性属性。 + +### 对开发者的影响 + +这些限制意味着一个合约不能通过其get方法直接访问另一个合约的状态。无法在确定性的合约流程中纳入实时外部数据可能看起来有限制。然而,正是这些约束确保了区块链技术的完整性和可靠性。 + +### 解决方案和变通方法 + +在TON区块链中,智能合约通过消息进行通信,而不是直接从另一个合约调用方法。向目标合约发送请求执行特定方法的消息。这些请求通常以特殊的[操作码](/develop/smart-contracts/guidelines/internal-messages)开头。 + +被设计为接受这些请求的合约将执行所需的方法,并在单独的消息中发送结果。虽然这可能看起来很复杂,但它实际上简化了合约之间的通信,并提高了区块链网络的可扩展性和性能。 + +这种消息传递机制是TON区块链运作的一个整体部分,为可扩展网络增长铺平了道路,而无需在分片之间进行广泛的同步。 + +为了有效的合约间通信,至关重要的是您的合约被设计为正确接受和响应请求。这包括指定可以在链上调用以返回响应的方法。 + +让我们考虑一个简单的例子: + +```func +#include "imports/stdlib.fc"; + +int get_total() method_id { + return get_data().begin_parse().preload_uint(32); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_bits() < 32) { + return (); + } + + slice cs = in_msg_full.begin_parse(); + cs~skip_bits(4); + slice sender = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); ;; load the operation code + + if (op == 1) { ;; increase and update the number + int number = in_msg_body~load_uint(32); + int total = get_total(); + total += number; + set_data(begin_cell().store_uint(total, 32).end_cell()); + } + elseif (op == 2) { ;; query the number + int total = get_total(); + send_raw_message(begin_cell() + .store_uint(0x18, 6) + .store_slice(sender) + .store_coins(0) + .store_uint(0, 107) ;; default message headers (see sending messages page) + .store_uint(3, 32) ;; response operation code + .store_uint(total, 32) ;; the requested number + .end_cell(), 64); + } +} +``` + +在这个示例中,合约接收并处理内部消息,通过解读操作码、执行特定方法和适当地返回响应: + +- 操作码`1`表示更新合约数据中的数字的请求。 +- 操作码`2`表示从合约数据中查询数字的请求。 +- 操作码`3`用于响应消息,发起调用的智能合约必须处理以接收结果。 + +为了简单起见,我们只是使用了简单的小数字1、2和3作为操作码。但对于真实项目,请根据标准设置它们: + +- [用于操作码的CRC32哈希](/develop/data-formats/crc32) + +## 常见陷阱及如何避免 + +1. **误用 get 方法**:如前所述,get方法被设计用于从合约的状态返回数据,不是用来更改合约的状态。尝试在get方法中更改合约的状态实际上不会这样做。 + +2. **忽略返回类型**:每个get方法都应该有一个明确定义的返回类型,与检索的数据相匹配。如果一个方法预期返回特定类型的数据,请确保该方法的所有路径都返回此类型。避免使用不一致的返回类型,因为这可能导致与合约交互时出现错误和困难。 + +3. **假设跨合约调用**:一个常见的误解是可以从链上的其他合约调用get方法。然而,如我们所讨论的,这是不可能的,因为区块链技术的性质和需要达成共识的需求。始终记住,get方法旨在链下使用,合约间的链上交互是通过内部消息完成的。 + +## 结论 + +Get方法是TON区块链中查询智能合约数据的重要工具。尽管它们有其局限性,但了解这些限制并知道如何克服它们是有效使用智能合约中的get方法的关键。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/guidelines.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/guidelines.mdx new file mode 100644 index 0000000000..22917696a0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/guidelines.mdx @@ -0,0 +1,46 @@ +import Button from '@site/src/components/button' + +# 概览 + +本页面收集了一些建议和最佳实践,可在开发TON区块链上的新智能合约时遵循。 + +- [内部消息](/develop/smart-contracts/guidelines/internal-messages) +- [外部消息](/develop/smart-contracts/guidelines/external-messages) +- [使用不可弹回消息](/develop/smart-contracts/guidelines/non-bouncable-messages) +- [Get方法](/develop/smart-contracts/guidelines/get-methods) +- ["accept_message"作用](/develop/smart-contracts/guidelines/accept) +- [支付处理查询和发送响应的费用](/develop/smart-contracts/guidelines/processing) +- [如何及为何对您的TON智能合约进行分片。研究TON的Jettons结构](https://blog.ton.org/how-to-shard-your-ton-smart-contract-and-why-studying-the-anatomy-of-tons-jettons) +- [TON Keeper创始人Oleg Andreev和Oleg Illarionov关于TON jettons的谈话](https://www.youtube.com/watch?v=oEO29KmOpv4) + +## TON 课程:合约开发 + +[TON区块链课程](https://stepik.org/course/201638/)是关于TON区块链开发的全面指南。 + +- 第2模块专注于**TVM、交易、可扩展性和商业案例**。 +- 第3模块专注于**智能合约开发的全过程**。 + + + + + + + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/compilation-instructions.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/compilation-instructions.md new file mode 100644 index 0000000000..b8eff00459 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/compilation-instructions.md @@ -0,0 +1,232 @@ +# 从源代码编译 + +您可以[在此处](/develop/smart-contracts/environment/installation#1-download)下载预构建的二进制文件。 + +如果您仍然想自己编译源代码,请按照以下说明操作。 + +:::caution +This is a simplified quick build guide. + +如果您是为生产而不是家庭使用而构建,最好使用[自动构建脚本](https://github.com/ton-blockchain/ton/tree/master/.github/workflows)。 +::: + +## 通用 + +该软件可能在大多数Linux系统上都能正确编译和工作。它应该适用于macOS甚至Windows。 + +1. 在GitHub库 https://github.com/ton-blockchain/ton/ 下载TON区块链源代码的最新版本: + +```bash +git clone --recurse-submodules https://github.com/ton-blockchain/ton.git +``` + +2. 安装最新版本的: + + - `make` + - `cmake` 版本 3.0.2 或更高 + - `g++` 或 `clang`(或适用于您的操作系统的另一种C++14兼容编译器)。 + - OpenSSL(包括C头文件)版本 1.1.1 或更高 + - `build-essential`, `zlib1g-dev`, `gperf`, `libreadline-dev`, `ccache`, `libmicrohttpd-dev`, `pkg-config`, `libsodium-dev`, `libsecp256k1-dev` + + 在Ubuntu上: + +```bash +apt update +sudo apt install build-essential cmake clang openssl libssl-dev zlib1g-dev gperf libreadline-dev ccache libmicrohttpd-dev pkg-config libsodium-dev libsecp256k1-dev +``` + +3. 假设您已将源代码树获取到目录`~/ton`,其中`~`是您的主目录,并且您已创建一个空目录`~/ton-build`: + +```bash +mkdir ton-build +``` + +然后在Linux或MacOS的终端中运行以下命令: + +```bash +cd ton-build +export CC=clang +export CXX=clang++ +cmake -DCMAKE_BUILD_TYPE=Release ../ton && cmake --build . -j$(nproc) +``` + +:::warning +On MacOS Intel before next step we need maybe install `openssl@3` with `brew` or just link the lib: + +```zsh +brew install openssl@3 ninja libmicrohttpd pkg-config +``` + +然后需要检查`/usr/local/opt`: + +```zsh +ls /usr/local/opt +``` + +找到`openssl@3`库并导出本地变量: + +```zsh +export OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3 +``` + +::: + +:::tip +如果您在内存较小的计算机上编译(例如,1GB),请不要忘记[创建交换分区](/develop/howto/compile-swap)。 +::: + +## 下载全局配置 + +对于像轻客户端这样的工具,您需要下载全局网络配置。 + +从 https://ton-blockchain.github.io/global.config.json 下载主网的最新配置文件: + +```bash +wget https://ton-blockchain.github.io/global.config.json +``` + +或从 https://ton-blockchain.github.io/testnet-global.config.json 下载测试网的配置文件: + +```bash +wget https://ton-blockchain.github.io/testnet-global.config.json +``` + +## 轻客户端 + +要构建轻客户端,请执行[通用部分](/develop/howto/compile#common),[下载配置](/develop/howto/compile#download-global-config),然后执行: + +```bash +cmake --build . --target lite-client +``` + +使用配置运行轻客户端: + +```bash +./lite-client/lite-client -C global.config.json +``` + +如果一切安装成功,轻客户端将连接到一个特殊的服务器(TON区块链网络的完整节点)并向服务器发送一些查询。如果您向客户端指示一个可写的“数据库”目录作为额外参数,它将下载并保存与最新的主链块相对应的块和状态: + +```bash +./lite-client/lite-client -C global.config.json -D ~/ton-db-dir +``` + +通过在轻客户端中输入`help`可以获得基本帮助信息。输入`quit`或按`Ctrl-C`退出。 + +## FunC + +要从源代码构建FunC编译器,请执行上面描述的[通用部分](/develop/howto/compile#common),然后: + +```bash +cmake --build . --target func +``` + +要编译FunC智能合约: + +```bash +func -o output.fif -SPA source0.fc source1.fc ... +``` + +## Fift + +要从源代码构建Fift编译器,请执行上面描述的[通用部分](/develop/howto/compile#common),然后: + +```bash +cmake --build . --target fift +``` + +要运行Fift脚本: + +```bash +fift -s script.fif script_param0 script_param1 .. +``` + +## Tonlib-cli + +要构建tonlib-cli,请执行[通用部分](/develop/howto/compile#common),[下载配置](/develop/howto/compile#download-global-config),然后执行: + +```bash +cmake --build . --target tonlib-cli +``` + +使用配置运行tonlib-cli: + +```bash +./tonlib/tonlib-cli -C global.config.json +``` + +通过在tonlib-cli中输入`help`可以获得基本帮助信息。输入`quit`或按`Ctrl-C`退出。 + +## RLDP-HTTP-Proxy + +要构建rldp-http-proxy,请执行[通用部分](/develop/howto/compile#common),[下载配置](/develop/howto/compile#download-global-config),然后执行: + +```bash +cmake --build . --target rldp-http-proxy +``` + +代理二进制文件将位于: + +```bash +rldp-http-proxy/rldp-http-proxy +``` + +## generate-random-id + +要构建generate-random-id,请执行[通用部分](/develop/howto/compile#common),然后执行: + +```bash +cmake --build . --target generate-random-id +``` + +二进制文件将位于: + +```bash +utils/generate-random-id +``` + +## storage-daemon + +要构建storage-daemon和storage-daemon-cli,请执行[通用部分](/develop/howto/compile#common),然后执行: + +```bash +cmake --build . --target storage-daemon storage-daemon-cli +``` + +二进制文件将位于: + +```bash +storage/storage-daemon/ +``` + +# 编译旧版本的TON + +TON版本发布:https://github.com/ton-blockchain/ton/tags + +```bash +git clone https://github.com/ton-blockchain/ton.git +cd ton +# git checkout for example checkout func-0.2.0 +git checkout func-0.2.0 +git submodule update --init --recursive +cd .. +mkdir ton-build +cd ton-build +cmake ../ton +# build func 0.2.0 +cmake --build . --target func +``` + +## 在Apple M1上编译旧版本: + +TON从2022年6月11日开始支持Apple M1([添加apple m1支持 (#401)](https://github.com/ton-blockchain/ton/commit/c00302ced4bc4bf1ee0efd672e7c91e457652430)提交)。 + +在 Apple M1 上编译 TON 旧版本: + +1. 将RocksDb子模块更新到6.27.3 + ```bash + cd ton/third-party/rocksdb/ + git checkout fcf3d75f3f022a6a55ff1222d6b06f8518d38c7c + ``` + +2. 用https://github.com/ton-blockchain/ton/blob/c00302ced4bc4bf1ee0efd672e7c91e457652430/CMakeLists.txt 替换根目录的`CMakeLists.txt` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/instructions-low-memory.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/instructions-low-memory.md new file mode 100644 index 0000000000..99dd6316f5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/compile/instructions-low-memory.md @@ -0,0 +1,50 @@ +# 在低内存机器上编译TON + +:::caution +本节描述了与TON进行低层级交互的说明和手册。 +::: + +在内存较小(小于1GB)的计算机上创建交换分区以编译TON。 + +## 必要条件 + +在Linux系统中进行C++编译时出现以下错误,导致编译中止: + +``` +C++: fatal error: Killed signal terminated program cc1plus compilation terminated. +``` + +## 解决方案 + +这是由于内存不足引起的,通过创建交换分区来解决。 + +```bash +# Create the partition path +sudo mkdir -p /var/cache/swap/ +# Set the size of the partition +# bs=64M is the block size, count=64 is the number of blocks, so the swap space size is bs*count=4096MB=4GB +sudo dd if=/dev/zero of=/var/cache/swap/swap0 bs=64M count=64 +# Set permissions for this directory +sudo chmod 0600 /var/cache/swap/swap0 +# Create the SWAP file +sudo mkswap /var/cache/swap/swap0 +# Activate the SWAP file +sudo swapon /var/cache/swap/swap0 +# Check if SWAP information is correct +sudo swapon -s +``` + +删除交换分区的命令: + +```bash +sudo swapoff /var/cache/swap/swap0 +sudo rm /var/cache/swap/swap0 +``` + +释放空间命令: + +```bash +sudo swapoff -a +#Detailed usage: swapoff --help +#View current memory usage: --swapoff: free -m +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig-js.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig-js.md new file mode 100644 index 0000000000..3c8e5d17e2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig-js.md @@ -0,0 +1,197 @@ +--- +description: 在本指南结束时,您将部署多重签名钱包并使用ton库发送一些交易 +--- + +# 使用 TypeScript 与多重签名钱包交互 + +## 引言 + +如果您不知道TON中的多重签名钱包是什么,可以在[此处](/develop/smart-contracts/tutorials/multisig)查看。 + +按照以下步骤操作,您将学习如何: + +- 创建并部署多重签名钱包 +- 使用该钱包创建、签名并发送交易 + +我们将创建一个TypeScript项目,并使用[ton](https://www.npmjs.com/package/ton)库,因此您需要首先安装它。我们还将使用[ton-access](https://www.orbs.com/ton-access/): + +```bash +yarn add typescript @types/node ton ton-crypto ton-core buffer @orbs-network/ton-access +yarn tsc --init -t es2022 +``` + +本指南的完整代码可在此处查看: + +- https://github.com/Gusarich/multisig-ts-example + +## 创建并部署多重签名钱包 + +首先创建一个源文件,例如`main.ts`。在您喜欢的代码编辑器中打开它,然后按照本指南操作! + +首先我们需要导入所有重要的东西 + +```js +import { Address, beginCell, MessageRelaxed, toNano, TonClient, WalletContractV4, MultisigWallet, MultisigOrder, MultisigOrderBuilder } from "ton"; +import { KeyPair, mnemonicToPrivateKey } from 'ton-crypto'; +import { getHttpEndpoint } from "@orbs-network/ton-access"; +``` + +创建`TonClient`实例: + +```js +const endpoint = await getHttpEndpoint(); +const client = new TonClient({ endpoint }); +``` + +然后我们需要一些密钥对来操作: + +```js +let keyPairs: KeyPair[] = []; + +let mnemonics[] = [ + ['orbit', 'feature', ...], //this should be the seed phrase of 24 words + ['sing', 'pattern', ...], + ['piece', 'deputy', ...], + ['toss', 'shadow', ...], + ['guard', 'nurse', ...] +]; + +for (let i = 0; i < mnemonics.length; i++) keyPairs[i] = await mnemonicToPrivateKey(mnemonics[i]); +``` + +创建`MultisigWallet`对象有两种方式: + +- 从地址导入现有钱包 + +```js +let addr: Address = Address.parse('EQADBXugwmn4YvWsQizHdWGgfCTN_s3qFP0Ae0pzkU-jwzoE'); +let mw: MultisigWallet = await MultisigWallet.fromAddress(addr, { client }); +``` + +- 创建一个新的 + +```js +let mw: MultisigWallet = new MultisigWallet([keyPairs[0].publicKey, keyPairs[1].publicKey], 0, 0, 1, { client }); +``` + +部署它也有两种方式 + +- 通过内部消息 + +```js +let wallet: WalletContractV4 = WalletContractV4.create({ workchain: 0, publicKey: keyPairs[4].publicKey }); +//wallet should be active and have some balance +await mw.deployInternal(wallet.sender(client.provider(wallet.address, null), keyPairs[4].secretKey), toNano('0.05')); +``` + +- 通过外部消息 + +```js +await mw.deployExternal(); +``` + +## 创建、签名并发送订单 + +我们需要一个`MultisigOrderBuilder`对象来创建新订单。 + +```js +let order1: MultisigOrderBuilder = new MultisigOrderBuilder(0); +``` + +然后我们可以向它添加一些消息。 + +```js +let msg: MessageRelaxed = { + body: beginCell().storeUint(0, 32).storeBuffer(Buffer.from('Hello, world!')).endCell(), + info: { + bounce: true, + bounced: false, + createdAt: 0, + createdLt: 0n, + dest: Address.parse('EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx'), + forwardFee: 0n, + ihrDisabled: true, + ihrFee: 0n, + type: "internal", + value: { coins: toNano('0.01') } + } +}; + +order1.addMessage(msg, 3); +``` + +添加消息后,通过调用`build()`方法将`MultisigOrderBuilder`转换为`MultisigOrder`。 + +```js +let order1b: MultisigOrder = order1.build(); +order1b.sign(0, keyPairs[0].secretKey); +``` + +现在让我们创建另一个订单,向其中添加消息,使用另一组密钥对其进行签名,并合并这些订单的签名。 + +```js +let order2: MultisigOrderBuilder = new MultisigOrderBuilder(0); +order2.addMessage(msg, 3); +let order2b = order2.build(); +order2b.sign(1, keyPairs[1].secretKey); + +order1b.unionSignatures(order2b); //Now order1b have also have all signatures from order2b +``` + +最后,发送已签名的订单: + +```js +await mw.sendOrder(order1b, keyPairs[0].secretKey); +``` + +现在构建项目 + +```bash +yarn tsc +``` + +运行编译后的文件 + +```bash +node main.js +``` + +如果没有抛出任何错误,您就做对了!现在使用任何浏览器或钱包检查您的交易是否成功。 + +## 其他方法和属性 + +您可以轻松地从`MultisigOrderBuilder`对象中清除消息: + +```js +order2.clearMessages(); +``` + +您还可以从`MultisigOrder`对象中清除签名: + +```js +order2b.clearSignatures(); +``` + +当然,您还可以从`MultisigWallet`、`MultisigOrderBuilder`和`MultisigOrder`对象中获取公共属性 + +- MultisigWallet: + - `owners` - 签名的`Dictionary` *ownerId => signature* + - `workchain` - 钱包部署的工作链 + - `walletId` - 钱包ID + - `k` - 确认交易所需的签名数量 + - `address` - 钱包地址 + - `provider` - `ContractProvider`实例 + +- MultisigOrderBuilder + - `messages` - 要添加到订单的`MessageWithMode`数组 + - `queryId` - 订单有效的全局时间 + +- MultisigOrder + - `payload` - 带有订单有效载荷的`Cell` + - `signatures` - 签名的`Dictionary` *ownerId => signature* + +## 参考资料 + +- [低层级多重签名指南](/develop/smart-contracts/tutorials/multisig) +- [ton.js文档](https://ton-community.github.io/ton/) +- [多重签名合约源代码](https://github.com/ton-blockchain/multisig-contract) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig.md new file mode 100644 index 0000000000..aebf69f56a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/multisig.md @@ -0,0 +1,270 @@ +--- +description: 本教程结束时,您将在TON区块链上部署了多签合约。 +--- + +# 如何制作一个简单的多签合约 + +## 💡 概览 + +本教程将帮助您学习如何部署您的多签合约。回想一下,(n, k)多签合约是一个有n个私钥持有者的多签钱包,如果请求(又称订单、查询)至少收集到持有者的k个签名,则接受发送消息的请求。 + +基于akifoq对原始多签合约代码的更新: + +- [原始TON区块链多签代码.multisig-code.fc](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/multisig-code.fc) +- [akifoq/multisig](https://github.com/akifoq/multisig),带有fift库以使用多签。 + +:::tip 初学者提示 +对多签不熟悉的人可以看:[什么是多签技术?(视频)](https://www.youtube.com/watch?v=yeLqe_gg2u0) +::: + +## 📖 您将学到什么 + +- 如何创建和定制一个简单的多签钱包。 +- 如何使用轻客户端部署多签钱包。 +- 如何签署请求并将其作为消息发送到区块链。 + +## ⚙ 设置您的环境 + +在我们开始之前,检查并准备您的环境。 + +- 从[安装](/develop/smart-contracts/environment/installation)部分中安装`func`、`fift`、`lite-client`二进制文件和`fiftlib`。 +- 克隆[库](https://github.com/akifoq/multisig)并在CLI中打开其目录。 + +```cpp +https://github.com/akifoq/multisig.git +cd ~/multisig +``` + +## 🚀 开始吧! + +1. 将代码编译为fift。 +2. 准备多签所有者的密钥。 +3. 部署您的合约。 +4. 与区块链中部署的多签钱包进行交互。 + +### 编译合约 + +使用以下命令将合约编译为Fift: + +```cpp +func -o multisig-code.fif -SPA stdlib.fc multisig-code.fc +``` + +### 准备多签所有者密钥 + +#### 创建参与者密钥 + +要创建一个密钥,您需要运行: + +```cpp +fift -s new-key.fif $KEY_NAME$ +``` + +- 其中`KEY_NAME`是将写入私钥的文件的名称。 + +例如: + +```cpp +fift -s new-key.fif multisig_key +``` + +我们将收到一个包含私钥的`multisig_key.pk`文件。 + +#### 收集公钥 + +此外,脚本还会以以下格式发出一个公钥: + +``` +Public key = Pub5XqPLwPgP8rtryoUDg2sadfuGjkT4DLRaVeIr08lb8CB5HW +``` + +在`"Public key = "`之后的任何内容都需要保存在某个地方! + +让我们将其存储在`keys.txt`文件中。每行一个公钥,这很重要。 + +### 部署您的合约 + +#### 通过轻客户端部署 + +创建所有密钥后,您需要将公钥收集到文本文件`keys.txt`中。 + +例如: + +```bash +PubExXl3MdwPVuffxRXkhKN1avcGYrm6QgJfsqdf4dUc0an7/IA +PubH821csswh8R1uO9rLYyP1laCpYWxhNkx+epOkqwdWXgzY4 +``` + +之后,您需要运行: + +```cpp +fift -s new-multisig.fif 0 $WALLET_ID$ wallet $KEYS_COUNT$ ./keys.txt +``` + +- `$WALLET_ID$` - 分配给当前密钥的钱包号。对于每个使用相同密钥的新钱包,建议使用唯一的`$WALLET_ID$`。 +- `$KEYS_COUNT$` - 确认所需的密钥数量,通常等于公钥数量 + +:::info wallet_id 解释 +使用相同的密钥(Alice密钥,Bob密钥)可以创建许多钱包。如果Alice和Bob已经有treasure怎么办?这就是为什么`$WALLET_ID$`在这里至关重要。 +::: + +脚本将输出类似于以下的内容: + +```bash +new wallet address = 0:4bbb2660097db5c72dd5e9086115010f0f8c8501e0b8fef1fe318d9de5d0e501 + +(Saving address to file wallet.addr) + +Non-bounceable address (for init): 0QBLuyZgCX21xy3V6QhhFQEPD4yFAeC4_vH-MY2d5dDlAbel + +Bounceable address (for later access): kQBLuyZgCX21xy3V6QhhFQEPD4yFAeC4_vH-MY2d5dDlAepg + +(Saved wallet creating query to file wallet-create.boc) +``` + +:::info +如果您遇到“公钥必须为48个字符长”的错误,请确保您的`keys.txt`具有unix类型的换行符 - LF。例如,可以通过Sublime文本编辑器更改换行符。 +::: + +:::tip +最好保留可弹回地址 - 这是钱包的地址。 +::: + +#### 激活您的合约 + +您需要向我们新生成的_treasure_发送一些TON,例如0.5 TON。 + +之后,您需要运行轻客户端: + +```bash +lite-client -C global.config.json +``` + +:::info 如何获取`global.config.json`? +您可以为[主网](https://ton.org/global-config.json)或[测试网](https://ton.org/testnet-global.config.json)获取最新的配置文件`global.config.json`。 +::: + +启动轻客户端后,最好在轻客户端控制台运行`time`命令,以确保连接成功: + +```bash +time +``` + +好的,轻客户端工作正常! + +之后,您需要部署钱包。运行命令: + +``` +sendfile ./wallet-create.boc +``` + +之后,钱包将在一分钟内准备好可供使用。 + +### 与多签钱包进行交互 + +#### 创建请求 + +首先,您需要创建一个消息请求: + +```cpp +fift -s create-msg.fif $ADDRESS$ $AMOUNT$ $MESSAGE$ +``` + +- `$ADDRESS$` - 发送代币的地址 +- `$AMOUNT$` - 代币的数量 +- `$MESSAGE$` - 被编译消息的文件名。 + +例如: + +```cpp +fift -s create-msg.fif EQApAj3rEnJJSxEjEHVKrH3QZgto_MQMOmk8l72azaXlY1zB 0.1 message +``` + +:::tip +要为您的交易添加评论,请使用`-C comment`属性。要获取更多信息,请在没有参数的情况下运行_create-msg.fif_文件。 +::: + +#### 选择钱包 + +接下来,您需要选择一个要发送代币的钱包: + +``` +fift -s create-order.fif $WALLET_ID$ $MESSAGE$ -t $AWAIT_TIME$ +``` + +其中 + +- `$WALLET_ID$` — 是由此多签合约支持的钱包的ID。 +- `$AWAIT_TIME$` — 智能合约将等待多签钱包所有者对请求签名的时间(以秒为单位)。 +- `$MESSAGE$` — 上一步中创建的消息boc文件的名称。 + +:::info +如果在请求得到签名之前,时间等于`$AWAIT_TIME$`这样的条件已经过去了,请求将过期。通常,$AWAIT_TIME$等于几个小时(7200秒) +::: + +例如: + +``` +fift -s create-order.fif 0 message -t 7200 +``` + +准备好的文件将保存在`order.boc`中 + +:::info +`order.boc`需要与密钥持有者共享,他们必须对其进行签名。 +::: + +#### 签署您的部分 + +要签名,您需要执行: + +```bash +fift -s add-signature.fif $KEY$ $KEY_INDEX$ +``` + +- `$KEY$` - 包含签名私钥的文件的名称,不带扩展名。 +- `$KEY_INDEX$` - `keys.txt`中给定密钥的索引(从零开始) + +例如,对于我们的`multisig_key.pk`文件: + +``` +fift -s add-signature.fif multisig_key 0 +``` + +#### 创建消息 + +在每个人都签署了订单后,需要将其转换为钱包的消息,并再次使用以下命令进行签名: + +``` +fift -s create-external-message.fif wallet $KEY$ $KEY_INDEX$ +``` + +在这种情况下,只需要钱包所有者的一个签名即可。这样做的想法是,您无法使用无效签名攻击合约。 + +例如: + +``` +fift -s create-external-message.fif wallet multisig_key 0 +``` + +#### 将签名发送到TON区块链 + +之后,您需要再次启动轻客户端: + +```bash +lite-client -C global.config.json +``` + +最后,我们要发送我们的签名!只需运行: + +```bash +sendfile wallet-query.boc +``` + +如果其他人都签署了请求,它将被完成! + +您做到了,哈哈!🚀🚀🚀 + +## 接下来 + +- [阅读更多关于TON中多签钱包的信息](https://github.com/akifoq/multisig),来自akifoq。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/single-nominator-pool.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/single-nominator-pool.mdx new file mode 100644 index 0000000000..c7eac8b755 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/single-nominator-pool.mdx @@ -0,0 +1,184 @@ +# 单一提名者池 + +## 使用 mytonctrl + +目前 [mytonctrl](https://github.com/ton-blockchain/mytonctrl) 支持 `single_nominator` 合约,但首先需要安装 mytonctrl 2.0。 + +### 准备验证者 + +如果已经安装了 mytonctrl,只需使用 "更新 mytonctrl2 "命令。如果没有安装 mytonctrl,请按照以下步骤操作: + +1. 下载安装脚本: + +```bash +wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/mytonctrl2/scripts/install.sh +``` + +2. 运行安装脚本: + +```bash +sudo bash ./install.sh -b mytonctrl2 +``` + +### 设置单一提名人 + +创建并激活验证器钱包后,请按以下步骤操作: + +1. 启用单一提名模式 + +```bash +MyTonCtrl> enable_mode single-nominator +``` + +2. 创建池 + +```bash +MyTonCtrl> new_single_pool +``` + +3. 输入 `pools_list` 以显示池地址 + +4. 发送1个TON到池子并激活它 + +```bash +MyTonCtrl> activate_single_pool +``` + +现在,你可以通过 mytonctrl 像使用标准提名池一样使用这个提名池。 + +## 没有 mytonctrl + +#### 准备已启动的验证者 + + 如果您已安装 mytonctrl 并正在运行验证者: + +1. 停止验证并提取所有资金。 + +#### 从一开始就做好准备 + + 如果之前没有验证器,请执行以下操作: + +1. [运行验证器](/participate/run-nodes/full-node)并确保同步。 +2. 停止验证并提取所有资金。 + +## 准备single_nominator + +1. 安装 [nodejs](https://nodejs.org/en) v.16 及更高版本和 npm ( [详细说明](https://github.com/nodesource/distributions#debian-and-ubuntu-based-distributions)) +2. 安装 `ts-node` 和 `arg` 模块 + +```bash +$ sudo apt install ts-node +$ sudo npm i arg -g +``` + +4. 创建符号链接: + +```bash +$ sudo ln -s /usr/bin/ton/crypto/fift /usr/local/bin/fift +$ sudo ln -s /usr/bin/ton/crypto/func /usr/local/bin/func +``` + +5. 运行测试,确保一切设置正确: + +```bash +$ npm run test +``` + +6. 替换 mytonctrl 提名者池脚本:https://raw.githubusercontent.com/orbs-network/single-nominator/main/mytonctrl-scripts/install-pool-scripts.sh + +## 创建single_nominator池 + +1. 从 Telegram [@tonapibot] 获取 Toncenter API 密钥(https://t.me/tonapibot) +2. 设置环境变量: + +```bash +export OWNER_ADDRESS= +export VALIDATOR_ADDRESS= +export TON_ENDPOINT=https://toncenter.com/api/v2/jsonRPC +export TON_API_KEY= +``` + +2. 创建部署者地址: + +```bash +$ npm run init-deploy-wallet +Insufficient Deployer [EQAo5U...yGgbvR] funds 0 +``` + +3. 为部署者地址充值2.1 TON +4. 部署池合同,您将获得池地址:Ef-kC0...\_WLqgs\`: + +``` +$ npm run deploy +``` + +5. 将地址转换为 .addr: + +``` +$ fift -s ./scripts/fift/str-to-addr.fif Ef-kC0..._WLqgs +``` + +(将地址保存到文件 single-nominator.addr 中) + +6. 备份部署器私钥"./build/deploy.config.json "和 "single-nominator.addr "文件 +7. 将 "single-nominator.addr "复制到 "mytoncore/spool/single-nominator-1.addr"。 +8. 将木桩从所有者地址发送至单一提名人地址 + +## 从单一提名者提款 + +使用钱包从single_nominator提取 +Fift: + +1. 创建包含金额的 "withdraw.boc "申请: + +```bash +$ fift -s ./scripts/fift/withdraw.fif +``` + +2. 从所有者的钱包创建和签署请求: + +```bash +$ fift -s wallet-v3.fif -B withdraw.boc +``` + +3. 广播查询: + +```bash +$ lite-client -C global.config.json -c 'sendfile wallet-query.boc' +tons +``` + +1. 创建包含金额的 "withdraw.boc "申请: + +```bash +$ fift -s ./scripts/fift/withdraw.fif +``` + +2. 将申请发送至单一提名人地址: + +a. + +```bash +$ tons wallet transfer --body withdraw.boc +tonkeeper +``` + +b. + +``` +npm link typescript +``` + +c. + +``` +npx ts-node scripts/ts/withdraw-deeplink.ts +``` + +d.打开机主手机上的 deeplink + +## 参阅 + +- [单一提名者池合约](https://github.com/orbs-network/single-nominator) +- [归属合约](/participate/network-maintenance/vesting-contract) +- [single-nominator-quick-how-to-09-25](https://telegra.ph/single-nominator-quick-how-to-09-25) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/wallet.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/wallet.md new file mode 100644 index 0000000000..fc4bef7bd1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/howto/wallet.md @@ -0,0 +1,2722 @@ +--- +description: 在本教程中,您将学习如何完全使用钱包、交易和智能合约进行工作。 +--- + +import Tabs from'@theme/Tabs'; +import TabItem from'@theme/TabItem'; + +# 使用钱包智能合约的工作 + +## 👋 介绍 + +在开始智能合约开发之前,学习 TON 上的钱包和交易如何工作是必不可少的。这些知识将帮助开发者了解钱包、交易和智能合约之间的交互,以实现特定的开发任务。 + +在本节中,我们将学习如何创建操作,而不使用预配置的函数,以了解开发工作流程。本教程的所有必要参考资料都位于参考章节。 + +## 💡 必要条件 + +这个教程需要对 JavaScript、TypeScript 和 Golang 有基本的了解。同时至少需要持有 3 个 TON(可以存储在交易所账户、非托管钱包中,或使用电报机器人钱包进行存储)。此外,还需要对 [cell(单元)](/learn/overviews/cells)、[TON 地址](/learn/overviews/addresses) 和[区块链的区块链](/learn/overviews/ton-blockchain) 有基本的了解,以理解本教程。 + +:::info 主网开发至关重要 +在 TON 测试网上工作往往会导致部署错误、难以跟踪交易以及不稳定的网络功能。因此,完成大部分开发工作时间可能好处是建议在 TON Mainnet 上完成,以避免这些问题,这可能需要减少交易数量,从而可能减小费用。 +::: + +## 源代码 +本教程中使用的所有代码示例都可以在以下 [GitHub 存储库](https://github.com/aSpite/wallet-tutorial) 中找到。 + +## ✍️ 您开始所需的内容 + +- 确保 NodeJS 已安装。 +- 需要特定的 Ton 库,包括:@ton/ton 13.5.1+、@ton/core 0.49.2+ 和 @ton/crypto 3.2.0+。 + +**可选**: 如果您喜欢使用 Golang 而不是使用 JS,那么需要安装 [tonutils-go](https://github.com/xssnick/tonutils-go) 库以及 GoLand IDE,用于进行 TON 开发。本教程中将使用这个库来进行 Golang 版本的操作。 + + + + + +```bash +npm i --save @ton/ton @ton/core @ton/crypto +``` + + + + +```bash +go get github.com/xssnick/tonutils-go +go get github.com/xssnick/tonutils-go/adnl +go get github.com/xssnick/tonutils-go/address +``` + + + + +## ⚙ 设置您的环境 + +为了创建一个 TypeScript 项目,必须按照以下步骤进行操作: +1. 创建一个空文件夹(我们将其命名为 WalletsTutorial)。 +2. 使用 CLI 打开项目文件夹。 +3. 使用以下命令来设置项目: +```bash +npm init -y +npm install typescript @types/node ts-node nodemon --save-dev +npx tsc --init --rootDir src --outDir build \ --esModuleInterop --target es2020 --resolveJsonModule --lib es6 \ --module commonjs --allowJs true --noImplicitAny false --allowSyntheticDefaultImports true --strict false +``` +:::info +为了帮助我们完成下一个流程,我们使用了 `ts-node` 来直接执行 TypeScript 代码,而无需预编译。当检测到目录中的文件更改时,`nodemon` 会自动重新启动节点应用程序。 +::: +4. 删除 `tsconfig.json` 中的以下行: +```json + "files": [ + "\\", + "\\" + ] +``` +5. 然后,在项目根目录中创建 `nodemon.json` 配置文件,内容如下: +```json +{ + "watch": ["src"], + "ext": ".ts,.js", + "ignore": [], + "exec": "npx ts-node ./src/index.ts" +} +``` +6. 在 `package.json` 中添加以下脚本到 "test" 脚本的位置: +```json +"start:dev": "npx nodemon" +``` +7. 在项目根目录中创建 `src` 文件夹,然后在该文件夹中创建 `index.ts` 文件。 +8. 接下来,添加以下代码: +```ts +async function main() { + console.log("Hello, TON!"); +} + +main().finally(() => console.log("Exiting...")); +``` +9. 使用终端运行以下代码: +```bash +npm run start:dev +``` +10. 最后,控制台将输出以下内容。 + +![](/img/docs/how-to-wallet/wallet_1.png) + +:::tip Blueprint +TON 社区创建了一个优秀的工具来自动化所有开发过程(部署、合约编写、测试)称为 [Blueprint](https://github.com/ton-org/blueprint)。然而,我们在本教程中不需要这么强大的工具,所以建议遵循上述说明。 +::: + +**可选:** 当使用 Golang 时,请按照以下说明进行操作: + +1. 安装 GoLand IDE。 +2. 使用以下内容创建项目文件夹和 `go.mod` 文件(如果使用的当前版本已过时,则可能需要更改 Go 版本): +``` +module main + +go 1.20 +``` +3. 在终端中输入以下命令: +```bash +go get github.com/xssnick/tonutils-go +``` +4. 在项目根目录中创建 `main.go` 文件,内容如下: +```go +package main + +import ( + "log" +) + +func main() { + log.Println("Hello, TON!") +} +``` +5. 将 `go.mod` 中的模块名称更改为 `main`。 +6. 运行上述代码,直到在终端中显示输出。 + +:::info +也可以使用其他 IDE,因为 GoLand 不是免费的,但建议使用 GoLand。 +::: + +:::warning 注意 +所有代码组件都应添加到在[⚙ 设置您的环境](/develop/smart-contracts/tutorials/wallet#-set-your-environment) 部分中创建的 `main` 函数中。 + +另外,下面的每个新部分将指定每个新部分所需的特定代码部分,并且需要将新的导入与旧导入合并起来。 +::: + +## 🚀 让我们开始! + +在本教程中,我们将学习在 TON 区块链上最常使用的钱包(版本 3 和 4),并了解它们的智能合约是如何工作的。这将使开发人员更好地理解 TON 平台上的不同类型的交易,以便更简单地创建交易、将其发送到区块链、部署钱包,并最终能够处理高负载的钱包。 + +我们的主要任务是使用 @ton/ton、@ton/core、@ton/crypto 的各种对象和函数构建交易,以了解大规模交易是怎样的。为了完成这个过程,我们将使用两个主要的钱包版本(v3 和 v4),因为交易所、非托管钱包和大多数用户仅使用这些特定版本。 + +:::note +在本教程中,可能会有一些细节没有解释。在这些情况下,将在本教程的后续阶段提供更多细节。 + +**重要:** 在本教程中,我们使用了 [wallet v3 代码](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet3-code.fc) 来更好地理解钱包开发过程。需要注意的是,v3 版本有两个子版本:r1 和 r2。目前,只使用第二个版本,这意味着当我们在本文档中提到 v3 时,它指的是 v3r2。 +::: + +## 💎 TON 区块链钱包 + +在 TON 区块链上运行的所有钱包实际上都是智能合约,与 TON 上的一切都是智能合约的方式相同。与大多数区块链一样,可以在网络上部署智能合约并根据不同的用途自定义它们。由于这个特性,**完全自定义的钱包是可能的**。 +在 TON 上,钱包智能合约帮助平台与其他智能合约类型进行通信。然而,重要的是要考虑钱包通信是如何进行的。 + +### 钱包通信 +通常,在 TON 区块链上有两种交易类型:`internal` 和 `external`。外部交易允许从外部世界向区块链发送消息,从而与接受此类交易的智能合约进行通信。负责执行此过程的函数如下: + +```func +() recv_external(slice in_msg) impure { + ;; 一些代码 +} +``` + +在我们深入研究钱包之前,让我们先看看钱包如何接受外部交易。在 TON 上,所有钱包都持有所有者的 `公钥`、`seqno` 和 `subwallet_id`。接收到外部交易时,钱包使用 `get_data()` 方法从钱包的存储部分中检索数据。然后进行多个验证流程,并决定是否接受此交易。这个过程的完成如下: + +```func +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); ;; 从消息体中获取签名 + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); ;; 从消息体中获取其他值 + throw_if(35, valid_until <= now()); ;; 检查交易的有效性 + var ds = get_data().begin_parse(); ;; 从存储获取数据并将其转换为可读取值的切片 + var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); ;; 从存储中读取值 + ds.end_parse(); ;; 确保变量 ds 中没有任何数据 + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); +``` + +> 💡 有用的链接: +> +> [“load_bits()(文档)](/develop/func/stdlib/#load_bits) +> +> [“get_data()(文档)](/develop/func/stdlib/#load_bits) +> +> [“begin_parse()(文档)](/develop/func/stdlib/#load_bits) +> +> [“end_parse()(文档)](/develop/func/stdlib/#end_parse) +> +> [“load_int()(文档)](/develop/func/stdlib/#load_int) +> +> [“load_uint()(文档)](/develop/func/stdlib/#load_int) +> +> [“check_signature()(文档)](/develop/func/stdlib/#check_signature) +> +> [“slice_hash()(文档)](/develop/func/stdlib/#slice_hash) +> +> [“accept_message()(文档)](/develop/func/stdlib/#accept_message) + +接下来,我们来详细看一下。 + +### 重放保护 - Seqno + +钱包智能合约中的交易重放保护与交易 seqno(序列号)直接相关,它跟踪哪些交易以什么顺序发送。不能重复发送钱包中的单个交易非常重要,因为这会完全破坏系统的完整性。如果进一步检查智能合约代码,通常会处理 `seqno` 如下: + +```func +throw_unless(33, msg_seqno == stored_seqno); +``` + +上述代码将检查在交易中获得的 `seqno` 是否与存储在智能合约中的 `seqno` 相匹配。如果不匹配,则合约返回带有 `33 exit code` 的错误。因此,如果发送者传递了无效的 `seqno`,则意味着他在交易序列中犯了一些错误,合约保护了这些情况。 + +:::note +还需要确认外部消息可以由任何人发送。这意味着如果您向某人发送 1 TON,其他人也可以重复该消息。但是,当 seqno 增加时,以前的外部消息失效,并且没有人可以重复该消息,从而防止窃取您的资金。 +::: + +### 签名 + +如前所述,钱包智能合约接受外部交易。然而,这些交易来自外部世界,这些数据不能 100% 可信。因此,每个钱包都存储所有者的公钥。当钱包接收到所有者使用私钥签名的外部交易时,智能合约使用公钥验证交易签名的合法性。这样可以验证交易实际上是来自合约所有者的。 + +要执行此过程,首先钱包需要从传入消息中获取签名,从存储中加载公钥,并使用以下过程验证签名: + +```func +var signature = in_msg~load_bits(512); +var ds = get_data().begin_parse(); +var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); +throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); +``` + +如果所有验证流程都顺利完成,智能合约接受消息并对其进行处理: + +```func +accept_message(); +``` + +:::info accept_message() +由于交易来自外部世界,它不包含支付交易费用所需的 Toncoin。在使用 accept_message() 函数发送 TON 时,应用gas_credit(在写入时其值为10,000 gas单位),并且只要gas不超过 gas_credit 值,就允许免费进行必要的计算。使用 accept_message() 函数后,从智能合约的账户余额中收取所有已花费的gas(以 TON 计)。可以在[此处](/develop/smart-contracts/guidelines/accept)了解有关此过程的更多信息。 +::: + +### 交易过期 + +用于检查外部交易的有效性的另一步是 `valid_until` 字段。从变量名称可以看出,这是交易在 UNIX 中在有效之前的时间。如果此验证过程失败,则合约完成交易处理并返回 32 退出码,如下所示: + +```func +var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); +throw_if(35, valid_until <= now()); +``` + +此算法用于在交易不再有效但仍然以未知原因发送到区块链时,防范各种错误的易受攻击性。 + +### 钱包 v3 和钱包 v4 的区别 + +钱包 v3 和钱包 v4 之间的唯一区别是钱包 v4 使用可以安装和删除的 `插件`。插件是特殊的智能合约,可以从钱包智能合约请求在特定时间从指定数量的 TON 中。钱包智能合约将相应地发送所需数量的 TON,而无需所有者参与。这类似于为插件创建的 **订阅模型**。我们不会在本教程中详细介绍这些细节,因为这超出了本教程的范围。 + +### 钱包如何促进与智能合约的交流 + +正如我们之前讨论的那样,钱包智能合约接受外部交易,验证它们,如果通过了所有检查,则接受它们。然后,合约开始从外部消息的主体中检索消息,然后创建内部消息并将其发送到区块链,如下所示: + +```func +cs~touch(); +while (cs.slice_refs()) { + var mode = cs~load_uint(8); ;; 加载交易模式 + send_raw_message(cs~load_ref(), mode); ;; 使用 load_ref() 将每一个新的内部消息作为一个带有 load_ref() 的cell,并发送它 +} +``` + +:::tip touch() +在 TON 上,所有智能合约都在基于堆栈的 TON 虚拟机(TVM)上运行。~ touch() 将变量 `cs` 放置在堆栈的顶部,以优化代码运行以节省 gas。 +::: + +由于一个cell中最多可以存储 4 个引用,我们可以每个外部消息发送最多 4 个内部消息。 + +> 💡 有用的链接: +> +> [slice_refs()](/develop/func/stdlib/#slice_refs) +> +> [send_raw_message() and transaction modes](/develop/func/stdlib/#send_raw_message) +> +> [load_ref()](/develop/func/stdlib/#load_ref) + +## 📬 外部和内部交易 + +在本节中,我们将学习有关 `internal` 和 `external` 交易的更多信息,并创建交易并将其发送到网络中以尽量减少使用预先创建的函数。 + +为了完成此过程,需要使用一个预先制作的钱包使任务变得更容易。为此: +1. 安装 [wallet 应用程序](/participate/wallets/apps)(例如,Tonkeeper 是作者使用的)。 +2. 将钱包应用切换到 v3r2 地址版本。 +3. 向钱包存入 1 TON。 +4. 将交易发送到另一个地址(可以发送给自己,发送到同一个钱包)。 + +这样,Tonkeeper 钱包应用程序将部署钱包合约,我们可以在以下步骤中使用它。 + +:::note +在撰写本文时,TON 上的大多数钱包应用程序默认使用钱包 v4 版本。在本教程中,不需要使用插件的功能,因此我们将使用钱包 v3 提供的功能。在使用过程中,Tonkeeper 允许用户选择他们想要的钱包版本。因此,建议部署钱包版本 3(wallet v3)。 +::: + +### TL-B + +如前所述,TON 区块链上的所有内容都是由cell组成的智能合约。为了正确进行序列化和反序列化过程,创建了 `TL-B` 作为一种通用工具,用于以不同的方式、不同的顺序来描述cell中的不同数据类型。 + +在本节中,我们将详细研究 [block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb)。在将来的开发中,此文件将非常有用,因为它描述了不同cell的组装方式。在我们的情况下,它详细描述了内部和外部交易的复杂性。 + +:::info +本指南将提供基本信息。有关更多详细信息,请参阅我们的 TL-B [文档](/develop/data-formats/tl-b-language),以了解更多关于 TL-B 的知识。 +::: + + +### CommonMsgInfo + +首先,每个消息必须首先存储 `CommonMsgInfo`([TL-B](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L123-L130))或 `CommonMsgInfoRelaxed`([TL-B](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L132-L137))。这允许我们定义与交易类型、交易时间、接收者地址、技术标志位和费用相关的技术细节。 + +通过阅读 `block.tlb` 文件,我们可以注意到 CommonMsgInfo有三种不同的类型:`int_msg_info$0`、`ext_in_msg_info$10`、`ext_out_msg_info$11`。我们将不对 `ext_out_msg_info` 的 TL-B 结构的具体细节进行详细解释。但需要注意的是,它是由智能合约发送的外部交易类型,用作外部日志。要查看此格式的示例,请仔细查看 [Elector](https://tonscan.org/address/Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF) 合约。 + +您可以从 [TL-B](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L127-L128) 中看到,**仅在与 ext_in_msg_info 类型一起使用时才可以使用 CommonMsgInfo**。因为交易类型字段,如 `src`、`created_lt`、`created_at` 等,由验证者在交易处理期间进行重写。在这种情况下,`src` 交易类型最重要,因为当发送交易时,发送者是未知的,验证者在验证期间对其在 `src` 字段中的地址进行重写。这样确保 `src` 字段中的地址是正确的,并且不能被操纵。 + +但是,`CommonMsgInfo` 结构仅支持 `MsgAddress` 规格,但通常情况下发送方的地址是未知的,并且需要写入 `addr_none`(两个零位 `00`)。在这种情况下,使用 `CommonMsgInfoRelaxed` 结构,该结构支持 `addr_none` 地址。对于 `ext_in_msg_info`(用于传入的外部消息),使用 `CommonMsgInfo` 结构,因为这些消息类型不使用sender,始终使用 [MsgAddressExt](https://hub.com/ton/ton.blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L100) 结构(`addr_none$00` 表示两个零位),因此无需覆盖数据。 + +:::note +`$`符号后面的数字是在某个结构的开始处所要求存储的位,以便在读取时(反序列化)可进一步识别这些结构。 +::: + +### 创建内部交易 +内部交易用于在合约之间发送消息。当分析发送使用合约进行编写的各种合约类型(例如 [NFTs](https://github.com/ton-blockchain/token-contract/blob/f2253cb0f0e1ae0974d7dc0cef3a62cb6e19f806/nft/nft-item.fc#L51-L56) 和 [Jetons](https://github.com/ton-blockchain/token-contract/blob/f2253cb0f0e1ae0974d7dc0cef3a62cb6e19f806/ft/jetton-wallet.fc#L139-L144)),常常会使用以下代码行: + +```func +var msg = begin_cell() + .store_uint(0x18, 6) ;; 或者 0x10 代表不可弹回 + .store_slice(to_address) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认的消息头(请参阅发送消息页面) + ;; 作为存储体 +``` + +让我们首先考虑 `0x18` 和 `0x10`(x - 16 进制),这些十六进制数是按以下方式排列的(考虑到我们分配了 6 个位):`011000` 和 `010000`。这意味着,可以将上述代码重写为以下内容: + +```func +var msg = begin_cell() + .store_uint(0, 1) ;; 这个位表示我们发送了一个内部消息,与 int_msg_info$0 对应 + .store_uint(1, 1) ;; IHR 禁用 + .store_uint(1, 1) ;; 或者 .store_uint(0, 1) 对于 0x10 | 退回 + .store_uint(0, 1) ;; 退回 + .store_uint(0, 2) ;; src -> 两个零位代表 addr_none + .store_slice(to_address) + .store_coins(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认的消息头(请参阅发送消息页面) + ;; 作为存储体 +``` + +现在我们来详细解释每个选项: + +选项 | 说明 +:---: | :---: +IHR Disabled | 当前此选项被禁用(意味着我们存储 1),因为 Instant Hypercube Routing 尚未完全实现。此外,当网络上有大量 [Shardchains](/learn/overviews/ton-blockchain#many-accountchains-shards) 时,这将是必要的。有关禁用 IHR 的更多信息,请阅读[tblkch.pdf](https://ton.org/tblkch.pdf)(第 2 章)。 +Bounce | 发送交易时,在处理智能合约期间可能发生各种错误。为了避免失去 TON,需要将 Bounce 选项设置为 1(true)。在这种情况下,如果在交易处理过程中发生任何合约错误,该交易将返回给发送者,并会收到总量减去手续费的 TON。有关无法反弹的消息的更多信息,请参阅 [此处](/develop/smart-contracts/guidelines/non-bouncable-messages)。 +Bounced | 弹回交易是发送者返回的交易,因为在处理交易时与智能合约发生了错误。此选项告诉您接收到的交易是否被弹回。 +Src | Src 是发送者地址。在这种情况下,写入了两个零位以指示 `addr_none` 地址。 +接下来的两行代码: +```func +... +.store_slice(to_address) +.store_coins(amount) +... +``` +- 我们指定了接收方和要发送的 TON 数量。 + +最后,我们来看剩下的代码行: + +```func +... + .store_uint(0, 1) ;; Extra currency + .store_uint(0, 4) ;; IHR fee + .store_uint(0, 4) ;; Forwarding fee + .store_uint(0, 64) ;; Logical time of creation + .store_uint(0, 32) ;; UNIX time of creation + .store_uint(0, 1) ;; State Init + .store_uint(0, 1) ;; Message body + ;; 作为存储体 +``` +选项 | 说明 +:---: | :---: +Extra currency | 这是现有 jettons 的本地实现,目前没有在使用中。 +IHR fee | 如前所述,目前未使用 IHR,因此该费用始终为零。更多信息请参阅 [tblkch.pdf](https://ton.org/tblkch.pdf)(3.1.8)。 +Forwarding fee | 转发消息费用。有关更多信息,请参阅 [费用文档](/develop/howto/fees-low-level#transactions-and-phases)。 +Logical time of creation | 用于创建正确的交易队列的时间。 +UNIX time of creation | 交易在 UNIX 中的创建时间。 +State Init | 用于部署智能合约的代码和源数据。如果此位设为 `0`,则表示我们没有 State Init。但如果设为 `1`,则需要写入另一个位,该位指示 State Init 是否存储在同一个cell中(`0`)或作为引用写入(`1`)。 +Message body | 此部分定义了如何存储消息体。有时,消息体太大而无法适合消息本身。在这种情况下,它应该作为一个**引用**存储,通过将位设置为 `1` 来显示该body作为引用使用。如果位为 `0`,则body在与消息相同的cell中。 + +上述值(包括 Src)具有以下特征,但不包括 State Init 和 Message Body 位,由验证者重写。 + +:::note +如果数字值适合的位数比指定的位数少,则在值的左侧添加缺少的零位。例如,0x18 适合 5 位 -> `11000`。然而,由于指定了 6 位,最终结果变为 `011000`。 +::: + +接下来,我们将开始准备一个交易,该交易将向另一个钱包 v3 发送 Toncoins。首先,假设用户想要向自己发送 0.5 TON,并附带文本“**你好,TON!**”,请参阅本文档的这一部分来了解[如何发送带有评论的消息](/develop/func/cookbook#how-to-send-a-simple-message)。 + + + + +```js +import { beginCell } from'@ton/core'; + +let internalMessageBody = beginCell(). + storeUint(0, 32). // 写入 32 个零位以指示接下来将有文本注释 + storeStringTail("你好,TON!"). // 写入我们的文本注释 + endCell(); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/tvm/cell" +) + +internalMessageBody := cell.BeginCell(). + MustStoreUInt(0, 32). // 写入 32 个零位以指示接下来将有文本注释 + MustStoreStringSnake("你好,TON!"). // 写入我们的文本注释 + EndCell() +``` + + + + +上面我们创建了 `InternalMessageBody`,其中存储了消息的正文。请注意,在存储不能适合单个cell的文本(1023 位)的情况下,根据[以下文档](/develop/smart-contracts/guidelines/internal-messages) 中的要求,需要**将数据拆分为多个cell**。但是,在此阶段,高层级库根据要求创建cell,因此现阶段无需担心这个问题。 + +接下来,根据我们之前学习的信息,创建 `InternalMessage` 如下所示: + + + + +```js +import { toNano, Address, beginCell } from'@ton/ton'; + +const walletAddress = Address.parse('把你的钱包地址放这里'); + +let internalMessage = beginCell(). + storeUint(0, 1). // 表示它是一条内部消息 -> int_msg_info$0 + storeBit(1). // 禁用 IHR + storeBit(1). // bounce + storeBit(0). // bounced + storeUint(0, 2). // src -> addr_none + storeAddress(walletAddress). + storeCoins(toNano("0.2")). // 金额 + storeBit(0). // Extra currency + storeCoins(0). // IHR 费用 + storeCoins(0). // Forwarding 费用 + storeUint(0, 64). // 创建的逻辑时间 + storeUint(0, 32). // 创建的 UNIX 时间 + storeBit(0). // 没有 State Init + storeBit(1). // 我们将 Message Body 存储为引用 + storeRef(internalMessageBody). // 将 Message Body 存储为引用 + endCell(); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" +) + +walletAddress := address.MustParseAddr("把你的钱包地址放这里") + +internalMessage := cell.BeginCell(). + MustStoreUInt(0, 1). // 表示它是一条内部消息 -> int_msg_info$0 + MustStoreBoolBit(true). // 禁用 IHR + MustStoreBoolBit(true). // bounce + MustStoreBoolBit(false). // bounced + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(walletAddress). + MustStoreCoins(tlb.MustFromTON("0.2").NanoTON().Uint64()). // 数量 + MustStoreBoolBit(false). // Extra 货币 + MustStoreCoins(0). // IHR 费用 + MustStoreCoins(0). // Forwarding 费用 + MustStoreUInt(0, 64). // 创建的逻辑时间 + MustStoreUInt(0, 32). // 创建的 UNIX 时间 + MustStoreBoolBit(false). // 没有 State Init + MustStoreBoolBit(true). // 我们将 Message Body 存储为引用 + MustStoreRef(internalMessageBody). // 将 Message Body 存储为引用 + EndCell() +``` + + + + +### 创建消息 + +有必要检索我们的钱包智能合约的`seqno`(序列号)。为此,我们创建了一个`Client`,用于发送请求以运行我们的钱包的Get方法“seqno”。还需要添加种子短语(在创建钱包时保存的种子短语)以通过以下步骤对我们的交易进行签名: + + + + +```js +import { TonClient } from '@ton/ton'; +import { mnemonicToWalletKey } from '@ton/crypto'; + +const client = new TonClient({ + endpoint: "https://toncenter.com/api/v2/jsonRPC", + apiKey: "put your api key" // you can get an api key from @tonapibot bot in Telegram +}); + +const mnemonic = 'put your mnemonic'; // word1 word2 word3 +let getMethodResult = await client.runMethod(walletAddress, "seqno"); // run "seqno" GET method from your wallet contract +let seqno = getMethodResult.stack.readNumber(); // get seqno from response + +const mnemonicArray = mnemonic.split(' '); // get array from string +const keyPair = await mnemonicToWalletKey(mnemonicArray); // get Secret and Public keys from mnemonic +``` + + + + +```go +import ( + "context" + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/ton" + "golang.org/x/crypto/pbkdf2" + "log" + "strings" +) + +mnemonic := strings.Split("put your mnemonic", " ") // get our mnemonic as array + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) // create client + +block, err := client.CurrentMasterchainInfo(context.Background()) // get current block, we will need it in requests to LiteServer +if err != nil { + log.Fatalln("CurrentMasterchainInfo err:", err.Error()) + return +} + +getMethodResult, err := client.RunGetMethod(context.Background(), block, walletAddress, "seqno") // run "seqno" GET method from your wallet contract +if err != nil { + log.Fatalln("RunGetMethod err:", err.Error()) + return +} +seqno := getMethodResult.MustInt(0) // get seqno from response + +// The next three lines will extract the private key using the mnemonic phrase. We will not go into cryptographic details. With the tonutils-go library, this is all implemented, but we’re doing it again to get a full understanding. +mac := hmac.New(sha512.New, []byte(strings.Join(mnemonic, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // In TON libraries "TON default seed" is used as salt when getting keys + +privateKey := ed25519.NewKeyFromSeed(k) +``` + + + + +因此,需要发送`seqno`,`keys` 和 `internal message`。现在需要为我们的钱包创建一条 [message](/develop/smart-contracts/messages),并将数据存储在此消息中以在教程开始时使用的序列中。操作步骤如下: + + + + +```js +import { sign } from '@ton/crypto'; + +let toSign = beginCell(). + storeUint(698983191, 32). // subwallet_id | We consider this further + storeUint(Math.floor(Date.now() / 1e3) + 60, 32). // Transaction expiration time, +60 = 1 minute + storeUint(seqno, 32). // store seqno + storeUint(3, 8). // store mode of our internal transaction + storeRef(internalMessage); // store our internalMessage as a reference + +let signature = sign(toSign.endCell().hash(), keyPair.secretKey); // get the hash of our message to wallet smart contract and sign it to get signature + +let body = beginCell(). + storeBuffer(signature). // store signature + storeBuilder(toSign). // store our message + endCell(); +``` + + + + +```go +import ( + "time" +) + +toSign := cell.BeginCell(). + MustStoreUInt(698983191, 32). // subwallet_id | We consider this further + MustStoreUInt(uint64(time.Now().UTC().Unix()+60), 32). // Transaction expiration time, +60 = 1 minute + MustStoreUInt(seqno.Uint64(), 32). // store seqno + MustStoreUInt(3, 8). // store mode of our internal transaction + MustStoreRef(internalMessage) // store our internalMessage as a reference + +signature := ed25519.Sign(privateKey, toSign.EndCell().Hash()) // get the hash of our message to wallet smart contract and sign it to get signature + +body := cell.BeginCell(). + MustStoreSlice(signature, 512). // store signature + MustStoreBuilder(toSign). // store our message + EndCell() +``` + + + + +注意,这里在`toSign`的定义中没有使用 `.endCell()`。事实上,在这种情况下,**需要将`toSign`的内容直接传递给消息主体**。如果需要编写cell,必须将其保存为引用。 + +:::提示 钱包 V4 +除了我们在钱包 V3中所学习到的基本验证流程,钱包 V4智能合约提取了操作码以确定是否需要简单转换或与插件相关的交易。为了匹配这个版本,需要在写入seqno(序列号)之后并在指定交易模式之前添加 `storeUint(0, 8).`(JS/TS),`MustStoreUInt(0, 8).`(Golang)函数。 +::: + +### 外部交易的创建 + +要从外部世界将任何内部消息传递到区块链中,需要将其包含在外部交易中发送。正如我们之前讨论的那样,仅需要使用 `ext_in_msg_info$10` 结构,因为目标是将外部消息发送到我们的合约中。现在,我们创建一个外部消息,将发送到我们的钱包: + + + + +```js +let externalMessage = beginCell(). + storeUint(0b10, 2). // 0b10 -> 10 in binary + storeUint(0, 2). // src -> addr_none + storeAddress(walletAddress). // Destination address + storeCoins(0). // Import Fee + storeBit(0). // No State Init + storeBit(1). // We store Message Body as a reference + storeRef(body). // Store Message Body as a reference + endCell(); +``` + + + + +```go +externalMessage := cell.BeginCell(). + MustStoreUInt(0b10, 2). // 0b10 -> 10 in binary + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(walletAddress). // Destination address + MustStoreCoins(0). // Import Fee + MustStoreBoolBit(false). // No State Init + MustStoreBoolBit(true). // We store Message Body as a reference + MustStoreRef(body). // Store Message Body as a reference + EndCell() +``` + + + + +选项 | 说明 +--- | --- +Src | 发送者地址。因为传入的外部消息不能有发送者,所以始终存在2个零位(`\u0000`)数据类型的数组(addr_none [TL-B](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L100))。 +Import Fee | 用于支付导入传入的外部消息的费用的费用。 +State Init | 和内部消息不同,外部消息中的State Init需要**从外部世界部署合约**。将State Init与内部消息一起使用,可以使一个合约可以部署另一个合约。 +Message Body | 必须发送到合约以进行处理的消息。 + +:::提示 `0b10` +`0b10` (b表示二进制)表示一个二进制记录。在此过程中,存储了两个位:`1` 和 `0`,因此我们指定为 `ext_in_msg_info$10`。 +::: + +现在我们有一条准备好发送给我们的合约的消息。为此,首先需要将其序列化为 `BOC`([cell集合](/develop/data-formats/cell-boc#bag-of-cells)),然后使用以下代码将其发送: + + + + +```js +console.log(externalMessage.toBoc().toString("base64")) + +client.sendFile(externalMessage.toBoc()); +``` + + + + +```go +import ( + "encoding/base64" + "github.com/xssnick/tonutils-go/tl" +) + +log.Println(base64.StdEncoding.EncodeToString(externalMessage.ToBOCWithFlags(false))) + +var resp tl.Serializable +err = client.Client().QueryLiteserver(context.Background(), ton.SendMessage{Body: externalMessage.ToBOCWithFlags(false)}, &resp) + +if err != nil { + log.Fatalln(err.Error()) + return +} +``` + + + + +> 💡 有用的链接: +> +> [更多关于cell集合的信息](/develop/data-formats/cell-boc#bag-of-cells) + +结果是,在控制台上得到了我们Boc的输出,并将交易发送到我们的钱包。您可以复制 base64 编码的字符串,然后可以[手动发送我们的交易并使用 toncenter 检索哈希](https://toncenter.com/api/v2/#/send/send_boc_return_hash_sendBocReturnHash_post)。 + +## 👛 部署钱包 + +我们已经学会了创建消息的基础知识,这对于部署钱包非常有帮助。 以前,我们通过钱包应用程序部署钱包,但在这种情况下,我们将需要手动部署钱包。 + +在本节中,我们将介绍如何从头开始创建钱包(钱包v3)。您将学习如何为钱包智能合约编译代码,生成助记词短语,获得钱包地址,并使用外部交易和State Init部署钱包。 + +### 生成助记词 + +正确定义钱包所需的第一件事是检索`private`和`public`密钥。为了完成这个任务,需要生成助记词种子短语,然后使用加密库提取私钥和公钥。 + +通过以下方式实现: + + + + +```js +import { mnemonicToWalletKey, mnemonicNew } from '@ton/crypto'; + +// const mnemonicArray = 'put your mnemonic'.split(' ') // get our mnemonic as array +const mnemonicArray = await mnemonicNew(24); // 24 is the number of words in a seed phrase +const keyPair = await mnemonicToWalletKey(mnemonicArray); // extract private and public keys from mnemonic +console.log(mnemonicArray) // if we want, we can print our mnemonic +``` + + + + +```go +import ( + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "log" + "github.com/xssnick/tonutils-go/ton/wallet" + "golang.org/x/crypto/pbkdf2" + "strings" +) + +// mnemonic := strings.Split("put your mnemonic", " ") // get our mnemonic as array +mnemonic := wallet.NewSeed() // get new mnemonic + +// The following three lines will extract the private key using the mnemonic phrase. We will not go into cryptographic details. It has all been implemented in the tonutils-go library, but it immediately returns the finished object of the wallet with the address and ready methods. So we’ll have to write the lines to get the key separately. Goland IDE will automatically import all required libraries (crypto, pbkdf2 and others). +mac := hmac.New(sha512.New, []byte(strings.Join(mnemonic, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // In TON libraries "TON default seed" is used as salt when getting keys +// 32 is a key len + +privateKey := ed25519.NewKeyFromSeed(k) // get private key +publicKey := privateKey.Public().(ed25519.PublicKey) // get public key from private key +log.Println(publicKey) // print publicKey so that at this stage the compiler does not complain that we do not use our variable +log.Println(mnemonic) // if we want, we can print our mnemonic +``` + + + + +私钥用于签署交易,公钥存储在钱包的智能合约中。 + +:::danger 重要 +需要将生成的助记词种子短语输出到控制台,然后保存和使用它(如前面的部分中所述),以便每次运行钱包代码时都使用同一对密钥。 +::: + +### 子钱包 ID + +钱包作为智能合约的最显着优势之一是能够仅使用一个私钥创建**大量的钱包**。这是因为TON区块链上的智能合约地址是使用多个因素计算出来的,其中包括`stateInit`。stateInit包含了`代码`和`初始数据`,这些数据存储在区块链的智能合约存储中。 + +通过在stateInit中只更改一个位,可以生成一个不同的地址。这就是为什么最初创建了`subwallet_id`。`subwallet_id`存储在合约存储中,可以用于使用一个私钥创建许多不同的钱包(具有不同的子钱包ID)。当将不同钱包类型与交易所等集中服务集成时,这种功能非常有用。 + +根据TON区块链的源代码中的[代码行](https://github.com/ton-blockchain/ton/blob/4b940f8bad9c2d3bf44f196f6995963c7cee9cc3/tonlib/tonlib/TonlibClient.cpp#L2420),默认的`subwallet_id`值为`698983191`: + +```cpp +res.wallet_id = td::as(res.config.zero_state_id.root_hash.as_slice().data()); +``` + +可以从[配置文件](https://ton.org/global-config.json)中获取创世块信息(zero_state)。了解其复杂性和细节并非必要,但重要的是要记住`subwallet_id`的默认值为`698983191`。 + +每个钱包合约都会检查外部交易的subwallet_id字段,以避免将请求发送到具有不同ID的钱包的情况: + +```func +var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); +var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256)); +throw_unless(34, subwallet_id == stored_subwallet); +``` + +我们需要将以上的值添加到合约的初始数据中,所以变量需要保存如下: + + + + +```js +const subWallet = 698983191; +``` + + + + +```go +var subWallet uint64 = 698983191 +``` + + + + + +### 编译钱包代码 + +既然我们已经明确定义了私钥、公钥和子钱包ID,我们需要编译钱包代码。为此,我们将使用官方库中的[钱包v3代码](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet3-code.fc)。 + +为了编译钱包代码,我们需要使用[@ton-community/func-js](https://github.com/ton-community/func-js)库。使用这个库,我们可以编译FunC代码并检索包含代码的cell。要开始使用,需要安装库并将其保存(--save)到`package.json`中,如下所示: + +```bash +npm i --save @ton-community/func-js +``` + +我们将仅使用JavaScript来编译代码,因为用于编译代码的库基于JavaScript。 +但是,一旦编译完成,只要我们拥有编译后的cell的**base64输出**,就可以在其他编程语言(如Go等)中使用这些编译后的代码。 + +首先,我们需要创建两个文件:`wallet_v3.fc`和`stdlib.fc`。编译器和stdlib.fc库一起使用。库中创建了所有必需的基本函数,这些函数对应于`asm`指令。可以从[这里](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc)下载stdlib.fc文件。在`wallet_v3.fc`文件中,需要复制上面的代码。 + +现在,我们为我们正在创建的项目有了以下结构: + +``` +. +├── src/ +│ ├── main.ts +│ ├── wallet_v3.fc +│ └── stdlib.fc +├── nodemon.json +├── package-lock.json +├── package.json +└── tsconfig.json +``` + +:::info +如果您的IDE插件与`stdlib.fc`文件中的`() set_seed(int) impure asm "SETRAND";`冲突,这没关系。 +::: + +请记住,在`wallet_v3.fc`文件的开头添加以下行,以指示将在下面使用stdlib中的函数: + +```func +#include "stdlib.fc"; +``` + +现在,让我们编写代码来编译我们的智能合约并使用`npm run start:dev`来运行它: + +```js +import { compileFunc } from '@ton-community/func-js'; +import fs from 'fs'; // 我们使用fs来读取文件内容 +import { Cell } from '@ton/core'; + +const result = await compileFunc({ + targets: ['wallet_v3.fc'], // 您的项目的目标 + sources: { + "stdlib.fc": fs.readFileSync('./src/stdlib.fc', { encoding: 'utf-8' }), + "wallet_v3.fc": fs.readFileSync('./src/wallet_v3.fc', { encoding: 'utf-8' }), + } +}); + +if (result.status === 'error') { + console.error(result.message) + return; +} + +const codeCell = Cell.fromBoc(Buffer.from(result.codeBoc, "base64"))[0]; // 从base64编码的BOC中获取缓冲区,并从该缓冲区获取cell + +// 现在我们获得了包含编译代码的base64编码的BOC +console.log('Code BOC: ' + result.codeBoc); +console.log('\nHash: ' + codeCell.hash().toString('base64')); // 获取cell的哈希并将其转换为base64编码的字符串。我们将会在后面需要它 +``` + +终端的输出结果如下: + +```text +Code BOC: te6ccgEBCAEAhgABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQCW8oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOgwAaTIyx/LH8v/ye1UAATQMAIBSAYHABe7Oc7UTQ0z8x1wv/gAEbjJftRNDXCx+A== + +Hash: idlku00WfSC36ujyK2JVT92sMBEpCNRUXOGO4sJVBPA= +``` + +完成后,可以使用其他库和语言使用我们的钱包代码检索相同的cell(使用base64编码的输出): + + + + +```go +import ( + "encoding/base64" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +base64BOC := "te6ccgEBCAEAhgABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQCW8oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOgwAaTIyx/LH8v/ye1UAATQMAIBSAYHABe7Oc7UTQ0z8x1wv/gAEbjJftRNDXCx+A==" // 保存我们从编译器保存的base64编码输出到变量 +codeCellBytes, _ := base64.StdEncoding.DecodeString(base64BOC) // 解码base64以获取字节数组 +codeCell, err := cell.FromBOC(codeCellBytes) // 从字节数组获取包含代码的cell +if err != nil { // 检查是否有任何错误 + panic(err) +} + +log.Println("Hash:", base64.StdEncoding.EncodeToString(codeCell.Hash())) // 获取cell的哈希,将其编码为base64,因为它具有[]byte类型,并输出到终端 +``` + + + + + + +将会在终端输出以下内容: + +```text +idlku00WfSC36ujyK2JVT92sMBEpCNRUXOGO4sJVBPA= +``` + +完成上述过程后,确认我们的cell中正在使用正确的代码,因为哈希值相匹配。 + +### 创建部署的初始化状态 + +在构建交易之前,了解State Init非常重要。首先让我们了解[TL-B方案](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L141-L143): + +选项 | 说明 +:---: | :---: +split_depth | 此选项适用于可以拆分并位于多个[分片链](/learn/overviews/ton-blockchain#many-accountchains-shards)上的高负载智能合约。有关此工作原理的更多详细信息,请参见[tblkch.pdf](https://ton.org/tblkch.pdf)(4.1.6)。只存储`0`,因为它仅在钱包智能合约内使用。 +special | 用于TicTok。这些智能合约会在每个区块自动调用,常规智能合约不需要。关于此的信息可以在[此章节中](/develop/data-formats/transaction-layout#tick-tock)或[tblkch.pdf](https://ton.org/tblkch.pdf) 中找到。此规范中仅存储`0`,因为我们不需要此功能。 +code | `1`位表示智能合约代码的存在。 +data | `1`位表示智能合约数据的存在。 +library | 操作[主链](/learn/overviews/ton-blockchain#masterchain-blockchain-of-blockchains)上的库,可以由不同的智能合约使用。对于钱包,不会使用它,因此设置为`0`。有关此的信息可以在[tblkch.pdf](https://ton.org/tblkch.pdf)(1.8.4)中找到。 + +接下来我们将准备“初始数据”,这将在部署后立即出现在我们合约的存储中: + + + + +```js +import { beginCell } from '@ton/core'; + +const dataCell = beginCell(). + storeUint(0, 32). // Seqno + storeUint(698983191, 32). // Subwallet ID + storeBuffer(keyPair.publicKey). // Public Key + endCell(); +``` + + + + +```go +dataCell := cell.BeginCell(). + MustStoreUInt(0, 32). // Seqno + MustStoreUInt(698983191, 32). // Subwallet ID + MustStoreSlice(publicKey, 256). // Public Key + EndCell() +``` + + + + +在这个阶段,智能合约`代码`和`初始数据`都存在。有了这些数据,我们可以生成我们的**钱包地址**。钱包的地址取决于State Init,其中包括代码和初始数据。 + + + + +```js +import { Address } from '@ton/core'; + +const stateInit = beginCell(). + storeBit(0). // 没有split_depth + storeBit(0). // 没有special + storeBit(1). // 表示有代码 + storeRef(codeCell). + storeBit(1). // 表示有数据 + storeRef(dataCell). + storeBit(0). // 没有library + endCell(); + +const contractAddress = new Address(0, stateInit.hash()); // 获取stateInit的哈希,以获取我们的智能合约在`ID`为0的工作链中的地址 +console.log(`Contract address: ${contractAddress.toString()}`); // 将智能合约地址输出到控制台 +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/address" +) + +stateInit := cell.BeginCell(). + MustStoreBoolBit(false). // 没有split_depth + MustStoreBoolBit(false). // 没有special + MustStoreBoolBit(true). // 表示有代码 + MustStoreRef(codeCell). + MustStoreBoolBit(true). // 表示有数据 + MustStoreRef(dataCell). + MustStoreBoolBit(false). // 没有library + EndCell() + +contractAddress := address.NewAddress(0, 0, stateInit.Hash()) // 获取stateInit的哈希,以获取我们的智能合约在`ID`为0的工作链中的地址 +log.Println("Contract address:", contractAddress.String()) // 将智能合约地址输出到控制台 +``` + + + + +使用State Init,我们现在可以构建交易并发送到区块链。要执行此过程,需要一个最低交易余额为0.1 TON(余额可以更低,但此金额足够)。要完成这个操作,我们需要运行教程中提到的代码,获取正确的钱包地址,并向该地址发送0.1 TON。 + +让我们从构建类似于我们在**上一节**构建的交易开始: + + + + +```js +import { sign } from '@ton/crypto'; +import { toNano } from '@ton/core'; + +const internalMessageBody = beginCell(). + storeUint(0, 32). + storeStringTail("Hello, TON!"). + endCell(); + +const internalMessage = beginCell(). + storeUint(0x10, 6). // 不使用反弹 + storeAddress(Address.parse("put your first wallet address from were you sent 0.1 TON")). + storeCoins(toNano("0.03")). + storeUint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1). // 保存1表示body是引用 + storeRef(internalMessageBody). + endCell(); + +// 用于我们的钱包的交易 +const toSign = beginCell(). + storeUint(subWallet, 32). + storeUint(Math.floor(Date.now() / 1e3) + 60, 32). + storeUint(0, 32). // 我们将seqno设置为0,因为在部署之后,钱包将将0存储为seqno + storeUint(3, 8). + storeRef(internalMessage); + +const signature = sign(toSign.endCell().hash(), keyPair.secretKey); +const body = beginCell(). + storeBuffer(signature). + storeBuilder(toSign). + endCell(); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/tlb" + "time" +) + +internalMessageBody := cell.BeginCell(). + MustStoreUInt(0, 32). + MustStoreStringSnake("Hello, TON!"). + EndCell() + +internalMessage := cell.BeginCell(). + MustStoreUInt(0x10, 6). // 没有反弹 + MustStoreAddr(address.MustParseAddr("put your first wallet address from were you sent 0.1 TON")). + MustStoreBigCoins(tlb.MustFromTON("0.03").NanoTON()). + MustStoreUInt(1, 1 + 4 + 4 + 64 + 32 + 1 + 1). // 保存1表示body是引用 + MustStoreRef(internalMessageBody). + EndCell() + +// 用于我们的钱包的交易 +toSign := cell.BeginCell(). + MustStoreUInt(subWallet, 32). + MustStoreUInt(uint64(time.Now().UTC().Unix()+60), 32). + MustStoreUInt(0, 32). // 我们将seqno设置为0,因为在部署之后,钱包将将0存储为seqno + MustStoreUInt(3, 8). + MustStoreRef(internalMessage) + +signature := ed25519.Sign(privateKey, toSign.EndCell().Hash()) +body := cell.BeginCell(). + MustStoreSlice(signature, 512). + MustStoreBuilder(toSign). + EndCell() +``` + + + + +完成后,结果是正确的State Init和消息体。 + +### 发送外部交易 + +主要的区别将在外部消息的存在上,因为State Init被存储用于正确的合约部署。由于合约尚无自己的代码,因此无法处理任何内部消息。因此,接下来,我们将在成功部署后发送其代码和初始数据,以便可处理我们带有“Hello, TON!”评论的消息: + + + + +```js +const externalMessage = beginCell(). + storeUint(0b10, 2). // 表示它是一笔外部传入的交易 + storeUint(0, 2). // src -> addr_none + storeAddress(contractAddress). + storeCoins(0). // 导入费用 + storeBit(1). // 我们有State Init + storeBit(1). // 我们将State Init存储为引用 + storeRef(stateInit). // 将State Init存储为引用 + storeBit(1). // 我们将消息体存储为引用 + storeRef(body). // 将消息体存储为引用 + endCell(); +``` + + + + +```go +externalMessage := cell.BeginCell(). + MustStoreUInt(0b10, 2). // 表示它是一笔外部传入的交易 + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(contractAddress). + MustStoreCoins(0). // 导入费用 + MustStoreBoolBit(true). // 我们有State Init + MustStoreBoolBit(true). // 我们将State Init存储为引用 + MustStoreRef(stateInit). // 将State Init存储为引用 + MustStoreBoolBit(true). // 我们将消息体存储为引用 + MustStoreRef(body). // 将消息体存储为引用 + EndCell() +``` + + + + +最后,我们可以将我们的交易发送到区块链上部署我们的钱包并使用它。 + + + + +```js +import { TonClient } from '@ton/ton'; + +const client = new TonClient({ + endpoint: "https://toncenter.com/api/v2/jsonRPC", + apiKey: "put your api key" // 你可以从Telegram中的@tonapibot获得API密钥 +}); + +client.sendFile(externalMessage.toBoc()); +``` + + + + +```go +import ( + "context" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/tl" + "github.com/xssnick/tonutils-go/ton" +) + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) + +var resp tl.Serializable +err = client.Client().QueryLiteserver(context.Background(), ton.SendMessage{Body: externalMessage.ToBOCWithFlags(false)}, &resp) +if err != nil { + log.Fatalln(err.Error()) + return +} +``` + + + + +请注意,我们使用mode`3`发送了一个内部消息。如果需要重复部署相同的钱包,**智能合约将被销毁**。为此,请正确设置的mode,通过添加128(取整个智能合约的余额)+ 32(销毁智能合约),以获取剩余的TON余额并再次部署钱包。 + +重要说明:对于每个新的交易,**seqno需要增加1**。 + +:::info +我们使用的合约代码是[已验证的](https://tonscan.org/tx/BL9T1i5DjX1JRLUn4z9JOgOWRKWQ80pSNevis26hGvc=),因此您可以在此处查看一个[示例](https://tonscan.org/address/EQDBjzo_iQCZh3bZSxFnK9ue4hLTOKgsCNKfC8LOUM4SlSCX#source)。 +::: + +## 💸 使用钱包智能合约 + +在完成本教程的前半部分后,我们现在对钱包智能合约以及它们的开发和使用有了更深入的了解。我们学习了如何部署和销毁它们,以及如何在不依赖预配置的库函数的情况下发送消息。为了更多地应用我们上面学到的知识,在下一部分中,我们将专注于构建和发送更复杂的消息。 + +### 同时发送多条消息 + +正如您可能已经知道的,[一个cell可以存储最多1023位的数据和最多4个指向其他cells的引用](/develop/data-formats/cell-boc#cell)。在本教程的第一部分中,我们详细介绍了内部消息是如何以“整体”循环作为链接发送的。这意味着可以**在外部消息内存储多达4条内部消息**。这允许同时发送四笔交易。 + +为了实现这一点,需要创建4个不同的内部消息。我们可以手动创建,也可以通过`循环(loop)`来创建。我们需要定义3个数组:TON金额数组,评论数组,消息数组。对于消息,我们需要准备另一个数组 - internalMessages。 + + + + +```js +import { Cell } from '@ton/core'; + +const internalMessagesAmount = ["0.01", "0.02", "0.03", "0.04"]; +const internalMessagesComment = [ + "Hello, TON! #1", + "Hello, TON! #2", + "", // 我们让第三笔交易不留评论 + "Hello, TON! #4" +] +const destinationAddresses = [ + "输入属于你的任何地址", + "输入属于你的任何地址", + "输入属于你的任何地址", + "输入属于你的任何地址" +] // 所有4个地址可以相同 + +let internalMessages:Cell[] = []; // 存储我们内部消息的数组 +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/tvm/cell" +) + +internalMessagesAmount := [4]string{"0.01", "0.02", "0.03", "0.04"} +internalMessagesComment := [4]string{ + "Hello, TON! #1", + "Hello, TON! #2", + "", // 我们让第三笔交易不留评论 + "Hello, TON! #4", +} +destinationAddresses := [4]string{ + "输入属于你的任何地址", + "输入属于你的任何地址", + "输入属于你的任何地址", + "输入属于你的任何地址", +} // 所有4个地址可以相同 + +var internalMessages [len(internalMessagesAmount)]*cell.Cell // 存储我们内部消息的数组 +``` + + + + +所有消息的[发送模式](/develop/smart-contracts/messages#message-modes)都设置为`mode 3`。但是,如果需要不同的模式,则可以创建一个数组来满足不同的目的。 + + + + +```js +import { Address, beginCell, toNano } from '@ton/core'; + +for (let index = 0; index < internalMessagesAmount.length; index++) { + const amount = internalMessagesAmount[index]; + + let internalMessage = beginCell(). + storeUint(0x18, 6). // bounce + storeAddress(Address.parse(destinationAddresses[index])). + storeCoins(toNano(amount)). + storeUint(0, 1 + 4 + 4 + 64 + 32 + 1); + + /* + 在这个阶段,并不清楚我们是否会有一个消息体。 + 所以只设置stateInit的一位,如果我们有评论,那意味着 + 我们有一个消息体。在这种情况下,将位设置为1并将 + 体作为引用存储。 + */ + + if(internalMessagesComment[index] != "") { + internalMessage.storeBit(1) // 我们将消息体作为引用存储 + + let internalMessageBody = beginCell(). + storeUint(0, 32). + storeStringTail(internalMessagesComment[index]). + endCell(); + + internalMessage.storeRef(internalMessageBody); + } + else + /* + 由于我们没有消息体,我们表明这个消息 + 中有消息体,但不写入,意味着它不存在。 + 在这种情况下,只需设置位为0。 + */ + internalMessage.storeBit(0); + + internalMessages.push(internalMessage.endCell()); +} +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" +) + +for i := 0; i < len(internalMessagesAmount); i++ { + amount := internalMessagesAmount[i] + + internalMessage := cell.BeginCell(). + MustStoreUInt(0x18, 6). // bounce + MustStoreAddr(address.MustParseAddr(destinationAddresses[i])). + MustStoreBigCoins(tlb.MustFromTON(amount).NanoTON()). + MustStoreUInt(0, 1+4+4+64+32+1) + + /* + 在这个阶段,并不清楚我们是否会有一个消息体。 + 所以只设置stateInit的一位,如果我们有评论,那意味着 + 我们有一个消息体。在这种情况下,将位设置为1并将 + 体作为引用存储。 + */ + + if internalMessagesComment[i] != "" { + internalMessage.MustStoreBoolBit(true) // 我们将消息体作为引用存储 + + internalMessageBody := cell.BeginCell(). + MustStoreUInt(0, 32). + MustStoreStringSnake(internalMessagesComment[i]). + EndCell() + + internalMessage.MustStoreRef(internalMessageBody) + } else { + /* + 由于我们没有消息体,我们表明这个消息 + 中有消息体,但不写入,意味着它不存在。 + 在这种情况下,只需设置位为0。 + */ + internalMessage.MustStoreBoolBit(false) + } + internalMessages[i] = internalMessage.EndCell() +} +``` + + + + +现在让我们利用[第二章](/develop/smart-contracts/tutorials/wallet#-deploying-our-wallet)的知识,为我们的钱包构建一个可以同时发送4笔交易的交易: + + + + +```js +import { TonClient } from '@ton/ton'; +import { mnemonicToWalletKey } from '@ton/crypto'; + +const walletAddress = Address.parse('输入你的钱包地址'); +const client = new TonClient({ + endpoint: "https://toncenter.com/api/v2/jsonRPC", + apiKey: "输入你的api密钥" // 你可以从Telegram中的@tonapibot机器人获取api密钥 +}); + +const mnemonic = '输入你的助记词'; // word1 word2 word3 +let getMethodResult = await client.runMethod(walletAddress, "seqno"); // 从你的钱包合约运行"seqno"GET方法 +let seqno = getMethodResult.stack.readNumber(); // 从响应中获取seqno + +const mnemonicArray = mnemonic.split(' '); // 从字符串获取数组 +const keyPair = await mnemonicToWalletKey(mnemonicArray); // 从助记词获取密钥对 + +let toSign = beginCell(). + storeUint(698983191, 32). // subwallet_id + storeUint(Math.floor(Date.now() / 1e3) + 60, 32). // 交易过期时间,+60 = 1分钟 + storeUint(seqno, 32); // 存储seqno + // 别忘了,如果我们使用Wallet V4,我们需要添加 storeUint(0, 8). +``` + + + + +```go +import ( + "context" + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/ton" + "golang.org/x/crypto/pbkdf2" + "log" + "strings" + "time" +) + +walletAddress := address.MustParseAddr("输入你的钱包地址") + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) + +mnemonic := strings.Split("输入你的助记词", " ") // word1 word2 word3 +// 以下三行代码将使用助记词提取私钥。 +// 我们不会深入讲解密码学细节。在tonutils-go库中,这一切都已经实现, +// 但它立即返回带有地址和现成方法的钱包对象。 +// 所以我们必须单独编写获取密钥的代码行。Goland IDE会自动导入 +// 所需的库(crypto, pbkdf2等)。 +mac := hmac.New(sha512.New, []byte(strings.Join(mnemonic, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // 在TON库中使用"TON default seed"作为提取密钥时的salt +// 32是密钥长度 +privateKey := ed25519.NewKeyFromSeed(k) // 获取私钥 + +block, err := client.CurrentMasterchainInfo(context.Background()) // 获取当前区块,我们在向LiteServer请求时会用到它 +if err != nil { + log.Fatalln("CurrentMasterchainInfo err:", err.Error()) + return +} + +getMethodResult, err := client.RunGetMethod(context.Background(), block, walletAddress, "seqno") // 从你的钱包合约运行"seqno"GET方法 +if err != nil { + log.Fatalln("RunGetMethod err:", err.Error()) + return +} +seqno := getMethodResult.MustInt(0) // 从响应中获取seqno + +toSign := cell.BeginCell(). + MustStoreUInt(698983191, 32). // subwallet_id | 我们之后考虑这个 + MustStoreUInt(uint64(time.Now().UTC().Unix()+60), 32). // 交易过期时间,+60 = 1分钟 + MustStoreUInt(seqno.Uint64(), 32) // 存储seqno + // 别忘了,如果我们使用Wallet V4,我们需要添加 MustStoreUInt(0, 8). +``` + + + + +接下来,我们将在循环中添加之前构建的消息: + + + + +```js +for (let index = 0; index < internalMessages.length; index++) { + const internalMessage = internalMessages[index]; + toSign.storeUint(3, 8) // 存储我们内部交易的mode + toSign.storeRef(internalMessage) // 将我们的内部消息作为引用存储 +} +``` + + + + +```go +for i := 0; i < len(internalMessages); i++ { + internalMessage := internalMessages[i] + toSign.MustStoreUInt(3, 8) // 存储我们内部交易的mode + toSign.MustStoreRef(internalMessage) // 将我们的内部消息作为引用存储 +} +``` + + + + +完成上述过程后,让我们**签名**我们的消息,**构建一个外部消息**(如本教程前几节所述)并**将其发送**到区块链: + + + + +```js +import { sign } from '@ton/crypto'; + +let signature = sign(toSign.endCell().hash(), keyPair.secretKey); // 获取我们钱包智能合约的消息的哈希并签名以获得签名 + +let body = beginCell(). + storeBuffer(signature). // 存储签名 + storeBuilder(toSign). // 存储我们的消息 + endCell(); + +let externalMessage = beginCell(). + storeUint(0b10, 2). // ext_in_msg_info$10 + storeUint(0, 2). // src -> addr_none + storeAddress(walletAddress). // 目的地址 + storeCoins(0). // 引入费 + storeBit(0). // 无State Init + storeBit(1). // 我们将消息体作为引用存储 + storeRef(body). // 将消息体作为引用存储 + endCell(); + +client.sendFile(externalMessage.toBoc()); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/tl" +) + +signature := ed25519.Sign(privateKey, toSign.EndCell().Hash()) // 获取我们钱包智能合约的消息的哈希并签名以获得签名 + +body := cell.BeginCell(). + MustStoreSlice(signature, 512). // 存储签名 + MustStoreBuilder(toSign). // 存储我们的消息 + EndCell() + +externalMessage := cell.BeginCell(). + MustStoreUInt(0b10, 2). // ext_in_msg_info$10 + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(walletAddress). // 目的地址 + MustStoreCoins(0). // 引入费 + MustStoreBoolBit(false). // 无State Init + MustStoreBoolBit(true). // 我们将消息体作为引用存储 + MustStoreRef(body). // 将消息体作为引用存储 + EndCell() + +var resp tl.Serializable +err = client.Client().QueryLiteserver(context.Background(), ton.SendMessage{Body: externalMessage.ToBOCWithFlags(false)}, &resp) + +if err != nil { + log.Fatalln(err.Error()) + return +} +``` + + + + +:::info 连接错误 +如果出现与轻服务器(lite-server)连接相关的错误(Golang),必须重复运行代码,直到能够发送交易。这是因为tonutils-go库通过代码中指定的全局配置使用了几个不同的轻服务器,但并非所有轻服务器都能接受我们的连接。 +::: + +完成此过程后,可以使用TON区块链浏览器来验证钱包是否已向之前指定的地址发送了四笔交易。 + +### NFT 转移 + +除了常规交易之外,用户经常彼此发送 NFT。不幸的是,并非所有库都包含为这种智能合约量身定制的方法。因此,我们需要创建代码,使我们能够构建发送 NFT 的交易。首先,让我们更熟悉 TON NFT [标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md)。 + +特别是,我们需要详细了解用于 [NFT 转移](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#1-transfer) 的 TL-B。 + +- `query_id`:查询 ID 在交易处理方面没有价值。NFT 合约不验证它;它只是读取它。当服务希望为其每个交易分配特定的查询 ID 以供识别之用时,此值可能会有用。因此,我们将其设置为 0。 + +- `response_destination`:处理所有权变更交易后会有额外的 TON。它们将发送到此地址,如果指定了的话,否则保留在 NFT 余额中。 + +- `custom_payload`:custom_payload 需要用来执行特定任务,并且不与普通 NFT 一起使用。 + +- `forward_amount`:如果 forward_amount 不为零,指定的 TON 数量将发送给新所有者。这样,新所有者将被通知他们收到了某物。 + +- `forward_payload`:forward_payload 是可以与 forward_amount 一起发送给新所有者的附加数据。例如,使用 forward_payload 允许用户在转移 NFT 时[添加评论](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#forward_payload-format),如本教程前面所示。然而,尽管 TON 的 NFT 标准中写有 forward_payload,区块链浏览器并不完全支持显示各种细节。显示 Jettons 时也存在同样的问题。 + +现在让我们构建交易本身: + + + + +```js +import { Address, beginCell, toNano } from '@ton/core'; + +const destinationAddress = Address.parse("put your wallet where you want to send NFT"); +const walletAddress = Address.parse("put your wallet which is the owner of NFT") +const nftAddress = Address.parse("put your nft address"); + +// 我们可以添加评论,但由于目前尚未得到支持,因此不会在浏览器中显示。 +const forwardPayload = beginCell(). + storeUint(0, 32). + storeStringTail("Hello, TON!"). + endCell(); + +const transferNftBody = beginCell(). + storeUint(0x5fcc3d14, 32). // NFT 转移的操作码 + storeUint(0, 64). // query_id + storeAddress(destinationAddress). // new_owner + storeAddress(walletAddress). // response_destination 的超额部分 + storeBit(0). // 我们没有 custom_payload + storeCoins(toNano("0.01")). // forward_amount + storeBit(1). // 我们以引用的形式存储 forward_payload + storeRef(forwardPayload). // 以引用的形式存储 forward_payload + endCell(); + +const internalMessage = beginCell(). + storeUint(0x18, 6). // 弹回 + storeAddress(nftAddress). + storeCoins(toNano("0.05")). + storeUint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1). // 我们存储 1 表示我们有body作为引用 + storeRef(transferNftBody). + endCell(); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +destinationAddress := address.MustParseAddr("put your wallet where you want to send NFT") +walletAddress := address.MustParseAddr("put your wallet which is the owner of NFT") +nftAddress := address.MustParseAddr("put your nft address") + +// 我们可以添加评论,但因为目前不支持,所以不会显示在浏览器中。 +forwardPayload := cell.BeginCell(). + MustStoreUInt(0, 32). + MustStoreStringSnake("Hello, TON!"). + EndCell() + +transferNftBody := cell.BeginCell(). + MustStoreUInt(0x5fcc3d14, 32). // NFT 转移的操作码 + MustStoreUInt(0, 64). // query_id + MustStoreAddr(destinationAddress). // new_owner + MustStoreAddr(walletAddress). // response_destination 的超额部分 + MustStoreBoolBit(false). // 我们没有 custom_payload + MustStoreBigCoins(tlb.MustFromTON("0.01").NanoTON()). // forward_amount + MustStoreBoolBit(true). // 我们以引用的形式存储 forward_payload + MustStoreRef(forwardPayload). // 以引用的形式存储 forward_payload + EndCell() + +internalMessage := cell.BeginCell(). + MustStoreUInt(0x18, 6). // 弹回 + MustStoreAddr(nftAddress). + MustStoreBigCoins(tlb.MustFromTON("0.05").NanoTON()). + MustStoreUInt(1, 1 + 4 + 4 + 64 + 32 + 1 + 1). // 我们存储 1 表示我们有body作为引用 + MustStoreRef(transferNftBody). + EndCell() +``` + + + + +NFT 转移操作码来自[相同的标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#tl-b-schema)。 +现在让我们完成交易,按本教程前面部分的布局。完成交易所需的正确代码可在 [GitHub 库](/develop/smart-contracts/tutorials/wallet#source-code)中找到。 + +使用 Jettons 也可以完成相同的程序。要进行此过程,请阅读有关 jettons 转移的 TL-B [标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md)。特别是,NFT 和 Jettons 转移之间存在一些小差异。 + +### Wallet v3 和 Wallet v4 的 Get 方法 + +智能合约经常使用 [GET 方法](/develop/smart-contracts/guidelines/get-methods),但它们不在区块链内部运行,而是在客户端上运行。GET 方法有许多用途,为智能合约提供对不同数据类型的访问。例如,NFT 智能合约中的 [get_nft_data() 方法](https://github.com/ton-blockchain/token-contract/blob/991bdb4925653c51b0b53ab212c53143f71f5476/nft/nft-item.fc#L142-L145) 允许用户检索特定的内容、所有者和 NFT 集合信息。 + +下面我们将了解 V3 和 V4 钱包使用的 GET 方法的基础知识: + +方法 | 说明 +:---: | :---: +int seqno() | 该方法需要用来接收当前的 seqno 并发送带有正确值的交易。在本教程的前几节中,该方法被频繁调用。 +int get_public_key() | 该方法用于检索公钥。get_public_key() 并不广泛使用,可以被不同的服务使用。例如,一些 API 服务允许检索具有相同公钥的多个钱包 + +现在,我们转向只有 V4 钱包使用的方法: + +方法 | 说明 +:---: | :---: +int get_subwallet_id() | 教程前面已经考虑过这个。此方法允许您检索 subwallet_id。 +int is_plugin_installed(int wc, int addr_hash) | 让我们知道插件是否已安装。调用此方法时,需要传递 [工作链](/learn/overviews/ton-blockchain#workchain-blockchain-with-your-own-rules) 和插件地址哈希。 +tuple get_plugin_list() | 此方法返回已安装插件的地址。 + +让我们考虑 `get_public_key` 和 `is_plugin_installed` 方法。选择这两种方法是因为,首先我们需要从 256 位数据中获取公钥,然后我们需要学习如何向 GET 方法传递切片和不同类型的数据。这对于我们正确使用这些方法非常有用。 + +首先,我们需要一个能够发送请求的客户端。因此,我们将使用特定的钱包地址([EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF](https://tonscan.org/address/EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF))作为例子: + + + + +```js +import { TonClient } from '@ton/ton'; +import { Address } from '@ton/core'; + +const client = new TonClient({ + endpoint: "https://toncenter.com/api/v2/jsonRPC", + apiKey: "put your api key" // 你可以从 Telegram 中的 @tonapibot 机器人获取 api 密钥 +}); + +const walletAddress = Address.parse("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"); // 以我的钱包地址为例 +``` + + + + +```go +import ( + "context" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/ton" + "log" +) + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) + +block, err := client.CurrentMasterchainInfo(context.Background()) // 获取当前区块, 我们将需要它用于向 LiteServer 发送请求 +if err != nil { + log.Fatalln("CurrentMasterchainInfo err:", err.Error()) + return +} + +walletAddress := address.MustParseAddr("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF") // 以我的钱包地址为例 +``` + + + + +现在我们需要调用钱包的 GET 方法。 + + + + +```js +// 我总是调用 runMethodWithError 而不是 runMethod,以便能够检查被调用方法的 exit_code。 +let getResult = await client.runMethodWithError(walletAddress, "get_public_key"); // 运行 get_public_key GET 方法 +const publicKeyUInt = getResult.stack.readBigNumber(); // 读取包含 uint256 的回答 +const publicKey = publicKeyUInt.toString(16); // 从 bigint(uint256)获取十六进制字符串 +console.log(publicKey) +``` + + + + +```go +getResult, err := client.RunGetMethod(context.Background(), block, walletAddress, "get_public_key") // 运行 get_public_key GET 方法 +if err != nil { + log.Fatalln("RunGetMethod err:", err.Error()) + return +} + +// 我们有一个包含值的数组作为回应,并且在读取它时应该指定索引 +// 在 get_public_key 的情况下,我们只有一个返回值,存储在 0 索引处 +publicKeyUInt := getResult.MustInt(0) // 读取包含 uint256 的回答 +publicKey := publicKeyUInt.Text(16) // 从 bigint(uint256)获取十六进制字符串 +log.Println(publicKey) +``` + + + + +调用成功完成后,最终结果是一个极大的 256 位数,必须转换为十六进制字符串。对于我们提供的钱包地址,结果十六进制字符串如下:`430db39b13cf3cb76bfa818b6b13417b82be2c6c389170fbe06795c71996b1f8`。 +接下来,我们使用 [TonAPI](https://tonapi.io/swagger-ui)(/v1/wallet/findByPubkey 方法),通过输入获得的十六进制字符串到系统中,立即就可以清楚,答复内数组的第一个元素将识别我的钱包。 + +然后我们转向 `is_plugin_installed` 方法。作为例子,我们将再次使用之前使用的钱包([EQAM7M--HGyfxlErAIUODrxBA3yj5roBeYiTuy6BHgJ3Sx8k](https://tonscan.org/address/EQAM7M--HGyfxlErAIUODrxBA3yj5roBeYiTuy6BHgJ3Sx8k))和插件([EQBTKTis-SWYdupy99ozeOvnEBu8LRrQP_N9qwOTSAy3sQSZ](https://tonscan.org/address/EQBTKTis-SWYdupy99ozeOvnEBu8LRrQP_N9qwOTSAy3sQSZ)): + + + + +```js +const oldWalletAddress = Address.parse("EQAM7M--HGyfxlErAIUODrxBA3yj5roBeYiTuy6BHgJ3Sx8k"); // 我的旧钱包地址 +const subscriptionAddress = Address.parseFriendly("EQBTKTis-SWYdupy99ozeOvnEBu8LRrQP_N9qwOTSAy3sQSZ"); // 已经安装在钱包上的订阅插件地址 +``` + + + + +```go +oldWalletAddress := address.MustParseAddr("EQAM7M--HGyfxlErAIUODrxBA3yj5roBeYiTuy6BHgJ3Sx8k") +subscriptionAddress := address.MustParseAddr("EQBTKTis-SWYdupy99ozeOvnEBu8LRrQP_N9qwOTSAy3sQSZ") // 已经安装在钱包上的订阅插件地址 +``` + + + + +现在我们需要检索插件的哈希地址,以便地址可以转换成数字并发送给 GET 方法。 + + + + +```js +const hash = BigInt(`0x${subscriptionAddress.address.hash.toString("hex")}`) ; + +getResult = await client.runMethodWithError(oldWalletAddress, "is_plugin_installed", +[ + {type: "int", value: BigInt("0")}, // 作为 int 传递 workchain + {type: "int", value: hash} // 作为 int 传递插件地址哈希 +]); +console.log(getResult.stack.readNumber()); // -1 +``` + + + + +```go +import ( + "math/big" +) + +hash := big.NewInt(0).SetBytes(subscriptionAddress.Data()) +// runGetMethod 会自动识别传递值的类型 +getResult, err = client.RunGetMethod(context.Background(), block, oldWalletAddress, + "is_plugin_installed", + 0, // 传递工作链 + hash) // 传递插件地址 +if err != nil { + log.Fatalln("RunGetMethod err:", err.Error()) + return +} + +log.Println(getResult.MustInt(0)) // -1 +``` + + + + +响应必须是 `-1`,意味着结果是真的。如果需要的话,也可以发送切片和cell。创建切片或cell并将其传递替代 BigInt 就足够了,指定相应的类型。 + +### 通过钱包部署合约 + +在第三章中,我们部署了一个钱包。为此,我们最初发送了一些TON,然后从钱包发送了一笔交易以部署一个智能合约。然而,这个过程并不常用于外部交易,通常主要用于钱包。在开发合约时,部署过程是通过发送内部消息来初始化的。 + +为了完成这个过程,我们将使用在[第三章](/develop/smart-contracts/tutorials/wallet#compiling-our-wallet-code)中使用的V3R2钱包智能合约。在这种情况下,我们将`subwallet_id`设置为`3`或者使用相同的私钥检索另一个地址时需要的任何其他数字(它是可变的): + + + + +```js +import { beginCell, Cell } from '@ton/core'; +import { mnemonicToWalletKey } from '@ton/crypto'; + +const mnemonicArray = 'put your mnemonic'.split(" "); +const keyPair = await mnemonicToWalletKey(mnemonicArray); // 从助记词提取私钥和公钥 + +const codeCell = Cell.fromBase64('te6ccgEBCAEAhgABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQCW8oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOgwAaTIyx/LH8v/ye1UAATQMAIBSAYHABe7Oc7UTQ0z8x1wv/gAEbjJftRNDXCx+A=='); +const dataCell = beginCell(). + storeUint(0, 32). // Seqno + storeUint(3, 32). // 子钱包ID + storeBuffer(keyPair.publicKey). // 公钥 + endCell(); + +const stateInit = beginCell(). + storeBit(0). // 没有 split_depth + storeBit(0). // 没有特殊 + storeBit(1). // 我们有代码 + storeRef(codeCell). + storeBit(1). // 我们有数据 + storeRef(dataCell). + storeBit(0). // 没有库 + endCell(); +``` + + + + +```go +import ( + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "encoding/base64" + "github.com/xssnick/tonutils-go/tvm/cell" + "golang.org/x/crypto/pbkdf2" + "strings" +) + +mnemonicArray := strings.Split("put your mnemonic", " ") +// 下面的三行将使用助记词短语提取私钥。 +// 我们不会深入讨论加密细节。在tonutils-go库中,这些都已实现, +// 但它直接返回的是带有地址和准备好的方法的完成的钱包对象。 +// 因此,我们必须单独编写代码行来获取密钥。Goland IDE将自动导入 +// 所需的所有库(crypto, pbkdf2等)。 +mac := hmac.New(sha512.New, []byte(strings.Join(mnemonicArray, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // 在TON库中,使用"TON default seed"作为获取密钥时的salt +// 32 是密钥长度 +privateKey := ed25519.NewKeyFromSeed(k) // 获取私钥 +publicKey := privateKey.Public().(ed25519.PublicKey) // 从私钥获取公钥 + +BOCBytes, _ := base64.StdEncoding.DecodeString("te6ccgEBCAEAhgABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQCW8oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOgwAaTIyx/LH8v/ye1UAATQMAIBSAYHABe7Oc7UTQ0z8x1wv/gAEbjJftRNDXCx+A==") +codeCell, _ := cell.FromBOC(BOCBytes) +dataCell := cell.BeginCell(). + MustStoreUInt(0, 32). // Seqno + MustStoreUInt(3, 32). // 子钱包ID + MustStoreSlice(publicKey, 256). // 公钥 + EndCell() + +stateInit := cell.BeginCell(). + MustStoreBoolBit(false). // 没有 split_depth + MustStoreBoolBit(false). // 没有特殊 + MustStoreBoolBit(true). // 我们有代码 + MustStoreRef(codeCell). + MustStoreBoolBit(true). // 我们有数据 + MustStoreRef(dataCell). + MustStoreBoolBit(false). // 没有库 + EndCell() +``` + + + + +接下来我们将从我们的合约中获取地址并构建内部消息。同时,我们将向我们的交易中添加"Deploying..."评论。 + + + + +```js +import { Address, toNano } from '@ton/core'; + +const contractAddress = new Address(0, stateInit.hash()); // 获取stateInit的哈希来获取我们的智能合约在工作链ID为0的地址 +console.log(`合约地址: ${contractAddress.toString()}`); // 输出合约地址到控制台 + +const internalMessageBody = beginCell(). + storeUint(0, 32). + storeStringTail('Deploying...'). + endCell(); + +const internalMessage = beginCell(). + storeUint(0x10, 6). // 无弹回 + storeAddress(contractAddress). + storeCoins(toNano('0.01')). + storeUint(0, 1 + 4 + 4 + 64 + 32). + storeBit(1). // 我们有State Init + storeBit(1). // 我们将State Init作为引用存储 + storeRef(stateInit). // 将State Init作为引用存储 + storeBit(1). // 我们将消息体作为引用存储 + storeRef(internalMessageBody). // 将消息体Init作为引用存储 + endCell(); +``` + + + + +```go +import ( + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "log" +) + +contractAddress := address.NewAddress(0, 0, stateInit.Hash()) // 获取stateInit的哈希来获取我们的智能合约在工作链ID为0的地址 +log.Println("合约地址:", contractAddress.String()) // 输出合约地址到控制台 + +internalMessageBody := cell.BeginCell(). + MustStoreUInt(0, 32). + MustStoreStringSnake("Deploying..."). + EndCell() + +internalMessage := cell.BeginCell(). + MustStoreUInt(0x10, 6). // 不反弹 + MustStoreAddr(contractAddress). + MustStoreBigCoins(tlb.MustFromTON("0.01").NanoTON()). + MustStoreUInt(0, 1+4+4+64+32). + MustStoreBoolBit(true). // 我们有State Init + MustStoreBoolBit(true). // 我们将State Init作为引用存储 + MustStoreRef(stateInit). // 将State Init作为引用存储 + MustStoreBoolBit(true). // 我们将消息体作为引用存储 + MustStoreRef(internalMessageBody). // 将消息体Init作为引用存储 + EndCell() +``` + + + + +:::info +请注意,上述中已指定位,并且stateInit和internalMessageBody已作为引用保存。由于链接是分开存储的,我们可以写4(0b100)+ 2(0b10)+ 1(0b1)->(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)即(0b111, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1),然后保存两个引用。 +::: + +接下来,我们将为我们的钱包准备一条消息并发送它: + + + + +```js +import { TonClient } from '@ton/ton'; +import { sign } from '@ton/crypto'; + +const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + apiKey: 'put your api key' // 您可以从Telegram中的@tonapibot bot获取api key +}); + +const walletMnemonicArray = 'put your mnemonic'.split(' '); +const walletKeyPair = await mnemonicToWalletKey(walletMnemonicArray); // 从助记词提取私钥和公钥 +const walletAddress = Address.parse('用来部署的你的钱包地址'); +const getMethodResult = await client.runMethod(walletAddress, 'seqno'); // 从你的钱包合约运行"seqno" GET方法 +const seqno = getMethodResult.stack.readNumber(); // 从回应中获取seqno + +// 我们钱包的交易 +const toSign = beginCell(). + storeUint(698983191, 32). // 子钱包id + storeUint(Math.floor(Date.now() / 1e3) + 60, 32). // 交易过期时间, +60 = 1 分钟 + storeUint(seqno, 32). // 存储seqno + // 不要忘记如果我们使用钱包V4,我们需要添加storeUint(0, 8). + storeUint(3, 8). + storeRef(internalMessage); + +const signature = sign(toSign.endCell().hash(), walletKeyPair.secretKey); // 获取我们发往钱包智能合约的消息hash并签名以获取签名 +const body = beginCell(). + storeBuffer(signature). // 存储签名 + storeBuilder(toSign). // 存储我们的消息 + endCell(); + +const external = beginCell(). + storeUint(0b10, 2). // 表示这是一个传入的外部交易 + storeUint(0, 2). // src -> addr_none + storeAddress(walletAddress). + storeCoins(0). // 导入费 + storeBit(0). // 我们没有State Init + storeBit(1). // 我们将消息体作为引用存储 + storeRef(body). // 将消息体作为引用存储 + endCell(); + +console.log(external.toBoc().toString('base64')); +client.sendFile(external.toBoc()); +``` + + + + +```go +import ( + "context" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/tl" + "github.com/xssnick/tonutils-go/ton" + "time" +) + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) + +block, err := client.CurrentMasterchainInfo(context.Background()) // 获取当前区块,我们在请求LiteServer时需要它 +if err != nil { + log.Fatalln("CurrentMasterchainInfo 错误:", err.Error()) + return +} + +walletMnemonicArray := strings.Split("put your mnemonic", " ") +mac = hmac.New(sha512.New, []byte(strings.Join(walletMnemonicArray, " "))) +hash = mac.Sum(nil) +k = pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // 在TON库中,使用"TON default seed"作为获取密钥时的salt +// 32 是密钥长度 +walletPrivateKey := ed25519.NewKeyFromSeed(k) // 获取私钥 +walletAddress := address.MustParseAddr("用来部署的你的钱包地址") + +getMethodResult, err := client.RunGetMethod(context.Background(), block, walletAddress, "seqno") // 从你的钱包合约运行"seqno" GET方法 +if err != nil { + log.Fatalln("RunGetMethod 错误:", err.Error()) + return +} +seqno := getMethodResult.MustInt(0) // 从回应中获取seqno + +toSign := cell.BeginCell(). + MustStoreUInt(698983191, 32). // 子钱包id | 我们稍后考虑这个 + MustStoreUInt(uint64(time.Now().UTC().Unix()+60), 32). // 交易过期时间, +60 = 1 分钟 + MustStoreUInt(seqno.Uint64(), 32). // 存储seqno + // 不要忘记如果我们使用钱包V4,我们需要添加MustStoreUInt(0, 8). + MustStoreUInt(3, 8). // 存储我们内部交易的模式 + MustStoreRef(internalMessage) // 将我们的内部消息作为引用存储 + +signature := ed25519.Sign(walletPrivateKey, toSign.EndCell().Hash()) // 获取我们发往钱包智能合约的消息hash并签名以获取签名 + +body := cell.BeginCell(). + MustStoreSlice(signature, 512). // 存储签名 + MustStoreBuilder(toSign). // 存储我们的消息 + EndCell() + +externalMessage := cell.BeginCell(). + MustStoreUInt(0b10, 2). // ext_in_msg_info$10 + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(walletAddress). // 目的地址 + MustStoreCoins(0). // 导入费 + MustStoreBoolBit(false). // 没有State Init + MustStoreBoolBit(true). // 我们将消息体作为引用存储 + MustStoreRef(body). // 将消息体作为引用存储 + EndCell() + +var resp tl.Serializable +err = client.Client().QueryLiteserver(context.Background(), ton.SendMessage{Body: externalMessage.ToBOCWithFlags(false)}, &resp) + +if err != nil { + log.Fatalln(err.Error()) + return +} +``` + + + + +这就结束了我们和普通钱包的工作。在这个阶段,您应该对如何与钱包智能合约互动,发送交易,以及能够使用各种库类型有一个深入的了解。 + +## 🔥 高负载钱包 + +在某些情况下,可能需要一次发送大量的交易。如前所述,普通钱包支持一次发送最多4笔交易,这是通过在单个cell中存储[最多4个引用](/develop/data-formats/cell-boc#cell)来支持的。高负载钱包则允许一次发送255笔交易。这个限制的存在是因为区块链的配置设置中对外部消息(动作)的最大数量设定为255。 + +交易所可能是使用高负载钱包的最佳示例。像币安这样的大型交易所有着极大的用户基础,这意味着在短时间内会处理大量的交易提款请求。高负载钱包有助于处理这些提款请求。 + +### 高负载钱包 FunC 代码 + +首先,让我们查看[高负载钱包智能合约的代码结构](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet-v2-code.fc): + +```func +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); ;; 从消息体中获取签名 + var cs = in_msg; + var (subwallet_id, query_id) = (cs~load_uint(32), cs~load_uint(64)); ;; 从消息体中获取其余值 + var bound = (now() << 32); ;; 位左移操作 + throw_if(35, query_id < bound); ;; 如果交易已过期则抛出错误 + var ds = get_data().begin_parse(); + var (stored_subwallet, last_cleaned, public_key, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict()); ;; 从存储中读取值 + ds.end_parse(); ;; 确保 ds 中没有任何东西 + (_, var found?) = old_queries.udict_get?(64, query_id); ;; 检查是否已经存在此类请求 + throw_if(32, found?); ;; 如果是则抛出错误 + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + var dict = cs~load_dict(); ;; 获取包含消息的字典 + cs.end_parse(); ;; 确保 cs 中没有任何东西 + accept_message(); +``` + +> 💡 有用的链接: +> +> [位运算](/develop/func/stdlib/#dict_get) +> +> [load_dict()](/develop/func/stdlib/#load_dict) +> +> [udict_get?()](/develop/func/stdlib/#dict_get) + +您会发现与普通钱包有些不同。现在让我们更详细地看看高负载钱包在TON上的工作原理(除了子钱包,因为我们之前已经讨论过了)。 + +### 使用查询 ID 代替 Seqno + +如我们之前讨论的,普通钱包在每次交易后 seqno 增加 `1`。在使用钱包序列时,我们必须等待这个值更新,然后使用 GET 方法检索它并发送新的交易。 +这个过程需要很长时间,高负载钱包不是为此设计的(如上所述,它们旨在快速发送大量交易)。因此,TON上的高负载钱包使用了 `query_id`。 + +如果相同的交易请求已经存在,合约将不会接受它,因为它已经被处理过了: + +```func +var (stored_subwallet, last_cleaned, public_key, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict()); ;; 从存储中读取值 +ds.end_parse(); ;; 确保 ds 中没有任何东西 +(_, var found?) = old_queries.udict_get?(64, query_id); ;; 检查是否已经存在此类请求 +throw_if(32, found?); ;; 如果是则抛出错误 +``` + +通过这种方式,我们**被保护免受重复交易的影响**,这是普通钱包中 seqno 的作用。 + +### 发送交易 + +合约接受外部消息后,将开始循环,在循环中取出存储在字典中的 `slices`。这些切片存储了交易模式和交易本身。发送新交易一直进行,直到字典为空。 + +```func +int i = -1; ;; 我们写 -1 是因为它将是所有字典键中的最小值 +do { + (i, var cs, var f) = dict.idict_get_next?(16, i); ;; 获取键及其对应的最小键值,这个键值大于 i + if (f) { ;; 检查是否找到了任何值 + var mode = cs~load_uint(8); ;; 加载交易模式 + send_raw_message(cs~load_ref(), mode); ;; 加载交易本身并发送 + } +} until (~ f); ;; 如果找到任何值则继续 +``` + +> 💡 有用的链接: +> +> [idict_get_next()](/develop/func/stdlib/#dict_get_next) + +请注意,如果找到一个值,`f` 永远等于 -1(真)。`~ -1` 操作(位非)将始终返回 0 的值,意味着应该继续循环。与此同时,当字典填充了交易时,需要开始计算那些**大于 -1** 的值(例如,0),并且每次交易都将值递增 1。这个结构允许以正确的顺序发送交易。 + +### 移除过期查询 + +通常情况下,[TON上的智能合约需要为自己的存储付费](/develop/smart-contracts/fees#storage-fee)。这意味着智能合约可以存储的数据量是有限的,以防止高网络交易费用。为了让系统更高效,超过 64 秒的交易将从存储中移除。按照以下方式进行: + +```func +bound -= (64 << 32); ;; 清除记录,这些记录超过 64 秒前已过期 +old_queries~udict_set_builder(64, query_id, begin_cell()); ;; 将当前查询添加到字典中 +var queries = old_queries; ;; 将字典复制到另一个变量中 +do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { ;; 检查是否找到了任何值 + f = (i < bound); ;; 检查是否超过 64 秒后过期 + } + if (f) { + old_queries = old_queries'; ;; 如果是,则在我们的字典中保存更改 + last_cleaned = i; ;; 保存最后移除的查询 + } +} until (~ f); +``` + +> 💡 有用的链接: +> +> [udict_delete_get_min()](/develop/func/stdlib/#dict_delete_get_min) + +请注意,必须多次与 `f` 变量进行交互。由于 [TVM 是一个堆栈机器](/learn/tvm-instructions/tvm-overview#tvm-is-a-stack-machine),在每次与 `f` 变量交互时,必须弹出所有值以获得所需的变量。`f~touch()` 操作将 f 变量放在堆栈顶部,以优化代码执行。 + +### 位运算 + +如果您之前没有使用过位运算,那么这个部分可能会显得有些复杂。在智能合约代码中可以看到以下代码行: + +```func +var bound = (now() << 32); ;; 位左移操作 +``` +结果,在右侧的数字上添加了 32 位。这意味着 **现有值向左移动 32 位**。举例来说,让我们考虑数字 3 并将其翻译成二进制形式,结果是 11。应用 `3 << 2` 操作,11 移动了 2 位。这意味着在字符串的右侧添加了两位。最后,我们得到了 1100,即 12。 + +关于这个过程要理解的第一件事是记住 `now()` 函数返回 uint32 的结果,意味着结果值将是 32 位。通过向左移动 32 位,为另一个 uint32 打开了空间,结果是正确的 query_id。这样,**时间戳和 query_id 可以在一个变量中组合**以进行优化。 + +接下来,让我们考虑以下代码行: + +```func +bound -= (64 << 32); ;; 清除超过 64 秒之前过期的记录 +``` + +在上面,我们执行了一个操作,将数字 64 向左移动 32 位,以**减去 64 秒**的时间戳。这样我们就可以比较过去的 query_ids,看看它们是否小于接收到的值。如果是这样,它们就超过了 64 秒: + +```func +if (f) { ;; 检查是否找到了任何值 + f = (i < bound); ;; 检查是否超过 64 秒后过期 +} +``` +为了更好地理解,让我们使用 `1625918400` 作为时间戳的示例。它的二进制表示(左侧添加零以得到 32 位)是 01100000111010011000101111000000。执行 32 位位左移操作后,我们数字的二进制表示末尾会出现 32 个零。 + +完成后,**可以添加任何 query_id (uint32)**。然后减去 `64 << 32` 的结果是 64 秒前有相同 query_id 的时间戳。可以通过执行以下计算来验证这一点 `((1625918400 << 32) - (64 << 32)) >> 32`。这样我们可以比较我们数字的必要部分(时间戳),同时 query_id 不会干扰。 + +### 存储更新 + +所有操作完成后,剩下的唯一任务就是将新的值保存在存储中: + +```func + set_data(begin_cell() + .store_uint(stored_subwallet, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(old_queries) + .end_cell()); +} +``` + +### GET 方法 + +在我们深入了解钱包部署和交易创建之前,我们必须考虑的最后一件事是高负载钱包的 GET 方法: + +方法 | 说明 +:---: | :---: +int processed?(int query_id) | 通知用户特定请求是否已处理。这意味着如果请求已经处理,则返回 `-1`;如果尚未处理,则返回 `0`。此外,如果答案未知,因为请求较旧,且不再存储在合约中,此方法可能返回 `1`。 +int get_public_key() | 检索公钥。我们之前已经讨论过这个方法。 + +让我们仔细看看 `int processed?(int query_id)` 方法,以帮助我们了解为什么我们需要使用 last_cleaned: + +```func +int processed?(int query_id) method_id { + var ds = get_data().begin_parse(); + var (_, last_cleaned, _, old_queries) = (ds~load_uint(32), ds~load_uint(64), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + (_, var found) = old_queries.udict_get?(64, query_id); + return found ? true : - (query_id <= last_cleaned); +} +``` +`last_cleaned` 从合约的存储和旧查询字典中检索。如果找到了查询,它应返回 true;如果没有,则表达式 `- (query_id <= last_cleaned)`。last_cleaned 包含最后一个被删除的、**时间戳最高**的请求,因为我们开始时从最小时间戳删除请求。 + +这意味着,如果传递给方法的 query_id 小于 last_cleaned 值,就无法确定它是否曾在合约中。因此 `query_id <= last_cleaned` 返回 -1,而表达式前面的减号将答案改为 1。如果 query_id 大于 last_cleaned 方法,则表示它尚未被处理。 + +### 部署高负载钱包 + +为了部署高负载钱包,必须提前生成一个助记词密钥,用户将使用此密钥。可以使用在本教程之前部分中使用的相同密钥。 + +要开始部署高负载钱包的过程,必须将[智能合约的代码](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet-v2-code.fc)复制到 stdlib.fc 和 wallet_v3 所在的同一目录中,并记得在代码开头添加`#include "stdlib.fc";`。接下来,我们将像在[第三节](/develop/smart-contracts/tutorials/wallet#compiling-wallet-code)中所做的那样,编译高负载钱包代码: + + + + +```js +import { compileFunc } from '@ton-community/func-js'; +import fs from 'fs' +import { Cell } from '@ton/core'; + +const result = await compileFunc({ + targets: ['highload_wallet.fc'], // 你项目的目标 + sources: { + 'stdlib.fc': fs.readFileSync('./src/stdlib.fc', { encoding: 'utf-8' }), + 'highload_wallet.fc': fs.readFileSync('./src/highload_wallet.fc', { encoding: 'utf-8' }), + } +}); + +if (result.status === 'error') { +console.error(result.message) +return; +} + +const codeCell = Cell.fromBoc(Buffer.from(result.codeBoc, 'base64'))[0]; + +// 现在我们有了编译后代码的 base64 编码 BOC 在 result.codeBoc 中 +console.log('代码 BOC: ' + result.codeBoc); +console.log('\n哈希值: ' + codeCell.hash().toString('base64')); // 获取cell的哈希值并转换为 base64 编码字符串 + +``` + + + + +在终端中的输出将如下所示: + +```text +Code BOC: te6ccgEBCQEA5QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHq8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44WIYAQ9HhvpSCYAtMH1DAB+wCRMuIBs+ZbgyWhyEA0gED0Q4rmMQHIyx8Tyz/L//QAye1UCAAE0DACASAGBwAXvZznaiaGmvmOuF/8AEG+X5dqJoaY+Y6Z/p/5j6AmipEEAgegc30JjJLb/JXdHxQANCCAQPSWb6VsEiCUMFMDud4gkzM2AZJsIeKz + +Hash: lJTRzI7fEvBWcaGpugmSEJbrUIEeGSTsZcPGKfu4CBI= +``` + +在上述结果的基础上,我们可以使用base64编码的输出,在其他库和语言中检索包含我们钱包代码的cell,具体操作如下: + + + + +```go +import ( + "encoding/base64" + "github.com/xssnick/tonutils-go/tvm/cell" + "log" +) + +base64BOC := "te6ccgEBCQEA5QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHq8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44WIYAQ9HhvpSCYAtMH1DAB+wCRMuIBs+ZbgyWhyEA0gED0Q4rmMQHIyx8Tyz/L//QAye1UCAAE0DACASAGBwAXvZznaiaGmvmOuF/8AEG+X5dqJoaY+Y6Z/p/5j6AmipEEAgegc30JjJLb/JXdHxQANCCAQPSWb6VsEiCUMFMDud4gkzM2AZJsIeKz" // 将编译器输出的base64编码保存到变量中 +codeCellBytes, _ := base64.StdEncoding.DecodeString(base64BOC) // 解码base64以获取字节数组 +codeCell, err := cell.FromBOC(codeCellBytes) // 从字节数组中获取包含代码的cell +if err != nil { // 检查是否有任何错误 + panic(err) +} + +log.Println("Hash:", base64.StdEncoding.EncodeToString(codeCell.Hash())) // 获取我们cell的哈希值,因为它的类型是[]byte,所以编码为base64并输出到终端 +``` + + + + +现在我们需要检索由其初始数据组成的cell,构建一个State Init,并计算一个高负载钱包地址。经过研究智能合约代码后,我们发现subwallet_id、last_cleaned、public_key和old_queries是顺序存储在存储中的: + + + + +```js +import { Address, beginCell } from '@ton/core'; +import { mnemonicToWalletKey } from '@ton/crypto'; + +const highloadMnemonicArray = 'put your mnemonic that you have generated and saved before'.split(' '); +const highloadKeyPair = await mnemonicToWalletKey(highloadMnemonicArray); // 从助记词中提取私钥和公钥 + +const dataCell = beginCell(). + storeUint(698983191, 32). // 子钱包ID + storeUint(0, 64). // 上次清理时间 + storeBuffer(highloadKeyPair.publicKey). // 公钥 + storeBit(0). // 表示字典为空 + endCell(); + +const stateInit = beginCell(). + storeBit(0). // 无split_depth + storeBit(0). // 无special + storeBit(1). // 我们有代码 + storeRef(codeCell). + storeBit(1). // 我们有数据 + storeRef(dataCell). + storeBit(0). // 无库 + endCell(); + +const contractAddress = new Address(0, stateInit.hash()); // 获取stateInit的哈希值以获得我们智能合约在工作链ID为0的地址 +console.log(`Contract address: ${contractAddress.toString()}`); // 输出合约地址到控制台 +``` + + + + +```go +import ( + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "github.com/xssnick/tonutils-go/address" + "golang.org/x/crypto/pbkdf2" + "strings" +) + +highloadMnemonicArray := strings.Split("put your mnemonic that you have generated and saved before", " ") // 单词1 单词2 单词3 +mac := hmac.New(sha512.New, []byte(strings.Join(highloadMnemonicArray, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // 在TON库中,获取钥匙时使用的salt是"TON default seed" +// 钥匙长度为32 +highloadPrivateKey := ed25519.NewKeyFromSeed(k) // 获取私钥 +highloadPublicKey := highloadPrivateKey.Public().(ed25519.PublicKey) // 从私钥获取公钥 + +dataCell := cell.BeginCell(). + MustStoreUInt(698983191, 32). // 子钱包ID + MustStoreUInt(0, 64). // 上次清理时间 + MustStoreSlice(highloadPublicKey, 256). // 公钥 + MustStoreBoolBit(false). // 表示字典为空 + EndCell() + +stateInit := cell.BeginCell(). + MustStoreBoolBit(false). // 无split_depth + MustStoreBoolBit(false). // 无special + MustStoreBoolBit(true). // 我们有代码 + MustStoreRef(codeCell). + MustStoreBoolBit(true). // 我们有数据 + MustStoreRef(dataCell). + MustStoreBoolBit(false). // 无库 + EndCell() + +contractAddress := address.NewAddress(0, 0, stateInit.Hash()) // 获取stateInit的哈希值以获得我们智能合约在工作链ID为0的地址 +log.Println("Contract address:", contractAddress.String()) // 输出合约地址到控制台 +``` + + + + +以上我们详细描述的步骤与[通过钱包部署合约](/develop/smart-contracts/tutorials/wallet#contract-deployment-via-wallet)部分中的步骤一致。为了更好地分析完整功能的代码,请访问教程开始处提到的库,其中存储了所有源代码。 + +### 发送高负载钱包交易 + +现在,让我们编程高负载钱包同时发送多条消息。例如,让我们每条消息发送12笔交易,这样gas费用就很小。 + +:::info 高负载余额 +要完成交易,合约的余额必须至少为0.5 TON。 +::: + +每条消息携带其自己的含代码的评论,目的地址将是我们部署的钱包: + + + + +```js +import { Address, beginCell, Cell, toNano } from '@ton/core'; + +let internalMessages:Cell[] = []; +const walletAddress = Address.parse('put your wallet address from which you deployed high-load wallet'); + +for (let i = 0; i < 12; i++) { + const internalMessageBody = beginCell(). + storeUint(0, 32). + storeStringTail(`Hello, TON! #${i}`). + endCell(); + + const internalMessage = beginCell(). + storeUint(0x18, 6). // 弹回 + storeAddress(walletAddress). + storeCoins(toNano('0.01')). + storeUint(0, 1 + 4 + 4 + 64 + 32). + storeBit(0). // 我们没有State Init + storeBit(1). // 我们将消息体存储为引用 + storeRef(internalMessageBody). // 将消息体Init存储为引用 + endCell(); + + internalMessages.push(internalMessage); +} +``` + + + + +```go +import ( + "fmt" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +var internalMessages []*cell.Cell +wallletAddress := address.MustParseAddr("put your wallet address from which you deployed high-load wallet") + +for i := 0; i < 12; i++ { + comment := fmt.Sprintf("Hello, TON! #%d", i) + internalMessageBody := cell.BeginCell(). + MustStoreUInt(0, 32). + MustStoreBinarySnake([]byte(comment)). + EndCell() + + internalMessage := cell.BeginCell(). + MustStoreUInt(0x18, 6). // 弹回 + MustStoreAddr(wallletAddress). + MustStoreBigCoins(tlb.MustFromTON("0.001").NanoTON()). + MustStoreUInt(0, 1+4+4+64+32). + MustStoreBoolBit(false). // 我们没有State Init + MustStoreBoolBit(true). // 我们将消息体存储为引用 + MustStoreRef(internalMessageBody). // 将消息体Init存储为引用 + EndCell() + + messageData := cell.BeginCell(). + MustStoreUInt(3, 8). // 交易mode + MustStoreRef(internalMessage). + EndCell() + + internalMessages = append(internalMessages, messageData) +} +``` + + + + +完成上述过程后,结果是一系列内部消息。接下来,需要创建一个消息存储的字典来准备并签名消息体。如下所示: + + + + +```js +import { Dictionary } from '@ton/core'; +import { mnemonicToWalletKey, sign } from '@ton/crypto'; +import * as crypto from 'crypto'; + +const dictionary = Dictionary.empty(); // 创建一个键为数字值为cell的空字典 +for (let i = 0; i < internalMessages.length; i++) { + const internalMessage = internalMessages[i]; // 从数组中获取我们的消息 + dictionary.set(i, internalMessage); // 在字典中保存该消息 +} + +const queryID = crypto.randomBytes(4).readUint32BE(); // 创建一个随机的uint32数字,4字节 = 32位 +const now = Math.floor(Date.now() / 1000); // 获取当前时间戳 +const timeout = 120; // 消息失效的超时时间,120秒 = 2分钟 +const finalQueryID = (BigInt(now + timeout) << 32n) + BigInt(queryID); // 获取我们最终的query_id +console.log(finalQueryID); // 打印query_id。使用这个query_id我们可以调用GET方法来检查我们的请求是否已被处理 + +const toSign = beginCell(). + storeUint(698983191, 32). // subwallet_id + storeUint(finalQueryID, 64). + // 在这里我们创建自己的方法来保存 + // 交易mode和对交易的引用 + storeDict(dictionary, Dictionary.Keys.Int(16), { + serialize: (src, buidler) => { + buidler.storeUint(3, 8); // 保存交易mode,mode = 3 + buidler.storeRef(src); // 以引用形式保存交易 + }, + // 实际上我们不会使用这个,但这个方法 + // 将帮助读取我们保存的字典 + parse: (src) => { + let cell = beginCell(). + storeUint(src.loadUint(8), 8). + storeRef(src.loadRef()). + endCell(); + return cell; + } + } +); + +const highloadMnemonicArray = 'put your high-load wallet mnemonic'.split(' '); +const highloadKeyPair = await mnemonicToWalletKey(highloadMnemonicArray); // 从助记词中提取私钥和公钥 +const highloadWalletAddress = Address.parse('put your high-load wallet address'); + +const signature = sign(toSign.endCell().hash(), highloadKeyPair.secretKey); // 获取我们向智能合约钱包发送的消息哈希并签名以获取签名 +``` + + + + +```go +import ( + "crypto/ed25519" + "crypto/hmac" + "crypto/sha512" + "golang.org/x/crypto/pbkdf2" + "log" + "math/big" + "math/rand" + "strings" + "time" +) + +dictionary := cell.NewDict(16) // 创建一个空字典,键为数字,值为cell +for i := 0; i < len(internalMessages); i++ { + internalMessage := internalMessages[i] // 从数组中获取消息 + err := dictionary.SetIntKey(big.NewInt(int64(i)), internalMessage) // 在字典中保存消息 + if err != nil { + return + } +} + +queryID := rand.Uint32() +timeout := 120 // 消息过期的超时时间,120秒 = 2分钟 +now := time.Now().Add(time.Duration(timeout)*time.Second).UTC().Unix() << 32 // 获取当前时间戳 + 超时时间 +finalQueryID := uint64(now) + uint64(queryID) // 获取最终的query_id +log.Println(finalQueryID) // 打印query_id。使用此query_id我们可以调用GET方法检查请求是否已处理 + +toSign := cell.BeginCell(). + MustStoreUInt(698983191, 32). // subwallet_id + MustStoreUInt(finalQueryID, 64). + MustStoreDict(dictionary) + +highloadMnemonicArray := strings.Split("put your high-load wallet mnemonic", " ") // word1 word2 word3 +mac := hmac.New(sha512.New, []byte(strings.Join(highloadMnemonicArray, " "))) +hash := mac.Sum(nil) +k := pbkdf2.Key(hash, []byte("TON default seed"), 100000, 32, sha512.New) // 在TON库中,“TON default seed”被用作获取密钥时的salt +// 32是密钥长度 +highloadPrivateKey := ed25519.NewKeyFromSeed(k) // 获取私钥 +highloadWalletAddress := address.MustParseAddr("put your high-load wallet address") + +signature := ed25519.Sign(highloadPrivateKey, toSign.EndCell().Hash()) +``` + + + + +:::note 重要 +请注意,在使用JavaScript和TypeScript时,我们的消息被保存在数组中而没有使用发送模式。这是因为,在使用@ton/ton库时,预期开发者将自行实现序列化和反序列化的过程。因此,会传递一个首先保存交易模式然后保存交易本身的方法。如果我们使用`Dictionary.Values.Cell()`规范作为值方法,它会将整个消息作为cell引用保存,而不是单独保存模式。 +::: + +接下来我们将创建一个外部消息并使用以下代码发送到区块链: + + + + +```js +import { TonClient } from '@ton/ton'; + +const body = beginCell(). + storeBuffer(signature). // 保存签名 + storeBuilder(toSign). // 保存我们的消息 + endCell(); + +const externalMessage = beginCell(). + storeUint(0b10, 2). // 表明这是一个传入的外部交易 + storeUint(0, 2). // src -> addr_none + storeAddress(highloadWalletAddress). + storeCoins(0). // 导入费用 + storeBit(0). // 我们没有State Init + storeBit(1). // 我们以引用形式存储消息体 + storeRef(body). // 以引用形式存储消息体 + endCell(); + +// 我们在这里不需要键,因为我们将以每秒1个请求的速度发送 +const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + // apiKey: 'put your api key' // 你可以从Telegram中的@tonapibot bot获得一个api密钥 +}); + +client.sendFile(externalMessage.toBoc()); +``` + + + + +```go +import ( + "context" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/tl" + "github.com/xssnick/tonutils-go/ton" +) + +body := cell.BeginCell(). + MustStoreSlice(signature, 512). // 存储签名 + MustStoreBuilder(toSign). // 存储我们的消息 + EndCell() + +externalMessage := cell.BeginCell(). + MustStoreUInt(0b10, 2). // ext_in_msg_info$10 + MustStoreUInt(0, 2). // src -> addr_none + MustStoreAddr(highloadWalletAddress). // 目标地址 + MustStoreCoins(0). // 导入费用 + MustStoreBoolBit(false). // 无State Init + MustStoreBoolBit(true). // 我们以引用形式存储消息体 + MustStoreRef(body). // 以引用形式存储消息体 + EndCell() + +connection := liteclient.NewConnectionPool() +configUrl := "https://ton-blockchain.github.io/global.config.json" +err := connection.AddConnectionsFromConfigUrl(context.Background(), configUrl) +if err != nil { + panic(err) +} +client := ton.NewAPIClient(connection) + +var resp tl.Serializable +err = client.Client().QueryLiteserver(context.Background(), ton.SendMessage{Body: externalMessage.ToBOCWithFlags(false)}, &resp) + +if err != nil { + log.Fatalln(err.Error()) + return +} +``` + + + + +完成此过程后,我们可以查看我们的钱包并验证我们的钱包上发送了12个传出交易。使用我们最初在控制台中使用的query_id,也可以调用`processed?` GET方法。如果此请求已正确处理,它会提供`-1`(真)的结果。 + +## 🏁 结论 + +这个教程让我们更好地理解了TON区块链上不同钱包类型的运作方式。它还让我们学会了如何创建外部和内部消息,而不使用预定义的库方法。 + +这有助于我们独立于使用库,并以更深入的方式理解TON区块链的结构。我们还学习了如何使用高负载钱包,并分析了许多与不同数据类型和各种操作相关的细节。 + +## 🧩 下一步 + +阅读上述文档是一项复杂的任务,人们难以完全理解TON平台的全部内容。然而,这对于那些热衷于在TON上建设的人来说是一个很好的练习。另一个建议是开始学习如何在TON上编写智能合约,可以参考以下资源:[FunC概览](https://docs.ton.org/develop/func/overview),[最佳实践](https://docs.ton.org/develop/smart-contracts/guidelines),[智能合约示例](https://docs.ton.org/develop/smart-contracts/examples),[FunC开发手册](https://docs.ton.org/develop/func/cookbook) + +此外,建议读者更详细地熟悉以下文档:[ton.pdf](https://docs.ton.org/ton.pdf) 和 [tblkch.pdf](https://ton.org/tblkch.pdf) 文档。 + +## 📬 关于作者 + +如果您有任何问题、评论或建议,请通过 [Telegram](https://t.me/aspite) (@aSpite 或 @SpiteMoriarty) 或 [GitHub](https://github.com/aSpite) 联系本文档部分的作者。 + +## 📖 参阅 + +- 钱包的源代码:[V3](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet3-code.fc),[V4](https://github.com/ton-blockchain/wallet-contract/blob/main/func/wallet-v4-code.fc),[高负载](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/highload-wallet-v2-code.fc) + +- 有用的概念文件(可能包含过时信息):[ton.pdf](https://docs.ton.org/ton.pdf),[tblkch.pdf](https://ton.org/tblkch.pdf),[tvm.pdf](https://ton.org/tvm.pdf) + +代码的主要来源: + + - [@ton/ton (JS/TS)](https://github.com/ton-org/ton) + - [@ton/core (JS/TS)](https://github.com/ton-org/ton-core) + - [@ton/crypto (JS/TS)](https://github.com/ton-org/ton-crypto) + - [tonutils-go (GO)](https://github.com/xssnick/tonutils-go). + +官方文档: + + - [内部消息](/develop/smart-contracts/guidelines/internal-messages) + + - [外部消息](/develop/smart-contracts/guidelines/external-messages) + + - [钱包合约类型](/participate/wallets/contracts#wallet-v4) + + - [TL-B](/develop/data-formats/tl-b-language) + + - [区块链网络](https://docs.ton.org/learn/overviews/ton-blockchain) + +外部参考: + +- [Ton Deep](https://github.com/xssnick/ton-deep-doc) + +- [Block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) + +- [TON中的标准](https://github.com/ton-blockchain/TEPs) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/overview.mdx new file mode 100644 index 0000000000..9944305ab8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/overview.mdx @@ -0,0 +1,26 @@ +import Button from '@site/src/components/button' + +# 概览 + +:::info +本文需要更新。请帮助我们改进它。 +::: + +**本页包含了可以帮助您确保智能合约安全的建议。** + +如果您正在创建智能合约,那么在这里您可以看到一些示例,这些错误可能导致您丢失资金: + +- [TON Hack挑战赛#1](https://github.com/ton-blockchain/hack-challenge-1) + - [从TON Hack挑战赛中得出的结论](/develop/smart-contracts/security/ton-hack-challenge-1) + +## TON 课程:安全性 + +[TON区块链课程](https://stepik.org/course/201638/)是对TON区块链开发的全面指导。 + +第8模块完全涵盖了TON区块链上智能合约的安全性。 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random-number-generation.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random-number-generation.md new file mode 100644 index 0000000000..c1c5e6074b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random-number-generation.md @@ -0,0 +1,106 @@ +# 随机数生成 + +生成随机数是许多不同项目中常见的任务。你可能已经在FunC文档中看到过`random()`函数,但请注意,除非你采用一些额外的技巧,否则其结果很容易被预测。 + +## 如何预测随机数? + +计算机在生成随机信息方面非常糟糕,因为它们只是遵循用户的指令。然而,由于人们经常需要随机数,他们设计了各种方法来生成_伪随机_数。 + +这些算法通常要求你提供一个_seed_值,该值将被用来生成一系列_伪随机_数。因此,如果你多次运行相同的程序并使用相同的_seed_,你将始终得到相同的结果。在TON中,每个区块的_seed_是不同的。 + +- [区块随机seed的生成](/develop/smart-contracts/security/random) + +因此,要预测智能合约中`random()`函数的结果,你只需要知道当前区块的`seed`,如果你不是验证者,这是不可能的。 + +## 只需使用`randomize_lt()` + +为了使随机数生成不可预测,你可以将当前的[逻辑时间](/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time)添加到seed中,这样不同的交易将具有不同的seed和结果。 + +只需在生成随机数之前调用`randomize_lt()`,你的随机数就会变得不可预测: + +```func +randomize_lt(); +int x = random(); ;; users can't predict this number +``` + +然而,你应该注意验证者或协作者仍然可能影响随机数的结果,因为他们决定了当前区块的seed。 + +## 有没有办法防止验证者操纵? + +为了防止(或至少复杂化)验证者替换seed,你可以使用更复杂的方案。例如,你可以在生成随机数之前跳过一个区块。如果我们跳过一个区块,seed将以不太可预测的方式改变。 + +跳过区块并不是一个复杂的任务。你可以通过简单地将消息发送到主链,然后再发送回你合约的工作链来完成。让我们来看一个简单的例子! + +:::caution +不要在真实项目中使用此示例合约,请自己编写。 +::: + +### 任何工作链中的主合约 + +让我们以一个简单的彩票合约为例。用户将向它发送1 TON,有50%的机会会得到2 TON回报。 + +```func +;; set the echo-contract address +const echo_address = "Ef8Nb7157K5bVxNKAvIWreRcF0RcUlzcCA7lwmewWVNtqM3s"a; + +() recv_internal (int msg_value, cell in_msg_full, slice in_msg_body) impure { + var cs = in_msg_full.begin_parse(); + var flags = cs~load_uint(4); + if (flags & 1) { ;; ignore bounced messages + return (); + } + slice sender = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + if ((op == 0) & equal_slice_bits(in_msg_body, "bet")) { ;; bet from user + throw_unless(501, msg_value == 1000000000); ;; 1 TON + + send_raw_message( + begin_cell() + .store_uint(0x18, 6) + .store_slice(echo_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page) + .store_uint(1, 32) ;; let 1 be echo opcode in our contract + .store_slice(sender) ;; forward user address + .end_cell(), + 64 ;; send the remaining value of an incoming msg + ); + } + elseif (op == 1) { ;; echo + throw_unless(502, equal_slice_bits(sender, echo_address)); ;; only accept echoes from our echo-contract + + slice user = in_msg_body~load_msg_addr(); + + {- + at this point we have skipped 1+ blocks + so let's just generate the random number + -} + randomize_lt(); + int x = rand(2); ;; generate a random number (either 0 or 1) + if (x == 1) { ;; user won + send_raw_message( + begin_cell() + .store_uint(0x18, 6) + .store_slice(user) + .store_coins(2000000000) ;; 2 TON + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page) + .end_cell(), + 3 ;; ignore errors & pay fees separately + ); + } + } +} +``` + +在你需要的任何工作链(可能是基本链)部署这个合约,就完成了! + +## 这种方法100%安全吗? + +虽然它确实有所帮助,但如果入侵者同时控制了几个验证者,仍然有可能被操纵。在这种情况下,他们可能会以某种概率[影响](/develop/smart-contracts/security/random#conclusion)依赖的_seed_。即使这种可能性极小,仍然值得考虑。 + +随着最新的TVM升级,向`c7`寄存器中引入新值可以进一步提高随机数生成的安全性。具体来说,升级在`c7`寄存器中添加了关于最近16个主链区块的信息。 + +由于主链区块信息的不断变化性质,它可以作为随机数生成的额外熵源。通过将这些数据纳入你的随机算法中,你可以创建出更难以被潜在对手预测的数字。 + +有关此TVM升级的更多详细信息,请参考[TVM升级](/learn/tvm-instructions/tvm-upgrade-2023-07)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random.md new file mode 100644 index 0000000000..cb6007c725 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/random.md @@ -0,0 +1,76 @@ +# 区块随机 seed 的生成 + +:::caution +此信息在撰写时是最新的。它可能会在任何网络升级时发生变化。 +::: + +偶尔,在TON上会创建一个彩票合约。通常它使用不安全的方法来处理随机性,因此生成的值可以被用户预测,彩票可能被耗尽。 + +但是,利用随机数生成中的弱点通常涉及使用代理合约,如果随机值正确,代理合约会转发消息。存在有关钱包合约的提案,这些合约将能够执行链上任意代码(当然由用户指定和签名),但大多数流行的钱包版本不支持这样做。那么,如果彩票检查赌徒是否通过钱包合约参与,它是否安全? + +或者,这个问题可以这样表述。外部消息能否被包含在随机值正好符合发送者需求的区块中? + +当然,发送者无法以任何方式影响随机性。但是生成区块并包含提议的外部消息的验证者可以。 + +## 验证者如何影响seed + +即使在白皮书中,关于这一点的信息也不多,所以大多数开发者都感到困惑。这是关于区块随机的唯一提及,在 [TON白皮书](https://docs.ton.org/ton.pdf) 中: + +> 为每个分片(w, s)选择验证者任务组的算法是确定性伪随机的。**它使用验证者嵌入到每个主链区块中的伪随机数(通过使用阈值签名达成共识)来创建随机seed**,然后为每个验证者计算例如Hash(code(w). code(s).validator_id.rand_seed)。 + +然而,唯一被保证真实且最新的是代码。所以让我们看看 [collator.cpp](https://github.com/ton-blockchain/ton/blob/f59c363ab942a5ddcacd670c97c6fbd023007799/validator/impl/collator.cpp#L1590): + +```cpp + { + // generate rand seed + prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); + LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); + } +``` + +这是生成区块随机seed的代码。它位于协作者代码中,因为它由生成区块的一方需要(并且对轻量级验证者不是必需的)。 + +所以,我们可以看到,seed是由单个验证者或协作者与区块一起生成的。下一个问题是: + +## 在知道seed后是否可以决定包含外部消息? + +是的,可以。证据如下:如果外部消息被导入,它的执行必须成功。执行可以依赖于随机值,所以保证seed事先已知。 + +因此,如果发送者可以与验证者合作,那么确实**存在**一种方法来攻击"不安全"(让我们称之为单区块,因为它不使用发送消息后的任何区块信息)随机。即使使用了`randomize_lt()`。验证者可以生成适合发送者的seed,或者将提议的外部消息包含在将满足所有条件的区块中。这样做的验证者仍然被认为是公平的。这就是去中心化的本质。 + +为了让这篇文章完全覆盖随机性,这里还有一个问题。 + +## 区块seed如何影响合约中的随机数? + +验证者生成的seed并不直接用于所有合约。相反,它是[与账户地址一起哈希](https://github.com/ton-blockchain/ton/blob/f59c363ab942a5ddcacd670c97c6fbd023007799/crypto/block/transaction.cpp#L876)的。 + +```cpp +bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const { + // we might use SHA256(block_rand_seed . addr . trans_lt) + // instead, we use SHA256(block_rand_seed . addr) + // if the smart contract wants to randomize further, it can use RANDOMIZE instruction + td::BitArray<256 + 256> data; + data.bits().copy_from(cfg.block_rand_seed.cbits(), 256); + (data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256); + rand_seed.clear(); + data.compute_sha256(rand_seed); + return true; +} +``` + +然后,伪随机数是使用 [TVM指令](/learn/tvm-instructions/instructions#112-pseudo-random-number-generator-primitives) 页面上描述的过程生成的: + +> **x\{F810} RANDU256**\ +> 生成一个新的伪随机无符号256位整数x。算法如下:如果r是随机seed的旧值,被视为一个32字节的数组(通过构造无符号256位整数的大端表示),那么计算它的sha512(r);这个哈希的前32字节被存储为随机seed的新值r',剩余的32字节作为下一个随机值x返回。 + +我们可以通过查看 [准备c7合约](https://github.com/ton-blockchain/ton/blob/master/crypto/block/transaction.cpp#L903) 的代码(c7是存储临时数据的元组,存储合约地址、起始余额、随机seed等)和 [随机值本身的生成](https://github.com/ton-blockchain/ton/blob/master/crypto/vm/tonops.cpp#L217-L268) 来确认这一点。 + +## 结论 + +TON中没有随机是完全安全的,就不可预测性而言。这意味着**这里不可能存在完美的彩票**,也不可能相信任何彩票是公平的。 + +PRNG的典型用途可能包括`randomize_lt()`,但是可以通过选择正确的区块向它发送消息来欺骗这样的合约。提出的解决方案是向其他工作链发送消息,接收回答,从而跳过区块等...但这只是推迟了威胁。事实上,任何验证者(即TON区块链的1/250)都可以在正确的时间选择发送请求给彩票合约,以便来自其他工作链的回复在他生成的区块中到达,然后他可以选择他希望的任何区块seed。一旦协作者出现在主网,危险将会增加,因为他们永远不会因为标准投诉而被罚款,因为他们不会向Elector合约注入任何资金。 + + + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/ton-hack-challenge-1.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/ton-hack-challenge-1.md new file mode 100644 index 0000000000..ff5003e37e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/security/ton-hack-challenge-1.md @@ -0,0 +1,157 @@ +# 从 TON Hack 挑战赛中得出结论 + +TON Hack挑战赛于10月23日举行。在TON主网上部署了几个带有人为安全漏洞的智能合约。每个合约都有3000或5000 TON的余额,允许参与者攻击它并立即获得奖励。 + +源代码和比赛规则托管在Github [这里](https://github.com/ton-blockchain/hack-challenge-1)。 + +## 合约 + +### 1. 互助基金 + +:::note 安全规则 +始终检查函数是否有[`impure`](/develop/func/functions#impure-specifier)修饰符。 +::: + +第一个任务非常简单。攻击者可以发现`authorize`函数没有`impure`。这个修饰符的缺失允许编译器在函数不返回任何内容或返回值未使用时跳过对该函数的调用。 + +```func +() authorize (sender) inline { + throw_unless(187, equal_slice_bits(sender, addr1) | equal_slice_bits(sender, addr2)); +} +``` + +### 2. 银行 + +:::note 安全规则 +始终检查[修改/非修改](/develop/func/statements#methods-calls)方法。 +::: + +使用`.`而不是`~`调用了`udict_delete_get?`,所以真正的 dict 没有被触及。 + +```func +(_, slice old_balance_slice, int found?) = accounts.udict_delete_get?(256, sender); +``` + +### 3. DAO + +:::note 安全规则 +如果你真的需要,使用符号整数。 +::: + +投票权在消息中以整数形式存储。所以攻击者可以在转移投票权时发送一个负值,并获得无限投票权。 + +```func +(cell,()) transfer_voting_power (cell votes, slice from, slice to, int amount) impure { + int from_votes = get_voting_power(votes, from); + int to_votes = get_voting_power(votes, to); + + from_votes -= amount; + to_votes += amount; + + ;; No need to check that result from_votes is positive: set_voting_power will throw for negative votes + ;; throw_unless(998, from_votes > 0); + + votes~set_voting_power(from, from_votes); + votes~set_voting_power(to, to_votes); + return (votes,()); +} +``` + +### 4. 彩票 + +:::note 安全规则 +在执行[`rand()`](/develop/func/stdlib#rand)之前,始终随机化seed。 +::: + +seed来自交易的逻辑时间,黑客可以通过暴力破解当前区块中的逻辑时间来赢得比赛(因为lt在一个区块的边界内是连续的)。 + +```func +int seed = cur_lt(); +int seed_size = min(in_msg_body.slice_bits(), 128); + +if(in_msg_body.slice_bits() > 0) { + seed += in_msg_body~load_uint(seed_size); +} +set_seed(seed); +var balance = get_balance().pair_first(); +if(balance > 5000 * 1000000000) { + ;; forbid too large jackpot + raw_reserve( balance - 5000 * 1000000000, 0); +} +if(rand(10000) == 7777) { ...send reward... } +``` + +### 5. 钱包 + +:::note 安全规则 +记住区块链上存储的一切。 +::: + +钱包受密码保护,其哈希存储在合约数据中。然而,区块链记住一切——密码在交易历史中。 + +### 6. 资金库 + +:::note 安全规则 +始终检查[bounced](/develop/smart-contracts/guidelines/non-bouncable-messages)消息。 +不要忘记由[标准](/develop/func/stdlib/)函数引起的错误。 +尽可能使条件严格。 +::: + +资金库在数据库消息处理程序中有以下代码: + +```func +int mode = null(); +if (op == op_not_winner) { + mode = 64; ;; Refund remaining check-TONs + ;; addr_hash corresponds to check requester +} else { + mode = 128; ;; Award the prize + ;; addr_hash corresponds to the withdrawal address from the winning entry +} +``` + +如果用户发送“支票”,资金库没有弹回处理程序或代理消息到数据库。在数据库中,我们可以设置`msg_addr_none`作为奖励地址,因为`load_msg_address`允许它。我们向资金库请求支票,数据库尝试解析`msg_addr_none`使用[`parse_std_addr`](/develop/func/stdlib#parse_std_addr),并失败。消息从数据库弹回到金库,并且op不是`op_not_winner`。 + +### 7. 更好的银行 + +:::note 安全规则 +永远不要为了好玩而销毁账户。做[`raw_reserve`](/develop/func/stdlib#raw_reserve)而不是把钱发给自己。考虑可能的竞争条件。小心哈希映射的gas费用消耗。 +::: + +合约中存在竞争条件:你可以存入钱,然后尝试在并发消息中两次提取它。无法保证保留有资金的消息会被处理,所以银行在第二次提款后可能会关闭。之后,合约可以被重新部署,任何人都可以提取未领取的资金。 + +### 8. 驱逐者 + +:::note 安全规则 +避免在合约中执行第三方代码。 +::: + +```func +slice try_execute(int image, (int -> slice) dehasher) asm "<{ TRY:<{ EXECUTE DEPTH 2 THROWIFNOT }>CATCH<{ 2DROP NULL }> }>CONT" "2 1 CALLXARGS"; + +slice safe_execute(int image, (int -> slice) dehasher) inline { + cell c4 = get_data(); + + slice preimage = try_execute(image, dehasher); + + ;; restore c4 if dehasher spoiled it + set_data(c4); + ;; clean actions if dehasher spoiled them + set_c5(begin_cell().end_cell()); + + return preimage; +} +``` + +在合约中安全执行第三方代码是不可能的,因为[`out of gas`](/learn/tvm-instructions/tvm-exit-codes#standard-exit-codes)异常不能被`CATCH`处理。攻击者可以简单地[`COMMIT`](/learn/tvm-instructions/instructions#11-application-specific-primitives)合约的任何状态,并引发`out of gas`。 + +## 结论 + +希望这篇文章能对FunC开发者揭示一些不明显的规则。 + +## 参考资料 + +原文作者 Dan Volkov + +- [dvlkv on Github](https://github.com/dvlkv) +- [原文链接](https://dev.to/dvlkv/drawing-conclusions-from-ton-hack-challenge-1aep) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/overview.mdx new file mode 100644 index 0000000000..d65d01a29e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/overview.mdx @@ -0,0 +1,141 @@ +# 使用 Blueprint 编写测试 + +## 概览 + +测试工具包(通常是沙盒)已经包含在名为[Blueprint](/develop/smart-contracts/sdk/javascript)的TypeScript SDK中。您可以创建一个演示项目并通过两个步骤启动默认测试: + +1. 创建一个新的Blueprint项目: + +```bash +npm create ton@latest MyProject +``` + +2. 运行测试: + +```bash +cd MyProject +npx blueprint test +``` + +然后,您将在终端窗口中看到相应的输出: + +```bash +% npx blueprint test + +> MyProject@0.0.1 test +> jest + + PASS tests/Main.spec.ts + Main + ✓ should deploy (127 ms) + +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Snapshots: 0 total +Time: 1.224 s, estimated 2 s +Ran all test suites. +``` + +## 基本用法 + +测试智能合约可以涵盖安全性、优化Gas支出和检查极端情况。 +在Blueprint(基于[Sandbox](https://github.com/ton-org/sandbox))中编写测试是通过定义与合约的任意操作并将测试结果与预期结果进行比较来实现的,例如: + +```typescript +it('should execute with success', async () => { // description of the test case + const res = await main.sendMessage(sender.getSender(), toNano('0.05')); // performing an action with contract main and saving result in res + + expect(res.transactions).toHaveTransaction({ // configure the expected result with expect() function + from: main.address, // set expected sender for transaction we want to test matcher properties from + success: true // set the desirable result using matcher property success + }); + + printTransactionFees(res.transactions); // print table with details on spent fees +}); +``` + +### 编写复杂Assertion的测试 + +创建测试的基本流程是: + +1. 使用`blockchain.openContract()`创建特定的wrapped`Contract`实体。 +2. 描述您的`Contract`应执行的操作并将执行结果保存在`res`变量中。 +3. 使用`expect()`函数和匹配器`toHaveTransaction()`验证属性。 + +`toHaveTransaction`匹配器所期望的对象包含以下属性中的任意组合,这些属性来自`FlatTransaction`类型 + +| 名称 | 类型 | 描述 | +| -------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| from | Address? | 消息发送者的合约地址 | +| on | Address | 消息目的地的合约地址 (属性`to`的替代名称)。 | +| value | | 消息中Toncoin的数量,以nanotons计算 | +| body | Cell | 定义为Cell的消息体 | +| op | number? | op是操作标识符号码(通常为TL-B的crc32)。在消息体的前32位中。 | +| success | boolean? | 自定义沙盒标志,定义特定交易的结果状态。如果计算和行动阶段都成功,则为True。否则为False。 | + +您可以省略您不感兴趣的字段,并传递接受类型并返回布尔值的函数(true表示可以),以检查例如数字范围、消息操作码等。请注意,如果字段是可选的(如`from?: Address`),那么函数也需要接受可选类型。 + +:::tip +您可以从[Sandbox文档](https://github.com/ton-org/sandbox#test-a-transaction-with-matcher)中查看所有匹配器字段的完整列表。 +::: + +### 特定测试套件 + +#### 提取发送模式 + +要提取发送消息的发送模式,您可以使用以下代码: + +```ts + +const smc = await blockchain.getContract(addr); + +const re = blockchain.executor.runTransaction({ + config: blockchain.configBase64, libs: null, verbosity: 'full', + now: Math. floor (Date.now) / 1000), + lt: BigInt(Date.now()), + randomSeed: null, + ignoreChksig: false, + debugEnabled: true, + shardAccount: beginCell() + .store (storeShardAccount (smc.account)) + .endCell() + .toBoc() + .toString('base64'), + message: beginCell() + .store (storeMessageRelaxed (...)) + .endCell(), +}); + +if (!re.result. success || !re.result.actions) { + throw new Error('fail'); +} +const actions = loadoutList(Cell.fromBase64(re.result.actions).beginParse()); +actions[0].type === 'sendMsg' && actions[0].mode; + +``` + +## 教程 + +从TON社区最有价值的教程中了解更多关于测试的信息: + +- [第2课:为智能合约测试FunC](https://github.com/romanovichim/TonFunClessons_Eng/blob/main/lessons/smartcontract/2lesson/secondlesson.md) +- [TON Hello World第4部分:逐步指导测试您的第一个智能合约](https://ton-community.github.io/tutorials/04-testing/) +- [TON智能合约传递途径](https://dev.to/roma_i_m/ton-smart-contract-pipeline-write-simple-contract-and-compile-it-4pnh) +- [\[YouTube\]第六课 FunC & Blueprint。Gas,费用,测试。](https://youtu.be/3XIpKZ6wNcg) + +## 示例 + +查看用于TON生态系统合约的测试套件,并通过示例进行学习。 + +- [liquid-staking-contract沙盒测试](https://github.com/ton-blockchain/liquid-staking-contract/tree/main/tests) +- [governance_tests](https://github.com/Trinketer22/governance_tests/blob/master/config_tests/tests/) +- [JettonWallet.spec.ts](https://github.com/EmelyanenkoK/modern_jetton/blob/master/tests/JettonWallet.spec.ts) +- [governance_tests](https://github.com/Trinketer22/governance_tests/blob/master/elector_tests/tests/complaint-test.fc) +- [MassSender.spec.ts](https://github.com/Gusarich/ton-mass-sender/blob/main/tests/MassSender.spec.ts) +- [TonForwarder.spec.ts](https://github.com/TrueCarry/ton-contract-forwarder/blob/main/src/contracts/ton-forwarder/TonForwarder.spec.ts) +- [Assurer.spec.ts](https://github.com/aSpite/dominant-assurance-contract/blob/main/tests/Assurer.spec.ts) + +## 参阅 + +- +- [toncli](/develop/smart-contracts/testing/toncli) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/writing-test-examples.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/writing-test-examples.mdx new file mode 100644 index 0000000000..6d89c233d2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/smart-contracts/testing/writing-test-examples.mdx @@ -0,0 +1,572 @@ +# 编写测试示例 + +此页面展示了如何为在[Blueprint SDK](https://github.com/ton-org/blueprint)([Sandbox](https://github.com/ton-org/sandbox))中创建的FunC合约编写测试。 +测试套件为演示合约[fireworks](https://github.com/ton-community/fireworks-func)构建。Fireworks是一个通过`set_first`消息初始化运行的智能合约。 + +通过`npm create ton@latest`创建一个新的FunC项目后,测试文件`tests/contract.spec.ts`将自动生成在项目目录中,用于测试合约: + +```typescript +import ... + +describe('Fireworks', () => { +... + + + expect(deployResult.transactions).toHaveTransaction({ +... + }); + +}); + +it('should deploy', async () => { + // the check is done inside beforeEach + // blockchain and fireworks are ready to use +}); +``` + +使用以下命令运行测试: + +```bash +npx blueprint test +``` + +可以通过`blockchain.verbosity`指定附加选项和vmLogs: + +```typescript +blockchain.verbosity = { + ...blockchain.verbosity, + blockchainLogs: true, + vmLogs: 'vm_logs_full', + debugLogs: true, + print: false, +} +``` + +## 直接 cell 测试 + +Fireworks演示了在TON区块链中发送消息的不同操作。 + +![](/img/docs/writing-test-examples/test-examples-schemes.svg) + +一旦你有足够TON金额并通过`set_first`消息部署它,它将使用主要和可用的发送模式组合自动执行。 + +Fireworks重新部署自己,结果将创建3个Fireworks实体,每个实体都有自己的ID(被保存在存储中),因此有不同的智能合约地址。 + +为了清晰起见,我们定义不同ID的Fireworks实例(不同的`state_init`)并以下列名称命名: + +- 1 - Fireworks setter - 传播不同启动操作码的实体。可以扩展到四种不同的操作码。 +- 2 - Fireworks launcher-1 - 启动第一个firework的Fireworks实例,意味着消息将被发送给launcher。 +- 3 - Fireworks launcher-2 - 启动第二个firework的Fireworks实例,意味着消息将被发送给launcher。 + +
+ 展开交易细节 + +index - 是`launchResult`数组中交易的ID。 + +- `0` - 对资金库(the Launcher)的外部请求,导致向fireworks发送2.5 TON的出站消息`op::set_first` +- `1` - 在Fireworks setter合约中使用`op::set_first`调用的交易,并执行了两个出站消息到Fireworks Launcher-1和Fireworks Launcher-2 +- `2` - 在Fireworks launcher 1中使用`op::launch_first`调用的交易,并执行了四个出站消息到the Launcher。 +- `3` - 在Fireworks launcher 2中使用`op::launch_second`调用的交易,并执行了一个出站消息到the Launcher。 +- `4` - 在the Launcher中来自Fireworks launcher 1的入站消息的交易。此消息以`send mode = 0`发送。 +- `5` - 在the Launcher中来自Fireworks launcher 1的入站消息的交易。此消息以`send mode = 1`发送。 +- `6` - 在the Launcher中来自Fireworks launcher 1的入站消息的交易。此消息以`send mode = 2`发送。 +- `7` - 在the Launcher中来自Fireworks launcher 1的入站消息的交易。此消息以`send mode = 128 + 32`发送。 +- `8` - 在the Launcher中来自Fireworks launcher 2的入站消息的交易。此消息以`send mode = 64`发送。 + +
+ +每个“firework” - 是交易ID:3和ID:4中出现的带有独特消息体的出站消息。 + +以下是每个预期成功执行的交易的测试列表。交易[ID:0]是对资金库(the Launcher)的外部请求,导致向fireworks发送2.5 TON的出站消息`op::set_first`。如果您将Fireworks部署到区块链,launcher会是您的钱包。 + +### 交易ID:1 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L75)检查是否通过发送2.5 TON的交易成功设置了fireworks。 +这是最简单的情况,主要目的是确认交易成功属性为true。 + +要从`launhcResult.transactions`数组中过滤出特定交易,我们可以使用最方便的字段。 +通过`from`(合约发送方地址)、`to`(合约目的地地址)、`op`(操作码值) - 我们将仅检索此组合的一个交易。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id1.svg) + +交易[ID:1]在Fireworks Setter合约中被`op::set_first`调用,并执行了两个出站消息到Fireworks Launcher-1和Fireworks Launcher-2。 + +```typescript + + it('first transaction[ID:1] should set fireworks successfully', async () => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch(launcher.getSender(), toNano('2.5')); + + + expect(launchResult.transactions).toHaveTransaction({ + from: launcher.address, + to: fireworks.address, + success: true, + op: Opcodes.set_first + }) + + }); + +``` + +### 交易ID:2 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L92)检查交易[ID:2]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id2.svg) + +交易[ID:2]在Fireworks launcher 1中进行,用`op::launch_first`调用,并执行了四个出站消息到the Launcher。 + +```typescript + it('should exist a transaction[ID:2] which launch first fireworks successfully', async () => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch(launcher.getSender(), toNano('2.5')); + + expect(launchResult.transactions).toHaveTransaction({ + from: fireworks.address, + to: launched_f1.address, + success: true, + op: Opcodes.launch_first, + outMessagesCount: 4, + destroyed: true, + endStatus: "non-existing", + }) + + printTransactionFees(launchResult.transactions); + + }); +``` + +在交易要影响合约状态的情况下,可以使用`destroyed`、`endStatus`字段指定。 + +完整的账户状态相关字段列表: + +- `destroyed` - `true` - 如果现有合约因执行某个交易而被销毁。否则 - `false`。 +- `deploy` - 自定义沙盒标志位,表明合约在此交易期间是否部署。如果合约在此交易前未初始化,而在此交易后变为已初始化,则为`true`。否则 - `false`。 +- `oldStatus` - 交易执行前的账户状态。值:`'uninitialized'`, `'frozen'`, `'active'`, `'non-existing'`。 +- `endStatus` - 交易执行后的账户状态。值:`'uninitialized'`, `'frozen'`, `'active'`, `'non-existing'`。 + +### 交易ID:3 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L113)检查交易[ID:3]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id3.svg) + +交易[ID:3]在Fireworks launcher 1中进行,用`op::launch_first`调用,并执行了四个出站消息到the Launcher。 + +```typescript + + it('should exist a transaction[ID:3] which launch second fireworks successfully', async () => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch(launcher.getSender(), toNano('2.5')); + + expect(launchResult.transactions).toHaveTransaction({ + from: fireworks.address, + to: launched_f2.address, + success: true, + op: Opcodes.launch_second, + outMessagesCount: 1 + }) + + printTransactionFees(launchResult.transactions); + + }); + + + + +``` + +### 交易ID:4 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L133)检查交易[ID:4]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id4.svg) + +收到来自Fireworks launcher 1的入站消息,交易[ID:4]在the Launcher(部署钱包)中进行。此消息以`send mode = 0`发送。 + +```typescript + it('should exist a transaction[ID:4] with a comment send mode = 0', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + expect(launchResult.transactions).toHaveTransaction({ + from: launched_f1.address, + to: launcher.address, + success: true, + body: beginCell().storeUint(0,32).storeStringTail("send mode = 0").endCell() // 0x00000000 comment opcode and encoded comment + + }); + }) +``` + +### 交易ID:5 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L152)检查交易[ID:5]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id5.svg) + +收到来自Fireworks launcher 1的入站消息,交易[ID:5]在the Launcher中进行。此消息以`send mode = 1`发送。 + +```typescript + it('should exist a transaction[ID:5] with a comment send mode = 1', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + expect(launchResult.transactions).toHaveTransaction({ + from: launched_f1.address, + to: launcher.address, + success: true, + body: beginCell().storeUint(0,32).storeStringTail("send mode = 1").endCell() // 0x00000000 comment opcode and encoded comment + }); + + }) + + +``` + +### 交易ID:6 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L170)检查交易[ID:6]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id6.svg) + +收到来自Fireworks launcher 1的入站消息,交易[ID:6]在the Launcher中进行。此消息以`send mode = 2`发送。 + +```typescript + it('should exist a transaction[ID:6] with a comment send mode = 2', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + expect(launchResult.transactions).toHaveTransaction({ + from: launched_f1.address, + to: launcher.address, + success: true, + body: beginCell().storeUint(0,32).storeStringTail("send mode = 2").endCell() // 0x00000000 comment opcode and encoded comment + }); + + }) +``` + +### 交易ID:7 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L188)检查交易[ID:7]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id7.svg) + +收到来自Fireworks launcher 1的入站消息,交易[ID:7]在the Launcher中进行。此消息以`send mode = 128 + 32`发送。 + +```typescript + it('should exist a transaction[ID:7] with a comment send mode = 32 + 128', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + expect(launchResult.transactions).toHaveTransaction({ + from: launched_f1.address, + to: launcher.address, + success: true, + body: beginCell().storeUint(0,32).storeStringTail("send mode = 32 + 128").endCell() // 0x00000000 comment opcode and encoded comment + }); + }) +``` + +### 交易ID:8 成功测试 + +[此测试](https://github.com/ton-community/fireworks-func/blob/main/tests/Fireworks.spec.ts#L188)检查交易[ID:8]是否成功执行。 + +![](/img/docs/writing-test-examples/test-examples-schemes_id8.svg) + +收到来自Fireworks launcher 2的入站消息,交易[ID:8]在the Launcher中进行。此消息以`send mode = 64`发送。 + +```typescript + it('should exist a transaction[ID:8] with a comment send mode = 64', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + expect(launchResult.transactions).toHaveTransaction({ + from: launched_f2.address, + to: launcher.address, + success: true, + body: beginCell().storeUint(0,32).storeStringTail("send_mode = 64").endCell() // 0x00000000 comment opcode and encoded comment + + }); + + }) + +``` + +## 打印和阅读交易费用 + +在测试期间,阅读有关费用的详细信息对优化合约很有用。printTransactionFees函数以一种方便的方式打印整个交易链。" + +```typescript + + it('should be executed and print fees', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + console.log(printTransactionFees(launchResult.transactions)); + + }); + +``` + +例如,在`launchResult`的情况下,将打印以下表格: + +| (index) | op | valueIn | valueOut | totalFees | outActions | +| -------------------------- | ------------ | -------------- | -------------- | -------------- | ---------- | +| 0 | 'N/A' | 'N/A' | '2.5 TON' | '0.010605 TON' | 1 | +| 1 | '0x5720cfeb' | '2.5 TON' | '2.185812 TON' | '0.015836 TON' | 2 | +| 2 | '0x6efe144b' | '1.092906 TON' | '1.081142 TON' | '0.009098 TON' | 4 | +| 3 | '0xa2e2c2dc' | '1.092906 TON' | '1.088638 TON' | '0.003602 TON' | 1 | +| 4 | '0x0' | '0.099 TON' | '0 TON' | '0.000309 TON' | 0 | +| 5 | '0x0' | '0.1 TON' | '0 TON' | '0.000309 TON' | 0 | +| 6 | '0x0' | '0.099 TON' | '0 TON' | '0.000309 TON' | 0 | +| 7 | '0x0' | '0.783142 TON' | '0 TON' | '0.000309 TON' | 0 | +| 8 | '0x0' | '1.088638 TON' | '0 TON' | '0.000309 TON' | 0 | + +![](/img/docs/writing-test-examples/fireworks_trace_tonviewer.png?=RAW) + +index - 是`launchResult`数组中交易的ID。 + +- `0` - 对资金库(the Launcher)的外部请求,导致发送消息`op::set_first`到Fireworks +- `1` - 导致发送4条消息到the Launcher的Fireworks交易 +- `2` - 在Launched Fireworks - 1中从the Launcher收到消息,消息使用`op::launch_first`操作码发送。 +- `2` - 在Launched Fireworks - 2中从the Launcher收到消息,消息使用`op::launch_second`操作码发送。 +- `4` - 在the Launcher中收到来自Launched Fireworks - 1的消息的交易,消息以`send mode = 0`发送 +- `5` - 在the Launcher中收到来自Launched Fireworks - 1的消息的交易,消息以`send mode = 1`发送 +- `6` - 在the Launcher中收到来自Launched Fireworks - 1的消息的交易,消息以`send mode = 2`发送 +- `7` - 在the Launcher中收到来自Launched Fireworks - 1的消息的交易,消息以`send mode = 128 + 32`发送 +- `8` - 在the Launcher中收到来自Launched Fireworks - 2的消息的交易,消息以`send mode = 64`发送 + +## 交易费用测试 + +此测试验证启动fireworks的交易费用是否符合预期。可以为佣金费用的不同部分进行自定义定价。 + +```typescript + + it('should be executed with expected fees', async() => { + + const launcher = await blockchain.treasury('launcher'); + + const launchResult = await fireworks.sendDeployLaunch( + launcher.getSender(), + toNano('2.5'), + ); + + //totalFee + console.log('total fees = ', launchResult.transactions[1].totalFees); + + const tx1 = launchResult.transactions[1]; + if (tx1.description.type !== 'generic') { + throw new Error('Generic transaction expected'); + } + + //computeFee + const computeFee = tx1.description.computePhase.type === 'vm' ? tx1.description.computePhase.gasFees : undefined; + console.log('computeFee = ', computeFee); + + //actionFee + const actionFee = tx1.description.actionPhase?.totalActionFees; + console.log('actionFee = ', actionFee); + + + if ((computeFee == null || undefined) || + (actionFee == null || undefined)) { + throw new Error('undefined fees'); + } + + //The check, if Compute Phase and Action Phase fees exceed 1 TON + expect(computeFee + actionFee).toBeLessThan(toNano('1')); + + + }); + +``` + +## 极端情况测试 + +在本节中,将提供在交易处理期间可能发生的TVM [exit codes(退出代码)](/learn/tvm-instructions/tvm-exit-codes)的测试用例。这些exit codes在区块链代码本身中。同时,必须区分在[Compute Phase( Compute Phase )](/learn/tvm-instructions/tvm-overview#compute-phase)和Action Phase(行动阶段)期间的exit code。 + +Compute Phase期间执行合约逻辑(其代码)。在处理期间,可以创建不同的action(动作)。这些action将在下一阶段 - Action Phase处理。如果Compute Phase不成功,则Action Phase不开始。然而,如果Compute Phase成功,这并不保证Action Phase也会成功结束。 + +### Compute Phase | exit code = 0 + +此exit code表示交易的Compute Phase已成功完成。 + +### Compute Phase | exit code = 1 + +标记Compute Phase成功的另一种exit code是`1`。要获得此exit code,您需要使用[RETALT](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L20)。 + +值得注意的是,这个操作码应该在主函数中调用(例如,recv_internal)。如果在另一个函数中调用,则该函数的exit将为`1`,但总体exit code将为`0`。 + +### Compute Phase | exit code = 2 + +TVM是[堆栈机](/learn/tvm-instructions/tvm-overview#tvm-is-a-stack-machine)。与不同值交互时,它们会出现在堆栈上。如果突然堆栈上没有元素,但某些操作码需要它们,那么将抛出此错误。 + +这可能发生在直接使用操作码时,因为[stdlib.fc](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/stdlib.fc)(FunC的库)假设不会有这样的问题。 + +### Compute Phase | exit code = 3 + +任何代码在执行前都变成了`continuation`。这是一种特殊的数据类型,包含有代码的切片、堆栈、寄存器和其他执行代码所需的数据。如果需要,这种continuation可以在稍后运行,来传递开始执行堆栈的必要参数。 + +首先,我们[构建](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L31-L32)这样的continuation。在这种情况下,这只是一个空的continuation,什么也不做。接下来,使用操作码`0 SETNUMARGS`,我们指示在执行开始时堆栈中不应有值。然后,使用操作码`1 -1 SETCONTARGS`,我们调用continuation,传递1个值。由于本来应该没有值,因此我们得到了StackOverflow错误。 + +### Compute Phase | exit code = 4 + +在TVM中,`integer`可以在-2256 \< x \< 2256范围内。如果在计算过程中值超出此范围,则抛出exit code 4。 + +### Compute Phase | exit code = 5 + +如果`integer`值超出预期范围,则抛出exit code 5。例如,如果在`.store_uint()`函数中使用了负值。 + +### Compute Phase | exit code = 6 + +在较低层级,使用操作码而不是熟悉的函数名称,可以在[此表](/learn/archive/tvm-instructions)中以HEX形式看到。在这个例子中,我们[使用](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L25)`@addop`,添加了一个不存在的操作码。 + +模拟器在尝试处理此操作码时无法识别它,并抛出 6。 + +### Compute Phase | exit code = 7 + +这是一个发生在接收到错误的数据类型时的很常见的错误。在[示例](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L79-L80)中,`tuple`包含3个元素,但在解包时尝试获取4个。 + +还有许多其他情况会抛出此错误。其中一些: + +- [not a null](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L433) +- [not an integer](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L441) +- [not a cell](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L478) +- [not a cell builder](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L500) +- [not a cell slice](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L509) +- [not a string](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L518) +- [not a bytes chunk](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L527) +- [not a continuation](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L536) +- [not a box](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L545) +- [not a tuple](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L554) +- [not an atom](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/stack.cpp#L598) + +### Compute Phase | exit code = 8 + +TON 中的所有数据都存储在 [cells](/develop/data-formats/cell-boc#cell) 中。一个单元格可存储 1023 位数据和 4 个指向其他单元格的引用。如果尝试写入超过 1023 位的数据或超过 4 个引用,将抛出 exit code 8。 + +### Compute Phase | exit code = 9 + +如果尝试从切片中读取比它包含的更多数据(从cell中读取数据时,必须将其转换为切片数据类型),则抛出exit code 9。例如,如果切片中有10位,而读取了11位,或者如果没有对其他引用的链接,但尝试加载引用。 + +### Compute Phase | exit code = 10 + +此错误在处理[字典](/develop/func/stdlib/#dictionaries-primitives)时抛出。例如,当值属于键时[存储在另一个cell中](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L100-L110)作为引用。在这种情况下,您需要使用`.udict_get_ref()`函数来获取这样的值。 + +然而,另一个cell中的链接[应该只有1个](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/dict.cpp#L454),而不是2个,如我们的例子: + +``` +root_cell +├── key +│ ├── value +│ └── value - second reference for one key +└── key + └── value +``` + +这就是为什么在尝试读取数值时,我们会得到 exit code 10。 + +**附加:** 您还可以在字典中存储键旁的值: + +``` +root_cell +├── key-value +└── key-value +``` + +**注意:** 实际上,字典的结构(数据如何放置在cell中)比上面的示例更复杂。因此,它们被简化了,以便理解示例。 + +### Compute Phase | exit code = 11 + +此错误发生在未知情况。例如,在使用[SENDMSG](/learn/tvm-instructions/tvm-upgrade-2023-07#sending-messages)操作码时,如果传递了[错误](https://github.com/ton-community/fireworks-func/blob/ef49b4da12d287a8f6c2b6f0c19d65814c1578fc/contracts/fireworks.fc#L116)(例如,空的)的消息cell,那么就会发生这种错误。 + +此外,它还在尝试调用不存在的方法时发生。开发人员通常是在调用不存在的GET方法时面临这种情况。 + +### Compute Phase | exit code = -14 (13) + +如果处理Compute Phase的TON不足,则抛出此错误。在枚举类`Excno`中,其中指示了Compute Phase中各种错误的exit code,[指示的值为13](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/vm/excno.hpp#L39)。 + +然而,在处理过程中,对此值应用了[NOT操作](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/block/transaction.cpp#L1574),将此值更改为`-14`。这样做是为了这个exit code不能被伪造,例如使用`throw`函数,因为所有这些函数只接受exit code是正值。 + +### Action Phase | exit code = 32 + +Action Phase在Compute Phase之后开始,它处理在Compute Phase期间写入[寄存器c5](/learn/tvm-instructions/tvm-initialization#control-register-c5)的动作。如果此寄存器中的数据写入不正确,则抛出exit code 32。 + +### Action Phase | exit code = 33 + +目前,一个交易中最多可以有`255`个动作。如果超过这个值,则Action Phase将以exit code 33 结束。 + +### Action Phase | exit code = 34 + +Exit code是造成处理action时的大部分错误的原因:无效消息、不正确动作等。 + +### Action Phase | exit code = 35 + +在构建消息的 [CommonMsgInfo](/develop/smart-contracts/tutorials/wallet#commonmsginfo) 部分时,必须指定正确的源地址。它必须等于[addr_none](/develop/data-formats/msg-tlb#addr_none00) 或发送消息的账户地址。 + +在区块链代码中,这由[check_replace_src_addr](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/block/transaction.cpp#L1985)处理。 + +### Action Phase | exit code = 36 + +如果目的地地址无效,则抛出exit code 36。一些可能的原因是不存在的工作链或不正确的地址。所有检查都可以在[check_rewrite_dest_addr](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/block/transaction.cpp#L2014-L2113)中看到。 + +### Action Phase | exit code = 37 + +此exit code类似于Compute Phase的`-14`。在这里,它意味着余额不足以发送指定金额的TON。 + +### Action Phase | exit code = 38 + +与exit code 37相同,但指的是余额中缺乏[ExtraCurrency](/develop/research-and-development/minter-flow#extracurrency)。 + +### Action Phase | exit code = 40 + +在这种情况下,有足够的TON来处理消息的某个部分(比如说5个cell),而消息中有10个cell,将抛出exit code 40。 + +### Action Phase | exit code = 43 + +可能发生的情况是超过了库中cell的最大数量或超过了Merkle树的最大深度。 + +库是存储在[Masterchain](/learn/overviews/ton-blockchain#masterchain-blockchain-of-blockchains)中的cell,如果它是[公开的](https://github.com/ton-blockchain/ton/blob/9728bc65b75defe4f9dcaaea0f62a22f198abe96/crypto/block/transaction.cpp#L1844),可以被所有智能合约使用。 + +:::info +由于更新代码时行的顺序可能会改变,一些链接变得不相关。因此,所有链接都将使用提交[9728bc65b75defe4f9dcaaea0f62a22f198abe96](https://github.com/ton-blockchain/ton/tree/9728bc65b75defe4f9dcaaea0f62a22f198abe96)时的代码库状态。 +::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-comparison.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-comparison.md new file mode 100644 index 0000000000..efa2c6ae20 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-comparison.md @@ -0,0 +1,19 @@ +# TON Connect 2.0 与 1.0 对比 + +TON Connect 2.0 解决了 TON Connect 1.0 中存在的许多问题。 + +TON Connect 2.0 协议提供了最高级别的安全性,为分散式应用程序(dApps)的开发提供了一个对开发者友好的环境,并由实时UX驱动,提供了流畅的用户体验。 + +请参阅以下两个版本之间的对比: + +| | TON&nbsp;Connect&nbsp;v1 | TON&nbsp;Connect&nbsp;v2 | +| :---------------: | :----------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: | +| 连接到基于 web 的 dApps | ✔︎ | ✔︎ | +| 发送交易 | ✔︎ | ✔︎ | +| 在钱包内连接 dapps | | ✔︎ | +| 扫描二维码 | 每次操作都需要 | 连接时只需一次 | +| 无服务器 dApps | | ✔︎ | +| 实时UX | | ✔︎ | +| 切换账户 | | 即将推出 | +| 应用和用户之间发送消息 | | 即将推出 | +| 钱包兼容性 | Tonkeeper | Tonkeeper, OpenMask, MyTonWallet, TonHub (即将推出)和[其他](/participate/wallets/apps#basics-features) | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-business.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-business.md new file mode 100644 index 0000000000..62efb8b45a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-business.md @@ -0,0 +1,46 @@ +# TON Connect 企业版 + +TON Connect 旨在通过提供吸引流量和增加用户留存的强大功能,为企业定制化服务。 + +## 产品特性 + +- 安全私密的认证,可控的个人数据公开 +- 在单个用户会话内的 TON 上进行任意事务签名 +- 应用程序与用户钱包之间的即时连通性 +- 钱包内直接自动可用的应用程序 + +## 采用 TON Connect + +### 基本步骤 + +为了让开发者将 TON Connect 集成到他们的应用中,使用了专门的 TON Connect SDK。这个过程相当简单,可以在需要时访问正确的文档进行操作。 + +TON Connect 允许用户通过二维码或通用连接链接将他们的应用与众多钱包连接。应用程序还可以使用内置浏览器扩展在钱包内打开,因此了解添加到TON Connect中的附加功能至关重要。 + +### TON Connect 的开发者集成协助 + +1. 描述您的应用的现有用户流程 +2. 确定所需的操作(例如,事务授权,消息签名) +3. 向我们的团队描述您的技术栈 + +如果您想了解更多关于 TON Connect 及其各种服务和功能,欢迎通过 [developer](https://t.me/tonrostislav) 与 TON Connect 业务团队联系,讨论您想要的解决方案。 + +### 常见的实现案例 + +通过使用 [TON Connect SDK](https://github.com/ton-connect/sdk),详细的集成说明让开发者能够: + +- 将他们的应用与各种 TON 钱包类型连接 +- 通过相应钱包的地址进行后端登录 +- 发送请求事务和在钱包内签名(接受请求) + +为了更好地了解这个解决方案的可能性,请查看我们在 Github 上可用的示例应用:[https://github.com/ton-connect/](https://github.com/ton-connect/demo-dapp) + +### 目前支持的技术栈: + +- 所有 web 应用 —— 无服务器的和后端的 +- React-Native 移动应用 +- 即将推出:Swift、Java、Kotlin 的移动应用 SDK + +TON Connect 是一个开放协议,可用于使用任何编程语言或开发环境开发 dapps。 + +对于 JavaScript (JS) 应用,TON 开发者社区创建了一个 JavaScript SDK,使开发者能够在几分钟内无缝集成 TON Connect。将来,将提供设计用于其他编程语言的 SDK。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-security.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-security.md new file mode 100644 index 0000000000..cc1f118320 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/business/ton-connect-for-security.md @@ -0,0 +1,11 @@ +# TON Connect 的安全性 + +TON Connect 确保用户对他们分享的数据有明确的控制权,这意味着在应用程序和钱包传输期间数据不会泄露。为了加强这一设计,钱包和应用采用了强大的加密身份验证系统,这些系统相互协作。 + +## 用户数据和资金的安全性 + +- 在 TON Connect 上,当用户数据通过bridge被传输到钱包时,是端到端加密的。这允许应用和钱包使用第三方bridge服务器,减少数据被盗和被篡改的可能性,在此过程中显著提高数据的完整性和安全性。 +- 通过 TON Connect,安全参数被设置以允许用户数据直接与他们的钱包地址进行认证。这允许用户使用多个钱包,并选择在特定应用内使用哪一个。 +- TON Connect 协议允许分享个人数据项(如联系方式和 KYC 信息等),这意味着用户明确确认了这些数据的分享。 + +有关 TON Connect 及其以安全为重点的设计的具体细节和相关代码示例,可以通过 [TON Connect Github](https://github.com/ton-connect/) 找到。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/frameworks/react.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/frameworks/react.mdx new file mode 100644 index 0000000000..0c8ba0c270 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/frameworks/react.mdx @@ -0,0 +1,332 @@ +# TON Connect for React + +推荐用于React应用程序的SDK是[UI React SDK](/develop/dapps/ton-connect/developers#ton-connect-react)。它是一个React组件,提供了与TON Connect交互的高级方式。 + +## 实现 + +### 1. 安装 + +要开始将TON Connect集成到您的DApp中,您需要安装`@tonconnect/ui-react`包。您可以使用npm或yarn来实现这一目的: + +```bash npm2yarn +npm i @tonconnect/ui-react +``` + +### 2. TON Connect初始化 + +安装包之后,您应该为您的应用程序创建一个`manifest.json`文件。有关如何创建manifest.json文件的更多信息,请参阅[此处](/develop/dapps/ton-connect/manifest)。 + +创建manifest文件后,将TonConnectUIProvider导入到您的Mini App的根目录,并传入manifest URL: + +```tsx +import { TonConnectUIProvider } from '@tonconnect/ui-react'; + +export function App() { + return ( + + { /* Your app */ } + + ); +} + +``` + +### 3. 连接到钱包 + +添加`TonConnectButton`。TonConnect按钮是初始化连接的通用UI组件。连接钱包后,它会变成钱包菜单。建议将其放置在应用程序的右上角。 + +```tsx +export const Header = () => { + return ( +
+ My App with React UI + +
+ ); +}; +``` + +您还可以为按钮添加className和style属性。请注意,您不能给TonConnectButton传递子组件: + +```js + +``` + +此外,您始终可以使用`useTonConnectUI`hook和[connectWallet](https://github.com/ton-connect/sdk/tree/main/packages/ui#call-connect)方法手动初始化连接。 + +### 4. 重定向 + +如果您想在连接钱包后重定向用户至特定页面,您可以使用`useTonConnectUI`hook和[定制您的返回策略](https://github.com/ton-connect/sdk/tree/main/packages/ui#add-the-return-strategy)。 + +#### Telegram小程序 + +如果您想在连接钱包后重定向用户至[Telegram Mini App](/develop/dapps/telegram-apps/),您可以定制`TonConnectUIProvider`元素: + +```tsx + ' + }} + > + +``` + +[在GitHub上查看示例](https://github.com/ton-connect/demo-dapp-with-wallet/blob/master/src/App.tsx) + +### 5. UI自定义 + +要[定制模态窗口的UI](https://github.com/ton-connect/sdk/tree/main/packages/ui#ui-customisation),您可以使用`useTonConnectUI`hook和`setOptions`函数。详见[Hooks](#hooks)部分中关于useTonConnectUIhook的更多信息。 + +## Hooks + +如果您想在React应用程序中使用一些低级TON Connect UI SDK的特性,您可以使用`@tonconnect/ui-react`包中的hook。 + +### useTonAddress + +使用它来获取用户当前的ton钱包地址。传递布尔参数isUserFriendly来选择地址的格式。如果钱包未连接,hook将返回空字符串。 + +```tsx +import { useTonAddress } from '@tonconnect/ui-react'; + +export const Address = () => { + const userFriendlyAddress = useTonAddress(); + const rawAddress = useTonAddress(false); + + return ( + userFriendlyAddress && ( +
+ User-friendly address: {userFriendlyAddress} + Raw address: {rawAddress} +
+ ) + ); +}; +``` + +### useTonWallet + +使用它来获取用户当前的ton钱包。如果钱包未连接,hook会返回null。 + +查看所有钱包属性 + +[Wallet接口](https://ton-connect.github.io/sdk/interfaces/_tonconnect_sdk.Wallet.html) +[WalletInfo接口](https://ton-connect.github.io/sdk/types/_tonconnect_sdk.WalletInfo.html) + +```tsx +import { useTonWallet } from '@tonconnect/ui-react'; + +export const Wallet = () => { + const wallet = useTonWallet(); + + return ( + wallet && ( +
+ Connected wallet: {wallet.name} + Device: {wallet.device.appName} +
+ ) + ); +}; +``` + +### useTonConnectUI + +使用它来获取`TonConnectUI`实例和UI选项更新函数的访问权限。 + +[了解更多关于TonConnectUI实例方法](https://github.com/ton-connect/sdk/tree/main/packages/ui#send-transaction) + +[了解更多关于setOptions函数](https://github.com/ton-connect/sdk/tree/main/packages/ui#change-options-if-needed) + +```tsx +import { Locales, useTonConnectUI } from '@tonconnect/ui-react'; + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + const onLanguageChange = (lang: string) => { + setOptions({ language: lang as Locales }); + }; + + return ( +
+ + +
+ + +
+
+ ); +}; +``` + +### useIsConnectionRestored + +指示连接恢复过程的当前状态。 +您可以使用它检测连接恢复过程是否已完成。 + +```tsx +import { useIsConnectionRestored } from '@tonconnect/ui-react'; + +export const EntrypointPage = () => { + const connectionRestored = useIsConnectionRestored(); + + if (!connectionRestored) { + return Please wait...; + } + + return ; +}; +``` + +## 使用 + +让我们来看看如何在实践中使用React UI SDK。 + +### 发送交易 + +向特定地址发送TON币(以nanotons计): + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const transaction = { + messages: [ + { + address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // destination address + amount: "20000000" //Toncoin in nanotons + } + ] + +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + +- 在这里获取更多示例:[准备消息](/develop/dapps/ton-connect/message-builders) + +### 通过哈希理解交易状态 + +位于支付处理中(使用tonweb)的原理。[了解更多](/develop/dapps/asset-processing/#checking-contracts-transactions) + +### 后端的可选检查(tonproof) + +:::tip +了解如何签名和验证消息:[签名与验证](/develop/dapps/ton-connect/sign) +::: + +使用`tonConnectUI.setConnectRequestParameters`函数来传递你的连接请求参数。 + +该函数接受一个参数: + +当你在等待来自后端的响应时,设置状态为 ‘loading’。如果用户此刻打开连接钱包modals,他会看到一个加载器。 + +```ts +const [tonConnectUI] = useTonConnectUI(); + +tonConnectUI.setConnectRequestParameters({ + state: 'loading' +}); +``` + +或 + +设置状态为 ‘ready’ 并定义 `tonProof` 值。传递的参数将应用于连接请求(二维码和通用链接)。 + +```ts +const [tonConnectUI] = useTonConnectUI(); + +tonConnectUI.setConnectRequestParameters({ + state: 'ready', + value: { + tonProof: '' + } +}); +``` + +或 + +如果通过 `state: 'loading'` 启用了加载器(例如,你从后端收到一个错误而不是响应),则移除加载器。连接请求将不包含任何额外参数。 + +```ts +const [tonConnectUI] = useTonConnectUI(); + +tonConnectUI.setConnectRequestParameters(null); +``` + +如果你的tonProof有效载荷的期限是有限的(例如,你可以每10分钟刷新一次连接请求参数),你可以多次调用`tonConnectUI.setConnectRequestParameters`。 + +```ts +const [tonConnectUI] = useTonConnectUI(); + +// enable ui loader +tonConnectUI.setConnectRequestParameters({ state: 'loading' }); + +// fetch you tonProofPayload from the backend +const tonProofPayload: string | null = await fetchTonProofPayloadFromBackend(); + +if (!tonProofPayload) { + // remove loader, connect request will be without any additional parameters + tonConnectUI.setConnectRequestParameters(null); +} else { + // add tonProof to the connect request + tonConnectUI.setConnectRequestParameters({ + state: "ready", + value: { tonProof: tonProofPayload } + }); +} + +``` + +当钱包连接时,你可以在`wallet`对象中找到`ton_proof`结果: + +```ts +import {useTonConnectUI} from "@tonconnect/ui-react"; + +const [tonConnectUI] = useTonConnectUI(); + +useEffect(() => + tonConnectUI.onStatusChange(wallet => { + if (wallet.connectItems?.tonProof && 'proof' in wallet.connectItems.tonProof) { + checkProofInYourBackend(wallet.connectItems.tonProof.proof, wallet.account); + } + }), []); +``` + +### 钱包断开 + +断开钱包的调用: + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const [tonConnectUI] = useTonConnectUI(); + +await tonConnectUI.disconnect(); +``` + +## API 文档 + +[最新的API文档](https://ton-connect.github.io/sdk/modules/_tonconnect_ui_react.html) + +## 示例 + +- [TON Hello World 指南](https://ton-community.github.io/tutorials/03-client/) 来创建一个简单的DApp与React UI。 +- [Demo dApp](https://github.com/ton-connect/demo-dapp-with-react-ui) - 使用`@tonconnect/ui-react`的DApp示例。 +- [ton.vote](https://github.com/orbs-network/ton-vote) - 带有TON Connect实现的React网站示例。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/creating-manifest.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/creating-manifest.md new file mode 100644 index 0000000000..06d75cc616 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/creating-manifest.md @@ -0,0 +1,40 @@ +# 创建 manifest.json + +每个应用都需要有它的 manifest 文件,用以向钱包传递元信息。Manifest 是一个名为 `tonconnect-manifest.json` 的 JSON 文件,遵循以下格式: + +```json +{ + "url": "", // required + "name": "", // required + "iconUrl": "", // required + "termsOfUseUrl": "", // optional + "privacyPolicyUrl": "" // optional +} +``` + +## 示例 + +您可以在下面找到一个 manifest 的示例: + +```json +{ + "url": "https://ton.vote", + "name": "TON Vote", + "iconUrl": "https://ton.vote/logo.png" +} +``` + +## 最佳实践 + +- 最佳实践是将 manifest 放置在您应用和库的根目录,例如 `https://myapp.com/tonconnect-manifest.json`。这样可以让钱包更好地处理您的应用,并提升与您应用相关的用户体验。 +- 确保 `manifest.json` 文件通过其 URL 可以被 GET 访问。 + +## 字段描述 + +| 字段 | 要求 | 描述 | +| ------------------ | -- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `url` | 必需 | 应用 URL。将被用作 DApp 标识符。点击钱包中的图标后,将用来打开 DApp。推荐传递不带关闭斜杠的 url,例如 'https://mydapp.com' 而非 'https://mydapp.com/'。 | +| `name` | 必需 | 应用名称。可以简单,不会被用作标识符。 | +| `iconUrl` | 必需 | 应用图标的 URL。必须是 PNG、ICO 等格式,不支持 SVG 图标。最好传递指向 180x180px PNG 图标的 url。 | +| `termsOfUseUrl` | 可选 | 使用条款文档的 url。普通应用为可选,但对于放在 Tonkeeper 推荐应用列表中的应用则为必填。 | +| `privacyPolicyUrl` | 可选 | 隐私政策文档的 url。普通应用为可选,但对于放在 Tonkeeper 推荐应用列表中的应用则为必填。 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/developers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/developers.md new file mode 100644 index 0000000000..60c46ea119 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/developers.md @@ -0,0 +1,162 @@ +# TON Connect SDKs + +## SDK 列表 + +:::info +如果可能,建议您为您的 dApps 使用 [@tonconnect/ui-react](/develop/dapps/ton-connect/developers#ton-connect-ui-react) 工具包。仅当您的产品确实需要时,才切换到 SDK 的更低层级或重新实现协议版本。 +::: + +本页内容包括 TON Connect 的有用的库列表。 + +- [TON Connect React](/develop/dapps/ton-connect/developers#ton-connect-react) +- [TON Connect JS SDK](/develop/dapps/ton-connect/developers#ton-connect-js-sdk) +- [TON Connect Python SDK](/develop/dapps/ton-connect/developers#ton-connect-python) +- [TON Connect Dart](/develop/dapps/ton-connect/developers#ton-connect-dart) +- [TON Connect C#](/develop/dapps/ton-connect/developers#ton-connect-c) +- [TON Connect Unity](/develop/dapps/ton-connect/developers#ton-connect-unity) +- [TON Connect Go](/develop/dapps/ton-connect/developers#ton-connect-go) + +## TON Connect React + +- [@tonconnect/ui-react](/develop/dapps/ton-connect/developers#ton-connect-ui-react) - 适用于 React 应用的 TON Connect 用户界面(UI) + +TonConnect UI React 是一个 React UI 工具包,用于在 React 应用中通过 TonConnect 协议连接您的应用程序至 TON 钱包。 + +- 包含 `@tonconnect/ui-react` 的 DApp 示例:[GitHub](https://github.com/ton-connect/demo-dapp-with-react-ui) +- 部署的 `demo-dapp-with-react-ui` 示例:[GitHub](https://ton-connect.github.io/demo-dapp-with-react-ui/) + +```bash +npm i @tonconnect/ui-react +``` + +- [GitHub](https://github.com/ton-connect/sdk/tree/main/packages/ui-react) +- [NPM](https://www.npmjs.com/package/@tonconnect/ui-react) +- [API 文档](https://ton-connect.github.io/sdk/modules/_tonconnect_ui_react.html) + +## TON Connect JS SDK + +TON Connect 存储库包含以下主要包: + +- [@tonconnect/ui](/develop/dapps/ton-connect/developers#ton-connect-ui) - TON Connect 用户界面(UI) +- [@tonconnect/sdk](/develop/dapps/ton-connect/developers#ton-connect-sdk) - TON Connect SDK +- [@tonconnect/protocol](/develop/dapps/ton-connect/developers#ton-connect-protocol-models) - TON Connect 协议规范 + +### TON Connect UI + +TonConnect UI 是 TonConnect SDK 的一个 UI 工具包。使用它可以通过 TonConnect 协议将您的应用程序连接到 TON 钱包。它允许您使用我们的 UI 元素(如“连接钱包按钮”、“选择钱包对话框”和确认modals)更轻松地将 TonConnect 集成到您的应用中。 + +```bash +npm i @tonconnect/ui +``` + +- [GitHub](https://github.com/ton-connect/sdk/tree/main/packages/ui) +- [NPM](https://www.npmjs.com/package/@tonconnect/ui) +- [API 文档](https://ton-connect.github.io/sdk/modules/_tonconnect_ui.html) + +TON Connect 用户界面(UI)是一个框架,允许开发者提高应用用户的用户体验(UX)。 + +TON Connect 可以通过简单的 UI 元素(如“连接钱包按钮”、“选择钱包对话框”和确认模态)轻松地与应用集成。这里有三个主要示例,展示了 TON Connect 如何在应用中提升 UX: + +- DApp 浏览器中的应用功能示例:[GitHub](https://ton-connect.github.io/demo-dapp/) +- 上述 DApp 的后端部分示例:[GitHub](https://github.com/ton-connect/demo-dapp-backend) +- 使用 Go 的 Bridge 服务器:[GitHub](https://github.com/ton-connect/bridge) + +此工具包将简化用 TON Connect 实现到 TON 区块链为目标平台所构建的应用中。它支持标准的前端框架,以及不使用预定框架的应用。 + +### TON Connect SDK + +这三个框架中最底层的一个是 TON Connect SDK,它帮助开发者将 TON Connect 集成到他们的应用程序中。它主要用于通过 TON Connect 协议将应用程序连接到 TON 钱包。 + +- [GitHub](https://github.com/ton-connect/sdk/tree/main/packages/sdk) +- [NPM](https://www.npmjs.com/package/@tonconnect/sdk) + +### TON Connect 协议模型 + +该包含协议请求、协议响应、事件模型以及编码和解码功能。它可用于将 TON Connect 集成到用 TypeScript 编写的钱包应用中。为了将 TON Connect 集成到 DApp 中,应该使用 [@tonconnect/sdk](https://www.npmjs.com/package/@tonconnect/sdk)。 + +- [GitHub](https://github.com/ton-connect/sdk/tree/main/packages/protocol) +- [NPM](https://www.npmjs.com/package/@tonconnect/protocol) + +## TON Connect Python + +### pytonconnect + +TON Connect 2.0 的 Python SDK。相当于 `@tonconnect/sdk` 库。 + +使用它可以通过 TonConnect 协议将您的应用程序连接到 TON 钱包。 + +```bash +pip3 install pytonconnect +``` + +- [GitHub](https://github.com/XaBbl4/pytonconnect) + +### ClickoTON-Foundation tonconnect + +用于将 TON Connect 连接到 Python 应用的库 + +```bash +git clone https://github.com/ClickoTON-Foundation/tonconnect.git +pip install -e tonconnect +``` + +[GitHub](https://github.com/ClickoTON-Foundation/tonconnect) + +## TON Connect Dart + +TON Connect 2.0 的 Dart SDK。相当于 `@tonconnect/sdk` 库。 + +使用它可以通过 TonConnect 协议将您的应用程序连接到 TON 钱包。 + +```bash + $ dart pub add darttonconnect +``` + +- [GitHub](https://github.com/romanovichim/dartTonconnect) + +## TON Connect C\# + +TON Connect 2.0 的 C# SDK。相当于 `@tonconnect/sdk` 库。 + +使用它可以通过 TonConnect 协议将您的应用程序连接到 TON 钱包。 + +```bash + $ dotnet add package TonSdk.Connect +``` + +- [GitHub](https://github.com/continuation-team/TonSdk.NET/tree/main/TonSDK.Connect) + +## TON Connect Go + +TON Connect 2.0 的 Go SDK。 + +使用它可以通过 TonConnect 协议将您的应用程序连接到 TON 钱包。 + +```bash + go get github.com/cameo-engineering/tonconnect +``` + +- [GitHub](https://github.com/cameo-engineering/tonconnect) + +## 常见问题和关注点 + +如果我们的开发者或社区成员在使用 TON Connect 2.0 期间遇到任何额外问题,请联系 [Tonkeeper 开发者](https://t.me/tonkeeperdev) 频道。 + +如果您遇到任何额外的问题,或者想提出有关如何改进 TON Connect 2.0 的提议,请通过适当的 [GitHub 目录](https://github.com/ton-connect/) 直接联系我们。 + +## TON Connect Unity + +TON Connect 2.0 的 Unity 资源。使用 `continuation-team/TonSdk.NET/tree/main/TonSDK.Connect`。 + +使用它将 TonConnect 协议与您的游戏集成。 + +- [GitHub](https://github.com/continuation-team/unity-ton-connect) +- [Docs](https://docs.tonsdk.net/user-manual/unity-tonconnect-2.0/getting-started) + +## 参阅 + +- [构建您的第一个 Web 客户端的分步指南](https://ton-community.github.io/tutorials/03-client/) +- [[YouTube] TON 智能合约 | 10 | Telegram DApp[EN]](https://www.youtube.com/watch?v=D6t3eZPdgAU\&t=254s\&ab_channel=AlefmanVladimir%5BEN%5D) +- [Ton Connect 入门](https://github.com/ton-connect/sdk/tree/main/packages/sdk) +- [集成手册](/develop/dapps/ton-connect/integration) +- [[YouTube] TON Dev 研究 TON Connect 协议 [RU]](https://www.youtube.com/playlist?list=PLyDBPwv9EPsCJ226xS5_dKmXXxWx1CKz_) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/how-ton-connect-works.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/how-ton-connect-works.mdx new file mode 100644 index 0000000000..5661e9a20e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/how-ton-connect-works.mdx @@ -0,0 +1,31 @@ +import Button from '@site/src/components/button' +import ThemedImage from '@theme/ThemedImage'; + +# TON Connect的工作原理 + +TON Connect是TON中**钱包**和**应用**之间的通信协议。 + +

+ +

+ +## 概览 + +基于TON构建的**应用**提供丰富的功能和高性能,并旨在通过智能合约保护用户资金。由于应用程序使用了区块链等去中心化技术进行构建,它们通常被称为去中心化应用程序(dApps)。 + +**钱包**提供了批准交易的用户界面,并在个人设备上安全地持有用户的加密密钥。 +这种关注点的分离使得用户能够实现快速创新和高水平的安全性:钱包不需要自行构建封闭的生态系统,而应用程序也不需要承担持有最终用户账户的风险。 + +TON Connect旨在提供钱包和应用之间的无缝用户体验。 + +## 另请参见 + +- [TON Connect 企业版](/develop/dapps/ton-connect/business) +- [TON Connect安全](/develop/dapps/ton-connect/security) +- [TON Connect2.0与1.0的对比](/develop/dapps/ton-connect/comparison) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/integration-with-javascript-sdk.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/integration-with-javascript-sdk.md new file mode 100644 index 0000000000..7694b2fa85 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/integration-with-javascript-sdk.md @@ -0,0 +1,506 @@ +# 与 JavaScript SDK 的集成手册 + +在本教程中,我们将创建一个示例网页应用,支持 TON Connect 2.0 认证。这将允许进行签名验证,以消除在各方之间未建立协议时的身份冒用的可能性。 + +## 文档链接 + +1. [@tonconnect/sdk 文档](https://www.npmjs.com/package/@tonconnect/sdk) +2. [钱包应用消息交换协议](https://github.com/ton-connect/docs/blob/main/requests-responses.md) +3. [Tonkeeper 钱包端实现](https://github.com/tonkeeper/wallet/tree/main/src/tonconnect) + +## 必要条件 + +为了使应用和钱包之间的连接流畅,网页应用必须使用可通过钱包应用访问的 manifest。完成此项的必要条件通常是静态文件的主机。例如,假如开发者想利用 GitHub 页面,或使用托管在他们电脑上的 TON Sites 部署他们的网站。这将意味着他们的网页应用站点是公开可访问的。 + +## 获取钱包支持列表 + +为了提高 TON 区块链的整体采用率,TON Connect 2.0 需要能够促进大量应用和钱包连接集成。近期,TON Connect 2.0 的持续开发使得连接 Tonkeeper、TonHub、MyTonWallet 和其他钱包与各种 TON 生态系统应用成为可能。我们的使命是最终允许通过 TON Connect 协议在基于 TON 构建的所有钱包类型与应用之间交换数据。目前,这是通过为TON Connect提供加载当前在TON生态系统中运行的可用钱包的广泛列表的能力来实现的。 + +目前我们的示例网页应用能够实现以下功能: + +1. 加载 TON Connect SDK(旨在简化集成的库), +2. 创建一个连接器(当前没有应用 manifest), +3. 加载支持的钱包列表(来自 [GitHub 上的 wallets.json](https://raw.githubusercontent.com/ton-connect/wallets-list/main/wallets.json))。 + +为了学习目的,让我们来看看以下代码描述的 HTML 页面: + +```html + + + + + + + + + + +``` + +如果您在浏览器中加载此页面并查看控制台,可能会得到类似以下内容: + +```bash +> Array [ {…}, {…} ] + +0: Object { name: "Tonkeeper", imageUrl: "https://tonkeeper.com/assets/tonconnect-icon.png", aboutUrl: "https://tonkeeper.com", … } + aboutUrl: "https://tonkeeper.com" + bridgeUrl: "https://bridge.tonapi.io/bridge" + deepLink: undefined + embedded: false + imageUrl: "https://tonkeeper.com/assets/tonconnect-icon.png" + injected: false + jsBridgeKey: "tonkeeper" + name: "Tonkeeper" + tondns: "tonkeeper.ton" + universalLink: "https://app.tonkeeper.com/ton-connect" +``` + +根据 TON Connect 2.0 规范,钱包应用信息总是使用以下格式: + +```js +{ + name: string; + imageUrl: string; + tondns?: string; + aboutUrl: string; + universalLink?: string; + deepLink?: string; + bridgeUrl?: string; + jsBridgeKey?: string; + injected?: boolean; // true if this wallet is injected to the webpage + embedded?: boolean; // true if the DAppis opened inside this wallet's browser +} +``` + +## 不同钱包应用的按钮显示 + +按钮可能会根据您的网页应用设计而变化。 +当前页面产生以下结果: + +```html + + + + + + + // highlight-start + + // highlight-end + + + // highlight-start +
+
+ // highlight-end + + + + +``` + +请注意以下几点: + +1. 如果网页通过钱包应用显示,它会将 `embedded` 选项设置为 `true`。这意味着标记这个登录选项很重要,因为它是最常使用的。 +2. 如果一个特定的钱包只使用 JavaScript 构建(它没有 `bridgeUrl`),并且它没有设置 `injected` 属性(或 `embedded`,为了安全),那么它显然是不可访问的,按钮应该被禁用。 + +## 无应用 manifest 的连接 + +在没有应用 manifest 的情况下进行连接时,脚本应该如下更改: + +```js + const $ = document.querySelector.bind(document); + + window.onload = async () => { + const connector = new TonConnectSDK.TonConnect(); + const walletsList = await connector.getWallets(); + + const unsubscribe = connector.onStatusChange( + walletInfo => { + console.log('Connection status:', walletInfo); + } + ); + + let buttonsContainer = $('#tonconnect-buttons'); + + for (let wallet of walletsList) { + let connectButton = document.createElement('button'); + connectButton.innerText = 'Connect with ' + wallet.name; + + if (wallet.embedded) { + // `embedded` means we are browsing the app from wallet application + // we need to mark this sign-in option somehow + connectButton.classList.add('featured'); + } + + // highlight-start + if (wallet.embedded || wallet.injected) { + connectButton.onclick = () => { + connectButton.disabled = true; + connector.connect({jsBridgeKey: wallet.jsBridgeKey}); + }; + } else if (wallet.bridgeUrl) { + connectButton.onclick = () => { + connectButton.disabled = true; + console.log('Connection link:', connector.connect({ + universalLink: wallet.universalLink, + bridgeUrl: wallet.bridgeUrl + })); + }; + } else { + // wallet app does not provide any auth method + connectButton.disabled = true; + } + // highlight-end + + buttonsContainer.appendChild(connectButton); + } + }; +``` + +现在已经进行了上述操作,正在记录状态变化(以查看 TON Connect 是否工作)。展示用于连接的 QR 代码的modals超出了本手册的范围。为了测试目的,可以使用浏览器扩展或通过任何必要的手段将连接请求链接发送到用户的手机(例如,使用 Telegram)。 +注意:我们还没有创建应用 manifest。目前,如果未满足此要求,最佳做法是分析最终结果。 + +### 使用 Tonkeeper 登录 + +为了用 Tonkeeper 登录,创建了以下用于认证的链接(下面提供参考): + +``` +https://app.tonkeeper.com/ton-connect?v=2&id=3c12f5311be7e305094ffbf5c9b830e53a4579b40485137f29b0ca0c893c4f31&r=%7B%22manifestUrl%22%3A%22null%2Ftonconnect-manifest.json%22%2C%22items%22%3A%5B%7B%22name%22%3A%22ton_addr%22%7D%5D%7D +``` + +当解码时,`r` 参数产生以下 JSON 格式: + +```js +{"manifestUrl":"null/tonconnect-manifest.json","items":[{"name":"ton_addr"}]} +``` + +点击手机链接后,Tonkeeper 自动打开然后关闭,忽略请求。此外,在网页应用页面的控制台出现以下错误: +`Error: [TON_CONNECT_SDK_ERROR] Can't get null/tonconnect-manifest.json`。 + +这意味着应用 manifest 必须可供下载。 + +## 使用应用清单连接 + +从现在开始,需要在某处托管用户文件(主要是tonconnect-manifest.json)。在这个例子中,我们将使用另一个Web应用程序的清单。然而,这不推荐用于生产环境,但允许用于测试目的。 + +以下代码片段: + +```js + window.onload = async () => { + const connector = new TonConnectSDK.TonConnect(); + + const walletsList = await connector.getWallets(); + + const unsubscribe = connector.onStatusChange( + walletInfo => { + console.log('Connection status:', walletInfo); + } + ); +``` + +必须被这个版本替换: + +```js + window.onload = async () => { + const connector = new TonConnectSDK.TonConnect({manifestUrl: 'https://ratingers.pythonanywhere.com/ratelance/tonconnect-manifest.json'}); + // highlight-next-line + window.connector = connector; // for experimenting in browser console + + const walletsList = await connector.getWallets(); + + const unsubscribe = connector.onStatusChange( + walletInfo => { + console.log('Connection status:', walletInfo); + } + ); + // highlight-next-line + connector.restoreConnection(); +``` + +在上方的新版本中,添加了将 `connector` 变量存储在 `window` 中,使其在浏览器控制台中可以访问。此外,添加了 `restoreConnection`,这样用户就不必在每个Web应用程序页面都登录。 + +### 用Tonkeeper登录 + +如果我们拒绝钱包的请求,控制台显示的结果将是`Error: [TON_CONNECT_SDK_ERROR] Wallet declined the request`。 + +因此,如果保存了链接,用户能够接受相同的登录请求。这意味着Web应用程序应该能够将认证拒绝视为非最终状态,以确保其正确工作。 + +之后,接受登录请求,浏览器控制台立即反映如下: + +```bash +22:40:13.887 Connection status: +Object { device: {…}, provider: "http", account: {…} } + account: Object { address: "0:b2a1ec...", chain: "-239", walletStateInit: "te6cckECFgEAAwQAAgE0ARUBFP8A9..." } + device: Object {platform: "android", appName: "Tonkeeper", appVersion: "2.8.0.261", …} + provider: "http" +``` + +以上结果考虑了以下内容: + +1. **账户**:包含地址(工作链+哈希)、网络(主网/测试网)以及用于提取公钥的walletStateInit的信息。 +2. **设备**:包含请求时的名称和钱包应用程序版本(名称应该与最初请求的相同,但这可以进行验证以确保真实性),以及平台名称和支持功能列表。 +3. **提供者**:包含http -- 这允许钱包与Web应用程序之间进行的所有请求与响应通过bridge进行服务。 + +## 登出并请求TonProof + +现在我们已经登录了我们的Mini App,但是...后端如何知道它是正确的一方呢?为了验证这一点,我们必须请求钱包所有权证明。 + +这只能通过认证来完成,所以我们必须登出。因此,我们在控制台中运行以下代码: + +```js +connector.disconnect(); +``` + +当断开连接过程完成时,将显示 `Connection status: null`。 + +在添加TonProof之前,让我们更改代码以表明当前实现是不安全的: + +```js +let connHandler = connector.statusChangeSubscriptions[0]; +connHandler({ + device: { + appName: "Uber Singlesig Cold Wallet App", + appVersion: "4.0.1", + features: [], + maxProtocolVersion: 3, + platform: "ios" + }, + account: { + /* TON Foundation address */ + address: '0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8', + chain: '-239', + walletStateInit: 'te6ccsEBAwEAoAAFcSoCATQBAgDe/wAg3SCCAUyXuiGCATOcurGfcbDtRNDTH9MfMdcL/+ME4KTyYIMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOjRAaTIyx/LH8v/ye1UAFAAAAAAKamjF3LJ7WtipuLroUqTuQRi56Nnd3vrijj7FbnzOETSLOL/HqR30Q==' + }, + provider: 'http' +}); +``` + +控制台显示的代码行几乎与最初启动连接时显示的一样。因此,如果后端不按预期正确执行用户认证,需要一个方法来测试它是否工作正确。为了实现这一点,可以在控制台中充当TON Foundation,以便可以测试令牌余额和令牌所有权参数的合法性。自然,提供的代码不会更改连接器中的任何变量,但是用户可以根据自己的意愿使用应用程序,除非该连接器受到闭包的保护。即使是这种情况,使用调试器和编码断点也不难提取它。 + +现在用户的认证已经得到验证,让我们继续写代码。 + +## 使用TonProof连接 + +根据TON Connect的SDK文档,第二个参数指的是`connect()`方法,其中包含将由钱包warp并签名的有效载荷。因此,结果是新的连接代码: + +```js + if (wallet.embedded || wallet.injected) { + connectButton.onclick = () => { + connectButton.disabled = true; + connector.connect({jsBridgeKey: wallet.jsBridgeKey}, + {tonProof: 'doc-example-'}); + }; + } else if (wallet.bridgeUrl) { + connectButton.onclick = () => { + connectButton.disabled = true; + console.log('Connection link:', connector.connect({ + universalLink: wallet.universalLink, + bridgeUrl: wallet.bridgeUrl + }, {tonProof: 'doc-example-'})); + }; +``` + +连接链接: + +``` +https://app.tonkeeper.com/ton-connect?v=2&id=4b0a7e2af3b455e0f0bafe14dcdc93f1e9e73196ae2afaca4d9ba77e94484a44&r=%7B%22manifestUrl%22%3A%22https%3A%2F%2Fratingers.pythonanywhere.com%2Fratelance%2Ftonconnect-manifest.json%22%2C%22items%22%3A%5B%7B%22name%22%3A%22ton_addr%22%7D%2C%7B%22name%22%3A%22ton_proof%22%2C%22payload%22%3A%22doc-example-%3CBACKEND_AUTH_ID%3E%22%7D%5D%7D +``` + +展开并简化的`r`参数: + +```js +{ + "manifestUrl": + "https://ratingers.pythonanywhere.com/ratelance/tonconnect-manifest.json", + "items": [ + {"name": "ton_addr"}, + {"name": "ton_proof", "payload": "doc-example-"} + ] +} +``` + +接下来,将url地址链接发送到移动设备并使用Tonkeeper打开。 + +完成此过程后,接收到以下特定于钱包的信息: + +```js +{ + "device": { + "platform": "android", + "appName": "Tonkeeper", + "appVersion": "2.8.0.261", + "maxProtocolVersion": 2, + "features": [ + "SendTransaction" + ] + }, + "provider": "http", + "account": { + "address": "0:b2a1ecf5545e076cd36ae516ea7ebdf32aea008caa2b84af9866becb208895ad", + "chain": "-239", + "walletStateInit": "te6cckECFgEAAwQAAgE0ARUBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjFyM60x2mt5eboNyOTE+5RGOe9Ee2rK1Qcb+0ZuiP9vb7QJRlz/c=" + }, + "connectItems": { + "tonProof": { + "name": "ton_proof", + "proof": { + "timestamp": 1674392728, + "domain": { + "lengthBytes": 28, + "value": "ratingers.pythonanywhere.com" + }, + "signature": "trCkHit07NZUayjGLxJa6FoPnaGHkqPy2JyNjlUbxzcc3aGvsExCmHXi6XJGuoCu6M2RMXiLzIftEm6PAoy1BQ==", + "payload": "doc-example-" + } + } + } +} +``` + +让我们验证接收到的签名。为了完成这一点,签名验证使用Python,因为它可以轻松地与后端交互。要进行这个过程所需的库是`tonsdk`和`pynacl`。 + +接下来,需要检索钱包的公钥。为了完成这一任务,不使用`tonapi.io`或类似服务,因为最终结果不能可靠地被信任。取而代之,这是通过解析`walletStateInit`完成的。 + +确保`address`和`walletStateInit`匹配也至关重要,否则,如果他们在`stateInit`字段中提供自己的钱包,并在`address`字段中提供另一个钱包,则可以用他们的钱包密钥签名有效载荷。 + +`StateInit`由两种引用类型组成:一个用于代码,一个用于数据。在这个上下文中,目的是检索公钥,因此加载第二个引用(数据引用)。然后跳过8字节(在所有现代钱包合约中,4字节用于`seqno`字段和4字节用于`subwallet_id`),然后加载下一个32字节(256位)——公钥。 + +```python +import nacl.signing +import tonsdk + +import hashlib +import base64 + +received_state_init = 'te6cckECFgEAAwQAAgE0ARUBFP8A9KQT9LzyyAsCAgEgAxACAUgEBwLm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQUGAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAgPAgEgCQ4CAVgKCwA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIAwNABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xESExQAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjFyM60x2mt5eboNyOTE+5RGOe9Ee2rK1Qcb+0ZuiP9vb7QJRlz/c=' +received_address = '0:b2a1ecf5545e076cd36ae516ea7ebdf32aea008caa2b84af9866becb208895ad' + +state_init = tonsdk.boc.Cell.one_from_boc(base64.b64decode(received_state_init)) + +address_hash_part = base64.b16encode(state_init.bytes_hash()).decode('ascii').lower() +assert received_address.endswith(address_hash_part) + +public_key = state_init.refs[1].bits.array[8:][:32] + +print(public_key) +# bytearray(b'#:\xd3\x1d\xa6\xb7\x97\x9b\xa0\xdc\x8eLO\xb9Dc\x9e\xf4G\xb6\xac\xadPq\xbf\xb4f\xe8\x8f\xf6\xf6\xfb') + +verify_key = nacl.signing.VerifyKey(bytes(public_key)) +``` + +在实现了上述代码后,需要查阅正确的文档来检查使用钱包密钥验证和签名了哪些参数: + +> ``` +> message = utf8_encode("ton-proof-item-v2/") ++ +> Address ++ +> AppDomain ++ +> Timestamp ++ +> Payload +> +> signature = Ed25519Sign( +> privkey, +> sha256(0xffff ++ utf8_encode("ton-connect") ++ sha256(message)) +> ) +> ``` + +> 其中: +> +> - `Address` 表示钱包地址编码为序列: +> - `workchain`:32位有符号整数大端序; +> - `hash`:256位无符号整数大端序; +> - `AppDomain` 是 Length ++ EncodedDomainName +> - `Length` 使用32位值表示utf-8编码的app域名长度(以字节为单位) +> - `EncodedDomainName` 是 `Length` 字节的utf-8编码的域名 +> - `Timestamp` 表示签名操作的64位 unix epoch 时间 +> - `Payload` 表示可变长度的二进制字符串 +> - `utf8_encode` 生成一个没有长度前缀的纯字节字符串。 + +接下来用Python重实现这一部分。上述部分整数的端序没有详细说明,因此需要考虑几个示例。请参阅以下Tonkeeper实现,详细了解相关示例:: [ConnectReplyBuilder.ts](https://github.com/tonkeeper/wallet/blob/77992c08c663dceb63ca6a8e918a2150c75cca3a/src/tonconnect/ConnectReplyBuilder.ts#L42)。 + +```python +received_timestamp = 1674392728 +signature = 'trCkHit07NZUayjGLxJa6FoPnaGHkqPy2JyNjlUbxzcc3aGvsExCmHXi6XJGuoCu6M2RMXiLzIftEm6PAoy1BQ==' + +message = (b'ton-proof-item-v2/' + + 0 .to_bytes(4, 'big') + si.bytes_hash() + + 28 .to_bytes(4, 'little') + b'ratingers.pythonanywhere.com' + + received_timestamp.to_bytes(8, 'little') + + b'doc-example-') +# b'ton-proof-item-v2/\x00\x00\x00\x00\xb2\xa1\xec\xf5T^\x07l\xd3j\xe5\x16\xea~\xbd\xf3*\xea\x00\x8c\xaa+\x84\xaf\x98f\xbe\xcb \x88\x95\xad\x1c\x00\x00\x00ratingers.pythonanywhere.com\x984\xcdc\x00\x00\x00\x00doc-example-' + +signed = b'\xFF\xFF' + b'ton-connect' + hashlib.sha256(message).digest() +# b'\xff\xffton-connectK\x90\r\xae\xf6\xb0 \xaa\xa9\xbd\xd1\xaa\x96\x8b\x1fp\xa9e\xff\xdf\x81\x02\x98\xb0)E\t\xf6\xc0\xdc\xfdx' + +verify_key.verify(hashlib.sha256(signed).digest(), base64.b64decode(signature)) +# b'\x0eT\xd6\xb5\xd5\xe8HvH\x0b\x10\xdc\x8d\xfc\xd3#n\x93\xa8\xe9\xb9\x00\xaaH%\xb5O\xac:\xbd\xcaM' +``` + +在实现上述参数后,如果有攻击者试图冒充用户并且没有提供有效的签名,将会显示以下错误:`nacl.exceptions.BadSignatureError: Signature was forged or corrupt`。 + +## 下一步 + +当编写dApp时,还应该考虑: + +- 在成功完成连接(无论是恢复的连接还是新连接)后,应显示`Disconnect`按钮,而不是多个`Connect`按钮 +- 用户断开连接后,需要重新创建`Disconnect`按钮 +- 应检查钱包代码,因为 + - 最新版本的钱包可能会将公钥放在不同的位置,并导致问题 + - 当前用户可能使用非钱包类型的合约登录。幸运的是,这将在预期的位置包含公钥 + +祝你编写dApps时好运,并且能够享受乐趣! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/preparing-messages.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/preparing-messages.mdx new file mode 100644 index 0000000000..0c3b03a126 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/preparing-messages.mdx @@ -0,0 +1,1177 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# 准备消息 + +在使用TON Connect时,您应该为在各种交易中使用的Payload构造消息体。在此页面上,您可以找到与TON Connect SDK一起使用的payload的最相关示例。 + +:::info +期望您学习了创建TON Connect连接的基础知识。了解更多请参阅[集成手册](/develop/dapps/ton-connect/integration)。 +::: + + +## TON Connect JS SDK 示例 + +### 交易模板 + +无论开发者正在解决的任务级别如何,通常都需要使用来自@tonconnect/sdk或@tonconnect/ui的连接器实体。 +基于@tonconnect/sdk和@tonconnect/ui创建的示例: + + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; +const [tonConnectUI] = useTonConnectUI(); + +const transaction = { + //transaction body +}) + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + +
+ + +```js +import TonConnectUI from '@tonconnect/ui'; + +const tonConnectUI = new TonConnectUI({ //连接应用 + manifestUrl: 'https:///tonconnect-manifest.json', + buttonRootId: '' +}); + +const transaction = { + //transaction body +} + +const result = await tonConnectUI.sendTransaction(transaction) + +``` + + + + +```js +import TonConnect from '@tonconnect/sdk'; +const connector = new TonConnect(); + +await connector.sendTransaction({ + //transaction body +}) + +``` + + + +
+ +### 常规 TON 转账 + +TON Connect SDK提供了发送消息的封装器,使准备两个钱包之间的Toncoin的常规转账作为默认交易无需载荷变得容易。 + +使用TON Connect JS SDK执行常规TON转账如下所示: + + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; +const [tonConnectUI] = useTonConnectUI(); + +const transaction = { + messages: [ + { + address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // 目的地址 + amount: "20000000" //以nanotons计的Toncoin + } + ] + +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + +
+ + + +```js +import TonConnectUI from '@tonconnect/ui'; + +const tonConnectUI = new TonConnectUI({ //连接应用 + manifestUrl: 'https:///tonconnect-manifest.json', + buttonRootId: '' +}); + +const transaction = { + messages: [ + { + address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // 目的地址 + amount: "20000000" //以nanotons计的Toncoin + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + + +```js +import TonConnect from '@tonconnect/sdk'; +const connector = new TonConnect(); + +await connector.sendTransaction({ + messages: [ + { + address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // 目的地址 + amount: "20000000" //以nanotons计的Toncoin + } + ] +}) + +``` + +
+ +:::tip +了解更多信息请参阅[TON智能合约地址](/learn/overviews/addresses)。 +::: + +对于特定的自定义交易,必须定义特定的载荷。 + + + + +### 添加评论的转账 + +最简单的例子是添加一个包含评论的载荷。更多详情请查看[此页面](/develop/smart-contracts/guidelines/internal-messages#simple-message-with-comment)。 +在交易之前,需要通过[@ton/ton](https://github.com/ton-org/ton) JavaScript库准备一个`body` [cell](/develop/data-formats/cell-boc)。 + +```js +import { beginCell } from '@ton/ton' + +const body = beginCell() + .storeUint(0, 32) // 写入32个零位以表示后面将跟随文本评论 + .storeStringTail("Hello, TON!") // 写下我们的文本评论 + .endCell(); +``` + +通过以下方式创建交易体: + + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: destination, + amount: toNano("0.05"), + payload: body.toBoc().toString("base64") // body中带有评论的载荷 + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` +
+ + + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: destination, + amount: toNano("0.05"), + payload: body.toBoc().toString("base64") // body中带有评论的载荷 + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + +```js +import TonConnect from '@tonconnect/sdk'; +const connector = new TonConnect(); + +await connector.sendTransaction({ + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: destination, + amount: toNano("0.05"), + payload: body.toBoc().toString("base64") // body中带有评论的载荷 + } + ] +}) +``` + +
+ + +### Jetton 转账 + +根据以下方式进行的 Jetton 转账操作的 `body`([TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#1-transfer)) 通常应如下所示: + +```js + import {beginCell, toNano} from '@ton/ton' + // transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress + // response_destination:MsgAddress custom_payload:(Maybe ^Cell) + // forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) + // = InternalMsgBody; + + const body = beginCell() + .storeUint(0xf8a7ea5, 32) // jetton 转账操作码 + .storeUint(0, 64) // query_id:uint64 + .storeCoins(1000000) // amount:(VarUInteger 16) - 转账的 Jetton 金额(小数位 = 6 - jUSDT, 9 - 默认) + .storeAddress(Wallet_DST) // destination:MsgAddress + .storeAddress(Wallet_SRC) // response_destination:MsgAddress + .storeUint(0, 1) // custom_payload:(Maybe ^Cell) + .storeCoins(toNano(0.05)) // forward_ton_amount:(VarUInteger 16) + .storeUInt(0,1) // forward_payload:(Either Cell ^Cell) + .endCell(); +``` + +然后,将带有此 body 的交易发送到发送者的 jettonWalletContract 执行: + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 发送方 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 转账 body 的载荷 + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + + +
+ + + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 发送方 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 转账 body 的载荷 + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + +```js +import TonConnect from '@tonconnect/sdk'; +const connector = new TonConnect(); +//... +await connector.sendTransaction({ + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 发送方 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 转账 body 的载荷 + } + ] +}) +``` + + +
+ +- `validUntil` - 消息有效的 UNIX 时间 +- `jettonWalletAddress` - 地址,基于 JettonMaser 和 Wallet 合约定义的 JettonWallet 地址 +- `balance` - 整数,用于gas费用的 Toncoin 金额,以 nanotons 计。 +- `body` - 用于 jettonContract 的载荷 + +
+ Jetton 钱包状态初始化和地址准备示例 + +```js +import { Address, TonClient, beginCell, StateInit, storeStateInit } from '@ton/ton' + +async function main() { + const client = new TonClient({ + endpoint: 'https://toncenter.com/api/v2/jsonRPC', + apiKey: '放置你的 api key' + }) + + const jettonWalletAddress = Address.parse('Sender_Jetton_Wallet'); + let jettonWalletDataResult = await client.runMethod(jettonWalletAddress, 'get_wallet_data'); + jettonWalletDataResult.stack.readNumber(); + const ownerAddress = jettonWalletDataResult.stack.readAddress(); + const jettonMasterAddress = jettonWalletDataResult.stack.readAddress(); + const jettonCode = jettonWalletDataResult.stack.readCell(); + const jettonData = beginCell() + .storeCoins(0) + .storeAddress(ownerAddress) + .storeAddress(jettonMasterAddress) + .storeRef(jettonCode) + .endCell(); + + const stateInit: StateInit = { + code: jettonCode, + data: jettonData + } + + const stateInitCell = beginCell() + .store(storeStateInit(stateInit)) + .endCell(); + + console.log(new Address(0, stateInitCell.hash())); +} +``` + +
+ +### Jetton 销毁 + +Jetton 销毁([TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#2-burn)) 的`body`通常应该按照以下方式完成: + +```js + import {beginCell} from '@ton/ton' +// burn#595f07bc query_id:uint64 amount:(VarUInteger 16) +// response_destination:MsgAddress custom_payload:(Maybe ^Cell) +// = InternalMsgBody; + + const body = beginCell() + .storeUint(0x595f07bc, 32) // jetton 销毁操作码 + .storeUint(0, 64) // query_id:uint64 + .storeCoins(1000000) // amount:(VarUInteger 16) - 以小数形式的 Jetton 金额 + .storeAddress(Wallet_SRC) // response_destination:MsgAddress - 持有者的钱包 + .storeUint(0, 1) // custom_payload:(Maybe ^Cell) - 通常没有载荷 + .endCell(); +``` + +消息放入以下请求中: + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 持有者的 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 销毁 body 的载荷 + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + + +
+ + + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 持有者的 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 销毁 body 的载荷 + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + +```js +await connector.sendTransaction({ + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 持有者的 Jetton 钱包 + amount: toNano("0.05"), // 用于手续费,超额部分将被退回 + payload: body.toBoc().toString("base64") // 带有 Jetton 销毁 body 的载荷 + } + ] +}) +``` + + +
+ +- `jettonWalletAddress` - Jetton 钱包合约地址,基于 JettonMaser 和 Wallet 合约定义 +- `amount` - 整数,用于gas费用的 Toncoin 金额,以 nanotons 计。 +- `body` - 带有 `burn#595f07bc` 操作码的 Jetton 钱包载荷 + +### NFT 转移 + +`body` 消息通常应按照以下方式进行: + +```js +import { beginCell, toNano} from '@ton/ton' + +// 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; + + const body = beginCell() + .storeUint(0x5fcc3d14, 32) // NFT 转移操作码 0x5fcc3d14 + .storeUint(0, 64) // query_id:uint64 + .storeAddress(NEW_OWNER_WALLET) // new_owner:MsgAddress + .storeAddress(Wallet_DST) // response_destination:MsgAddress + .storeUint(0, 1) // custom_payload:(Maybe ^Cell) + .storeCoins(toNano('0.000000001')) // forward_amount:(VarUInteger 16) + .storeUint(0,1) // forward_payload:(Either Cell ^Cell) + .endCell(); +``` + +`WALLET_DST` - 地址 - 初始 NFT 持有者地址,用于接收超额资金 +将 `NFTitem` 转移给新所有者 `NEW_OWNER_WALLET`。 + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: jettonWalletContract, // 将要转移的 NFT 物品地址 + amount: toNano("0.05"), // 用于佣金费,超额部分将被返回 + payload: body.toBoc().toString("base64") // 带有 NFT 转移 body 的 payload + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + + +
+ + + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: NFTitem, // 将要转移的 NFT 物品地址 + amount: toNano("1.08"), // 用于佣金费,超额部分将被返回 + payload: transferNftBody.toBoc().toString("base64") // 带有 transferNftBody 消息的 payload + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + +```js +await connector.sendTransaction({ + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: NFTitem, // 将要转移的 NFT 物品地址 + amount: toNano("1.08"), // 用于佣金费,超额部分将被返回 + payload: transferNftBody.toBoc().toString("base64") // 带有 transferNftBody 消息的 payload + } + ] +}) +``` + +
+ +- `NFTitem` - 地址 - 我们希望转移到新所有者 `NEW_OWNER_Wallet` 的NFT项目智能合约的地址。 +- `balance` - 整数,用于gas支付的 Toncoin 数量(单位是nanotons)。 +- `body` - 用于 NFT 合约的载荷 + +### NFT 销售(GetGems) + +以下是根据合约[nft-fixprice-sale-v3r2](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v3r2.fc)准备消息和交易以在GetGems市场上进行销售的示例。 + +要将 NFT 放置在 GetGems 销售合约上,我们应该准备特殊消息体 `transferNftBody`,它将 NFT 转移到特殊的 NFT 销售合约。 +```js + const transferNftBody = beginCell() + .storeUint(0x5fcc3d14, 32) // NFT 转移操作码 + .storeUint(0, 64) // query_id + .storeAddress(destinationAddress) // new_owner - GetGems 销售合约部署者,此操作不应更改 + .storeAddress(walletAddress) // 超额资金的响应目的地 + .storeBit(0) // 我们没有 custom_payload + .storeCoins(toNano("1")) // forward_amount + .storeBit(0) // 我们在此cell中存储 forward_payload + .storeUint(0x0fe0ede, 31) // 非 32,因为之前存储的 0 位将作为销售操作码读取 + .storeRef(stateInitCell) + .storeRef(saleBody) + .endCell(); +``` + +因为消息需要很多步骤,整个算法庞大,可以在此处找到: +
+ 显示创建 NFT 销售消息体的整个算法 + + +```js +import { Address, beginCell, StateInit, storeStateInit, toNano, Cell } from '@ton/ton' + +async function main() { + const fixPriceV3R2Code = Cell.fromBase64('te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAIDAgFIBAUAfvIw7UTQ0wDTH/pA+kD6QPoA1NMAMMABjh34AHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBfB4IA//7y8AICzQYHAFegOFnaiaGmAaY/9IH0gfSB9AGppgBgYaH0gfQB9IH0AGEEIIySsKAVgAKrAQH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAgB92YIQO5rKAFJgoFIwvvLhwiTQ+kD6APpA+gAwU5KhIaFQh6EWoFKQcIAQyMsFUAPPFgH6AstqyXH7ACXCACXXScICsI4XUEVwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWnCAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FIKAejy0ZSzjkIxMzk5U1LHBZJfCeBRUccF8uH0ghAFE42RFrry4fUD+kAwRlAQNFlwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgMDcowAPjAijAAJw2NxA4R2UUQzBw8AXgCMACmFVEECQQI/AF4F8KhA/y8AkA1Dg5ghA7msoAGL7y4clTRscFUVLHBRWx8uHKcCCCEF/MPRQhgBDIywUozxYh+gLLassfFcs/J88WJ88WFMoAI/oCE8oAyYMG+wBxUGZFFQRwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VQAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVNZeZYk='); + + const marketplaceAddress = Address.parse('EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS'); // GetGems 地址 + const marketplaceFeeAddress = Address.parse('EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS'); // GetGems 收费地址 + const destinationAddress = Address.parse("EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR"); // GetGems 销售合约部署者 + + const walletAddress = Address.parse('EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162'); + const royaltyAddress = Address.parse('EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162'); + const nftAddress = Address.parse('EQCUWoe7hLlklVxH8gduCf45vPNocsjRP4wbX42UJ0Ja0S2f'); + const price = toNano('5'); // 5 TON + + const feesData = beginCell() + .storeAddress(marketplaceFeeAddress) + // 5% - GetGems 收费 + .storeCoins(price / BigInt(100) * BigInt(5)) + .storeAddress(royaltyAddress) + // 5% - 版权费,可更改 + .storeCoins(price / BigInt(100) * BigInt(5)) + .endCell(); + + const saleData = beginCell() + .storeBit(0) // is_complete + .storeUint(Math.round(Date.now() / 1000), 32) // created_at + .storeAddress(marketplaceAddress) // marketplace_address + .storeAddress(nftAddress) // nft_address + .storeAddress(walletAddress) // previous_owner_address + .storeCoins(price) // 以nanotons计的全价 + .storeRef(feesData) // fees_cell + .storeBit(0) // can_be_deployed_externally + .endCell(); + + const stateInit: StateInit = { + code: fixPriceV3R2Code, + data: saleData + }; + const stateInitCell = beginCell() + .store(storeStateInit(stateInit)) + .endCell(); + + // 仅示例,非必需 + const saleContractAddress = new Address(0, stateInitCell.hash()); + + const saleBody = beginCell() + .storeUint(1, 32) // 只是接收硬币 + .storeUint(0, 64) + .endCell(); + + const transferNftBody = beginCell() + .storeUint(0x5fcc3d14, 32) // NFT 转移操作码 + .storeUint(0, 64) // query_id + .storeAddress(destinationAddress) // new_owner + .storeAddress(walletAddress) // 超额资金的响应目的地 + .storeBit(0) // 我们没有 custom_payload + .storeCoins(toNano("1")) // forward_amount + .storeBit(0) // 我们在此cell中存储 forward_payload + // 非 32,因为我们存储了 0 位 | 部署者的销售操作码 + .storeUint(0x0fe0ede, 31) + .storeRef(stateInitCell) + .storeRef(saleBody) + .endCell(); +``` + +
+ +准备好的 `transferNftBody` 应发送到 NFT 物品合约,至少需要 `1.08` TON,以成功处理。多余的部分将退还给发件人钱包。 + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: NFTitem, // NFT 物品合约地址,应该放置于市场上 + amount: toNano("1.08"), // 需要的gas费金额,多余的部分将返回 + payload: transferNftBody.toBoc().toString("base64") // 带有 transferNftBody 消息的 payload + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + +
+ + + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: NFTitem, // NFT 物品合约地址,应该放置于市场上 + amount: toNano("1.08"), // 需要的gas费金额,多余的部分将返回 + payload: transferNftBody.toBoc().toString("base64") // 带有 transferNftBody 消息的 payload + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + + +```js +await connector.sendTransaction({ + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: NFTitem, // NFT 物品合约地址,应该放置于市场上 + amount: toNano("1.08"), // 需要的gas费金额,多余的部分将返回 + payload: transferNftBody.toBoc().toString("base64") // 带有 transferNftBody 消息的 payload + } + ] +}) +``` + +
+ +### NFT 购买 (GetGems) + +购买 [nft-fixprice-sale-v3r2](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v3r2.fc) 销售合约的 NFT 的过程可以通过常规转账进行,无需负荷,唯一重要的是准确的 TON 数量,按如下计算: +`buyAmount = Nftprice TON + 1.0 TON`。 + + + + +```js +import { useTonConnectUI } from '@tonconnect/ui-react'; + +const myTransaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: nftSaleContract, // 当前希望购买的 NFT 销售合约地址 + amount: toNano(buyAmount), // NFT 价格 + 1 TON, 多余的会被返回 + } + ] +} + +export const Settings = () => { + const [tonConnectUI, setOptions] = useTonConnectUI(); + + return ( +
+ +
+ ); +}; +``` + +
+ + +```js +import TonConnectUI from '@tonconnect/ui' + +const transaction = { + validUntil: Math.floor(Date.now() / 1000) + 360, + messages: [ + { + address: nftSaleContract, // 当前希望购买的 NFT 销售合约地址 + amount: toNano(buyAmount), // NFT 价格 + 1 TON, 多余的会被返回 + } + ] +} + +const result = await tonConnectUI.sendTransaction(transaction) +``` + + + + +```js +await connector.sendTransaction({ +validUntil: Math.floor(Date.now() / 1000) + 360, +messages: [ + { + address: nftSaleContract, // 当前希望购买的 NFT 销售合约地址 + amount: toNano(buyAmount), // NFT 价格 + 1 TON, 多余的会被返回 + } +] +}) +``` + + +
+ +## TON Connect Python SDK + +Python 示例使用 [PyTonConnect](https://github.com/XaBbl4/pytonconnect) 和 [pytoniq](https://github.com/yungwine/pytoniq)。 + +```python + from pytoniq_core import Address + from pytonconnect import TonConnect +``` + +:::tip +阅读示例 [源码](https://github.com/yungwine/ton-connect-examples/blob/master/main.py)。 +::: + +### 常规 TON 转账 + + +```python +connector = TonConnect( + manifest_url='https://raw.githubusercontent.com/XaBbl4/pytonconnect/main/pytonconnect-manifest.json') +is_connected = await connector.restore_connection() + +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + 'address' :'0:0000000000000000000000000000000000000000000000000000000000000000', # 目的地址 + 'amount' : 1000000000, # 1 TON,数额应以nanocoins计 + ) + ] +} +``` + + +### 附带评论的转账 + +首先,通过以下函数实现带有评论的消息: + +```python + def get_comment_message(destination_address: str, amount: int, comment: str) -> dict: + + data = { + 'address': destination_address, + 'amount': str(amount), + 'payload': urlsafe_b64encode( + begin_cell() + .store_uint(0, 32) # 评论消息的操作码 + .store_string(comment) # 储存评论 + .end_cell() # 结束 cell + .to_boc() # 转换成 boc + ) + .decode() # 编码成 url 安全的 base64 + } + + return data +``` + +带有评论的转账的最终交易体: + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_comment_message( + destination_address='0:0000000000000000000000000000000000000000000000000000000000000000', + amount=int(0.01 * 10**9), # 数额应以纳币指定 + comment='hello world!' + ) + ] +} +``` +:::tip +了解更多关于 [TON 智能合约地址](/learn/overviews/addresses)。 +::: + +### Jetton 转账 + +构建 jetton 转账交易的函数示例: + +```python +from pytoniq_core import begin_cell +from base64 import urlsafe_b64encode + +def get_jetton_transfer_message(jetton_wallet_address: str, recipient_address: str, transfer_fee: int, jettons_amount: int, response_address: str = None) -> dict: + data = { + 'address': jetton_wallet_address, + 'amount': str(transfer_fee), + 'payload': urlsafe_b64encode( + begin_cell() + .store_uint(0xf8a7ea5, 32) # jetton 转账消息的操作码 + .store_uint(0, 64) # query_id + .store_coins(jettons_amount) + .store_address(recipient_address) # 目的地址 + .store_address(response_address or recipient_address) # 超额资金发送到的地址 + .store_uint(0, 1) # 自定义负载 + .store_coins(1) # 转发金额 + .store_uint(0, 1) # 转发负载 + .end_cell() # 结束 cell + .to_boc() # 转换成 boc + ) + .decode() # 编码成 url 安全的 base64 + } + + return data +``` + +最终的交易体: + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_jetton_transfer_message( + jetton_wallet_address='EQCXsVvdxTVmSIvYv4tTQoQ-0Yq9mERGTKfbsIhedbN5vTVV', + recipient_address='0:0000000000000000000000000000000000000000000000000000000000000000', + transfer_fee=int(0.07 * 10**9), + jettons_amount=int(0.01 * 10**9), # 将jetton十进制数替换为9。例如对于 jUSDT 应该是 (amount * 10**6), + response_address=wallet_address + ), + ] +} + +``` + + +### Jetton 销毁 + +构建 jetton 销毁交易的函数示例: + +```python +from pytoniq_core import begin_cell +from base64 import urlsafe_b64encode + +def get_jetton_burn_message(jetton_wallet_address: str, transfer_fee: int, jettons_amount: int, response_address: str = None) -> dict: + data = { + 'address': jetton_wallet_address, + 'amount': str(transfer_fee), + 'payload': urlsafe_b64encode( + begin_cell() + .store_uint(0x595f07bc, 32) # jetton 转账消息的操作码 + .store_uint(0, 64) # query_id + .store_coins(jettons_amount) + .store_address(response_address) # # 超额资金发送到的地址 + .end_cell() # 结束 cell + .to_boc() # 转换成 boc + ) + .decode() # 编码成 url 安全的 base64 + } + return data +``` + +最终的交易体: + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_jetton_burn_message( + jetton_wallet_address='EQCXsVvdxTVmSIvYv4tTQoQ-0Yq9mERGTKfbsIhedbN5vTVV', + transfer_fee=int(0.07 * 10 ** 9), + jettons_amount=int(0.01 * 10 ** 9), # 将jetton十进制数替换为9。例如对于 jUSDT 应该是 (amount * 10**6), + response_address=wallet_address + ), + ] +} +``` + + +### NFT 转账 + +NFT 转账交易函数的示例: + +```python +from pytoniq_core import begin_cell +from base64 import urlsafe_b64encode + +def get_nft_transfer_message(nft_address: str, recipient_address: str, transfer_fee: int, response_address: str = None) -> dict: + data = { + 'address': nft_address, + 'amount': str(transfer_fee), + 'payload': urlsafe_b64encode( + begin_cell() + .store_uint(0x5fcc3d14, 32) # nft 转账消息的操作码 + .store_uint(0, 64) # query_id + .store_address(recipient_address) # 新主人 + .store_address(response_address or recipient_address) # 超额资金发送到的地址 + .store_uint(0, 1) # custom payload + .store_coins(1) # forward amount + .store_uint(0, 1) # forward payload + .end_cell() # 结束 cell + .to_boc() # 转换成 boc + ) + .decode() # 编码成 url 安全的 base64 + } + return data + +``` + +最终的交易体: + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_nft_transfer_message( + nft_address='EQDrA-3zsJXTfGo_Vdzg8d07Da4vSdHZllc6W9qvoNoMstF-', + recipient_address='0:0000000000000000000000000000000000000000000000000000000000000000', + transfer_fee=int(0.07 * 10**9), + response_address=wallet_address + ), + ] +} +``` + +### NFT 销售 (GetGems) + +以下是在 GetGems 市场上进行销售时准备消息和交易的示例,根据合约 [nft-fixprice-sale-v3r2](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v3r2.fc)。 + +为了将 NFT 放置在 GetGems 销售合约上,我们应该准备特殊的消息体 `transferNftBody`,该消息体将 NFT 转移给特殊的 NFT 销售合约。 + +
+创建 NFT NFT Sale Body的示例 + +```python +import time +from base64 import urlsafe_b64encode + +from pytoniq_core.boc import Cell, begin_cell, Address +from pytoniq_core.tlb import StateInit + + +def get_sale_body(wallet_address: str, royalty_address: str, nft_address: str, price: int, amount: int): + + # 合约代码 + nft_sale_code_cell = Cell.one_from_boc('te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAIDAgFIBAUAfvIw7UTQ0wDTH/pA+kD6QPoA1NMAMMABjh34AHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBfB4IA//7y8AICzQYHAFegOFnaiaGmAaY/9IH0gfSB9AGppgBgYaH0gfQB9IH0AGEEIIySsKAVgAKrAQH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAgB92YIQO5rKAFJgoFIwvvLhwiTQ+kD6APpA+gAwU5KhIaFQh6EWoFKQcIAQyMsFUAPPFgH6AstqyXH7ACXCACXXScICsI4XUEVwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWnCAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FIKAejy0ZSzjkIxMzk5U1LHBZJfCeBRUccF8uH0ghAFE42RFrry4fUD+kAwRlAQNFlwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgMDcowAPjAijAAJw2NxA4R2UUQzBw8AXgCMACmFVEECQQI/AF4F8KhA/y8AkA1Dg5ghA7msoAGL7y4clTRscFUVLHBRWx8uHKcCCCEF/MPRQhgBDIywUozxYh+gLLassfFcs/J88WJ88WFMoAI/oCE8oAyYMG+wBxUGZFFQRwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VQAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVNZeZYk=') + + # 费用cell + + marketplace_address = Address('EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS') + marketplace_fee_address = Address('EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS') + destination_address = Address('EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR') + + wallet_address = Address(wallet_address) + royalty_address = Address(royalty_address) + nft_address = Address(nft_address) + + marketplace_fee = int(price * 5 / 100) # 5% + royalty_fee = int(price * 5 / 100) # 5% + + fees_data_cell = (begin_cell() + .store_address(marketplace_fee_address) + .store_coins(marketplace_fee) + .store_address(royalty_address) + .store_coins(royalty_fee) + .end_cell()) + + + sale_data_cell = (begin_cell() + .store_bit_int(0) + .store_uint(int(time.time()), 32) + .store_address(marketplace_address) + .store_address(nft_address) + .store_address(wallet_address) + .store_coins(price) + .store_ref(fees_data_cell) + .store_bit_int(0) + .end_cell()) + + state_init_cell = StateInit(code=nft_sale_code_cell, data=sale_data_cell).serialize() + + sale_body = (begin_cell() + .store_uint(1, 32) + .store_uint(0, 64) + .end_cell()) + + transfer_nft_body = (begin_cell() + .store_uint(0x5fcc3d14, 32) + .store_uint(0, 64) + .store_address(destination_address) + .store_address(wallet_address) + .store_bit_int(0) + .store_coins(int(1 * 10**9)) + .store_bit_int(0) + .store_uint(0x0fe0ede, 31) + .store_ref(state_init_cell) + .store_ref(sale_body) + .end_cell()) + + data = { + 'address': nft_address.to_str(), + 'amount': str(amount), + 'payload': urlsafe_b64encode(transfer_nft_body.to_boc()).decode() + } + + return data +``` + +
+ +最终交易体: + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + get_nft_transfer_message( + nft_address='EQDrA-3zsJXTfGo_Vdzg8d07Da4vSdHZllc6W9qvoNoMstF-', + recipient_address='0:0000000000000000000000000000000000000000000000000000000000000000', + transfer_fee=int(0.07 * 10**9), + response_address=wallet_address + ), + ] +} +``` + +### NFT 购买 (GetGems) + +使用 [nft-fixprice-sale-v3r2](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v3r2.fc) 销售合约购买 NFT 的过程可以通过不含有效负载的常规转账进行,唯一重要的是准确的 TON 数额,计算方式如下: +`buyAmount = Nftprice TON + 1.0 TON`。 + +```python +transaction = { + 'valid_until': int(time.time() + 3600), + 'messages': [ + { + 'address': nft_address, + 'amount': buyAmount, + ] +} +``` + +## 作者 +- JavaScript 示例提供者 [@aSpite](https://t.me/aspite) +- Python 示例提供者 [@yunwine](https://t.me/yungwine) + +## 参阅 + +* [TON Connect SDKs](/develop/dapps/ton-connect/developers) +* [TON Connect - 发送消息](/develop/dapps/ton-connect/transactions) +* [智能合约开发 - 发送消息(低层级)](/develop/smart-contracts/messages) \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/sending-messages.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/sending-messages.md new file mode 100644 index 0000000000..49d13ec821 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/sending-messages.md @@ -0,0 +1,185 @@ +# 发送消息 + +TON Connect 2.0 不仅仅提供了在 dApp 中认证用户的强大选项:它还可以通过已连接的钱包发送外部消息! + +您将了解到: +- 如何从 DApp 发送消息到区块链 +- 如何在一次交易中发送多条消息 +- 如何使用 TON Connect 部署合约 + +## 演示页面 + +我们将使用 JavaScript 的低级 [TON Connect SDK](https://github.com/ton-connect/sdk/tree/main/packages/sdk) 。我们将在钱包已连接的页面上的浏览器控制台上做实验。以下是示例页面: + +```html + + + + + + + + + + + +``` + +随意将其复制粘贴到您的浏览器控制台并运行。 + +## 发送多条消息 + +### 1) 了解任务 + +我们将在一次交易中发送两条独立的消息:一条发送到您自己的地址,携带 0.2 TON,另一条发送到其他钱包地址,携带 0.1 TON。 + +顺便说一下,一次交易中发送的消息有限制: +- 标准 ([v3](/participate/wallets/contracts#wallet-v3)/[v4](/participate/wallets/contracts#wallet-v4)) 钱包:4 条传出消息; +- 高负载钱包:255 条传出消息(接近区块链限制)。 + +### 2) 发送消息 + +运行以下代码: + +```js +console.log(await connector.sendTransaction({ + validUntil: Math.floor(new Date() / 1000) + 360, + messages: [ + { + address: connector.wallet.account.address, + amount: "200000000" + }, + { + address: "0:b2a1ecf5545e076cd36ae516ea7ebdf32aea008caa2b84af9866becb208895ad", + amount: "100000000" + } + ] +})); +``` + +您会注意到这个命令没有在控制台打印任何东西,像返回无内容的函数一样,`null` 或 `undefined`。这意味着 `connector.sendTransaction` 不会立即退出。 + +打开您的钱包应用,您会看到原因。有一个请求,显示您要发送的内容以及coin将会去向哪里。请接受它。 + + +### 3) 获取结果 + +函数将退出,并且区块链的输出将被打印: + +```json +{ + boc: "te6cckEBAwEA4QAC44gBZUPZ6qi8Dtmm1cot1P175lXUARlUVwlfMM19lkERK1oCUB3RqDxAFnPpeo191X/jiimn9Bwnq3zwcU/MMjHRNN5sC5tyymBV3SJ1rjyyscAjrDDFAIV/iE+WBySEPP9wCU1NGLsfcvVgAAACSAAYHAECAGhCAFlQ9nqqLwO2abVyi3U/XvmVdQBGVRXCV8wzX2WQRErWoAmJaAAAAAAAAAAAAAAAAAAAAGZCAFlQ9nqqLwO2abVyi3U/XvmVdQBGVRXCV8wzX2WQRErWnMS0AAAAAAAAAAAAAAAAAAADkk4U" +} +``` + +BOC 是 [Bag of Cells](/learn/overviews/cells),这是 TON 中存储数据的方式。现在我们可以解码它。 + +在您选择的工具中解码这个 BOC,您将得到以下cell树: + +```bash +x{88016543D9EAA8BC0ED9A6D5CA2DD4FD7BE655D401195457095F30CD7D964111... + $10 ext_in_msg_info + $00 src:MsgAddressExt (null address) + "EQ..."a dest:MsgAddressInt (your wallet) + 0 import_fee:Grams + $0 (no state_init) + $0 (body starts in this cell) + ... +``` + +返回发送交易的 BOC 的目的是跟踪它。 + +## 发送复杂的交易 + +### cell的序列化 + +在我们继续之前,让我们谈谈我们打算发送的消息格式。 + +* **payload** (string base64, 可选): 以 Base64 编码的单cell BoC。 + * 我们将使用它来存储转账上的文本评论 +* **stateInit** (string base64, 可选): 以 Base64 编码的单cell BoC。 + * 我们将使用它来部署智能合约 + +构建消息后,您可以将其序列化为 BOC。 + +```js +TonWeb.utils.bytesToBase64(await payloadCell.toBoc()) +``` + +### 带评论的转账 + +您可以使用 [toncenter/tonweb](https://github.com/toncenter/tonweb) JS SDK 或您喜欢的工具将cell序列化为 BOC。 + +转账上的文本评论被编码为操作码 0 (32 个零位) + 评论的 UTF-8 字节。以下是将其转换为cell包的示例。 + +```js +let a = new TonWeb.boc.Cell(); +a.bits.writeUint(0, 32); +a.bits.writeString("TON Connect 2 教程!"); +let payload = TonWeb.utils.bytesToBase64(await a.toBoc()); + +console.log(payload); +// te6ccsEBAQEAHQAAADYAAAAAVE9OIENvbm5lY3QgMiB0dXRvcmlhbCFdy+mw +``` + +### 智能合约部署 + +我们将部署一个非常简单的 [聊天机器人 Doge](https://github.com/LaDoger/doge.fc),在[智能合约示例](/develop/smart-contracts/#smart-contract-examples)中提到。首先,我们加载它的代码并在数据中存储一些独特的内容,以便我们接收到一个尚未被其他人部署的全新实例。然后我们将代码和数据合并到 stateInit 中。 + +```js +let code = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes('te6cckEBAgEARAABFP8A9KQT9LzyyAsBAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AN4uuM8=')); +let data = new TonWeb.boc.Cell(); +data.bits.writeUint(Math.floor(new Date()), 64); + +let state_init = new TonWeb.boc.Cell(); +state_init.bits.writeUint(6, 5); +state_init.refs.push(code); +state_init.refs.push(data); + +let state_init_boc = TonWeb.utils.bytesToBase64(await state_init.toBoc()); +console.log(state_init_boc); +// te6ccsEBBAEAUwAABRJJAgE0AQMBFP8A9KQT9LzyyAsCAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AAAQAAABhltsPJ+MirEd + +let doge_address = '0:' + TonWeb.utils.bytesToHex(await state_init.hash()); +console.log(doge_address); +// 0:1c7c35ed634e8fa796e02bbbe8a2605df0e2ab59d7ccb24ca42b1d5205c735ca +``` + +现在,是时候发送我们的交易了! + +```js +console.log(await connector.sendTransaction({ + validUntil: Math.floor(new Date() / 1000) + 360, + messages: [ + { + address: "0:1c7c35ed634e8fa796e02bbbe8a2605df0e2ab59d7ccb24ca42b1d5205c735ca", + amount: "69000000", + payload: "te6ccsEBAQEAHQAAADYAAAAAVE9OIENvbm5lY3QgMiB0dXRvcmlhbCFdy+mw", + stateInit: "te6ccsEBBAEAUwAABRJJAgE0AQMBFP8A9KQT9LzyyAsCAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AAAQAAABhltsPJ+MirEd" + } + ] +})); +``` + +:::info +在 [准备消息](/develop/dapps/ton-connect/message-builders) 页面获取更多关于传输 NFT 和 Jettons 的示例。 +::: + +确认后,我们可以在 [tonscan.org](https://tonscan.org/tx/pCA8LzWlCRTBc33E2y-MYC7rhUiXkhODIobrZVVGORg=) 看到我们的交易已完成。 + +## 如果用户拒绝了交易请求会发生什么? + +处理请求拒绝相当简单,但当您正在开发某个项目时,最好提前知道会发生什么。 + +当用户在钱包应用中的弹出窗口中点击“取消”时,会抛出异常:`Error: [TON_CONNECT_SDK_ERROR] Wallet declined the request`。这个错误可以被视为最终的(不像连接取消那样) - 如果它被抛出,那么直到发送下一个请求,请求的交易绝对不会发生。 + +## 参阅 + +* [准备消息](/develop/dapps/ton-connect/message-builders) \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/verifying-signed-in-users.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/verifying-signed-in-users.mdx new file mode 100644 index 0000000000..792a506688 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/guidelines/verifying-signed-in-users.mdx @@ -0,0 +1,213 @@ +import ThemedImage from '@theme/ThemedImage'; + +# 签名与验证 + +## 使用案例 + +请注意,并非所有DApps都需要 ton_proof 验证。 +这对于后端的授权是必要的,以确保用户确实拥有声明的地址,因此可以推断出用户有权访问其在后端的数据。 + +如果您想验证用户以便从后端提供其个人信息,这将是有用的。 + +## ton_proof 如何工作? + +- 向客户端发送DAppid。通常,DAppid嵌入在二维码中。 +- 检索带有 ton_proof 实体的已签名交易 +- 在后端验证 ton_proof + + +

+ +

+ +## ton_proof 的结构 + +ton_proof 在 TON Connect 中与特殊的 `TonProof` 实体一起工作,该实体在连接器内部实现。 + +```js +type TonProofItemReply = TonProofItemReplySuccess | TonProofItemReplyError; + +type TonProofItemReplySuccess = { + name: "ton_proof"; + proof: { + timestamp: string; // 64位unix时代签名操作的时间(秒) + domain: { + lengthBytes: number; // AppDomain 长度 + value: string; // app 域名(作为url部分,无编码) + }; + signature: string; // base64编码的签名 + payload: string; // 来自请求的有效载荷 + } +} + +``` + +## 在服务器端检查 ton_proof + +1. 从用户处检索 `TonProofItemReply`。 +2. 验证接收到的域是否对应于应用程序的域。 +3. 检查 `TonProofItemReply.payload` 是否被原始服务器允许且仍然有效。 +4. 检查 `timestamp` 在当前是否真实。 +5. 根据[消息方案](/develop/dapps/ton-connect/sign#concept-explanation)组装消息。 +6. 通过API (a) 或 (b) 在后端实现的逻辑获取 `public_key` +- 6a: + - 通过 [TON API](https://docs.tonconsole.com/tonapi/api-v2#:~:text=/v2/-,tonconnect,-/stateinit) 方法 `POST /v2/tonconnect/stateinit` 从 `walletStateInit` 中检索 `{public_key, address}`。 + - 验证从 `walletStateInit` 提取的 `address` 或对应于用户声明的钱包 `address`。 +- 6b: + - 通过钱包合约的 [get 方法](https://github.com/ton-blockchain/wallet-contract/blob/main/func/wallet-v4-code.fc#L174) 获得钱包的 `public_key`。 + - 如果合约未激活,或者缺少在旧钱包版本(v1-v3)中发现的 get_method,则以这种方式获取密钥将是不可能的。相反,您将需要解析前端提供的 walletStateInit。确保 TonAddressItemReply.walletStateInit.hash() 等于 TonAddressItemReply.address.hash(),表示一个BoC哈希。 +7. 验证前端的 `signature` 实际上签署了组装的消息,并且对应于地址的 `public_key`。 + +## React 示例 + +1. 将token提供器添加到应用的根部: + +```tsx +function App() { + const [token, setToken] = useState(null); + + return ( + + { /* Your app */ } + + ) +} +``` + +2. 描述后端认证: + +
+示例 + +```tsx +import {useContext, useEffect, useRef} from "react"; +import {BackendTokenContext} from "./BackendTokenContext"; +import {useIsConnectionRestored, useTonConnectUI, useTonWallet} from "@tonconnect/ui-react"; +import {backendAuth} from "./backend-auth"; + +const localStorageKey = 'my-dapp-auth-token'; +const payloadTTLMS = 1000 * 60 * 20; + +export function useBackendAuth() { + const { setToken } = useContext(BackendTokenContext); + const isConnectionRestored = useIsConnectionRestored(); + const wallet = useTonWallet(); + const [tonConnectUI] = useTonConnectUI(); + const interval = useRef | undefined>(); + + useEffect(() => { + if (!isConnectionRestored || !setToken) { + return; + } + + clearInterval(interval.current); + + if (!wallet) { + localStorage.removeItem(localStorageKey); + setToken(null); + + const refreshPayload = async () => { + tonConnectUI.setConnectRequestParameters({ state: 'loading' }); + + const value = await backendAuth.generatePayload(); + if (!value) { + tonConnectUI.setConnectRequestParameters(null); + } else { + tonConnectUI.setConnectRequestParameters({state: 'ready', value}); + } + } + + refreshPayload(); + setInterval(refreshPayload, payloadTTLMS); + return; + } + + const token = localStorage.getItem(localStorageKey); + if (token) { + setToken(token); + return; + } + + if (wallet.connectItems?.tonProof && !('error' in wallet.connectItems.tonProof)) { + backendAuth.checkProof(wallet.connectItems.tonProof.proof, wallet.account).then(result => { + if (result) { + setToken(result); + localStorage.setItem(localStorageKey, result); + } else { + alert('Please try another wallet'); + tonConnectUI.disconnect(); + } + }) + } else { + alert('Please try another wallet'); + tonConnectUI.disconnect(); + } + + }, [wallet, isConnectionRestored, setToken]) +} +``` +
+ +## 概念解释 + +如果请求了 `TonProofItem`,钱包证明了账户所选密钥的所有权。签名消息绑定到: + +- 独特的前缀,以将消息与链上消息分开。(`ton-connect`) +- 钱包地址 +- App域 +- 签名时间戳 +- 应用的自定义有效载荷(服务器可能在其中放置其随机数、cookie id、过期时间) + +``` +message = utf8_encode("ton-proof-item-v2/") ++ + Address ++ + AppDomain ++ + Timestamp ++ + Payload + +signature = Ed25519Sign(privkey, sha256(0xffff ++ utf8_encode("ton-connect") ++ sha256(message))) +``` + +其中: + +* `Address` 是钱包地址编码为一系列: + * `workchain`:32位有符号整数大端; + * `hash`:256位无符号整数大端; +* `AppDomain` 是长度 ++ 编码的域名 +- `长度` 是utf-8编码的应用域名长度的32位值(以字节为单位) +- `编码的域名` 是长度字节的utf-8编码的应用域名 +* `Timestamp` 是签名操作的64位unix时代时间 +* `Payload` 是一个可变长度的二进制字符串。 + +注意:有效载荷是可变长度的不受信任数据。为避免使用不必要的长度前缀,我们将其放在消息的最后。 + +公钥必须验证签名: + +1. 首先,尝试通过在 `Address` 部署的智能合约上的 `get_public_key` get-method 获得公钥。 + +2. 如果智能合约尚未部署,或缺少get-method,您需要: + + 1. 解析 `TonAddressItemReply.walletStateInit` 并从stateInit获取公钥。您可以将 `walletStateInit.code` 与标准钱包合约的代码进行比较,并根据找到的钱包版本解析数据。 + + 2. 检查 `TonAddressItemReply.publicKey` 是否等于获得的公钥。 + + 3. 检查 `TonAddressItemReply.walletStateInit.hash()` 是否等于 `TonAddressItemReply.address`。`.hash()` 意味着BoC哈希。 + + +### TON Proof 验证示例 + +* [GO 演示应用](https://github.com/ton-connect/demo-dapp-backend/blob/master/proof.go) +* [TS 示例](https://gist.github.com/TrueCarry/cac00bfae051f7028085aa018c2a05c6) +* [Python 示例](https://github.com/XaBbl4/pytonconnect/blob/main/examples/check_proof.py) + +## 参阅 + +* [[YouTube] 为 @tonconnect/react-ui 检查 ton_proof [RU]](https://youtu.be/wIMbkJHv0Fs?list=PLyDBPwv9EPsCJ226xS5_dKmXXxWx1CKz_&t=2971) +* [准备消息](/develop/dapps/ton-connect/message-builders) +* [发送消息](/develop/dapps/ton-connect/transactions) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/overview.mdx new file mode 100644 index 0000000000..66b9641a5b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/overview.mdx @@ -0,0 +1,101 @@ +import Button from '@site/src/components/button' + +# 关于 TON Connect + +使用TON Connect,在[TON](/learn/introduction)中实现钱包之间的无缝连接。 + +![](/img/docs/ton-connect/ton-connect-overview.png?raw=true) + +随意使用以下流程之一,以集成您的应用程序: + + + + + + + +## 应用程序的使用案例 + +探索 TON 生态系统为卓越的应用程序集成提供的这些可交付成果。 + +- **流量**。通过支持TON Connect的加密钱包,推动额外用户访问。 +- **真实**。利用TON用户的钱包作为现成账户,无需额外的身份验证步骤,从而提升用户体验。 +- **支付**。通过TON区块链使用Toncoin或封装的稳定币(jUSDC/jUSDT)快速安全地处理交易。 +- **留存**。通过应用内列表保存功能增强用户留存,使用户能够跟踪最近打开和收藏的应用。 + +## 对于钱包开发者 + +如果您是钱包开发者,您可以将您的钱包连接到 TON Connect,让您的用户以安全、便捷的方式与 TON 应用程序互动,请阅读如何[将 TON Connect 集成到您的钱包](/develop/dapps/ton-connect/wallet/)。 + +## 成功案例 + +- [GetGems — 开放网络Marketplace](https://getgems.io/) +- [STON.fi — TON区块链AMM DEX](https://ston.fi/) +- [Tonstarter](http://tonstarter.com/) + +
+ 显示完整列表 + +- [getgems.io](https://getgems.io/) +- [fragment.com](https://fragment.com/) (Ton Connect v.1) +- [ston.fi](https://ston.fi/) +- [ton.diamonds](https://ton.diamonds/) +- [beta.disintar.io](https://beta.disintar.io/) +- [tegro.finance](https://tegro.finance/liquidity) +- [minter.ton.org](https://minter.ton.org/) +- [libermall.com](https://libermall.com/) +- [dedust.io](https://dedust.io/swap) +- [toncap.net](https://toncap.net/) +- [cryptomus.com](https://cryptomus.com/) +- [avanchange.com](https://avanchange.com/) +- [wton.dev](https://wton.dev/) +- [mint.spiroverse.io/shop](https://mint.spiroverse.io/shop) +- [vk.com/vk_nft_hub](https://vk.com/vk_nft_hub) +- [tonverifier.live](https://verifier.ton.org/) +- [stickerface.io/member](https://stickerface.io/member) +- [tonstarter.com](https://tonstarter.com/) +- [cryptogas.shop/ton](https://cryptogas.shop/ton) +- [megaton.fi](https://megaton.fi/) +- [dns.ton.org](https://dns.ton.org/) +- [coinpaymaster.com](https://coinpaymaster.com/) +- [ton.gagarin.world/app/](https://ton.gagarin.world/app) +- [daolama.co](https://daolama.co/) +- [marketplace.playmuse.org](http://marketplace.playmuse.org/) +- [ton.vote](https://ton.vote/) +- [plane.tonfancy.io](https://plane.tonfancy.io/) +- [pi.oberton.io](https://pi.oberton.io/) +- [business.thetonpay.app](https://business.thetonpay.app/) +- [bridge.orbitchain.io](https://bridge.orbitchain.io/) +- [connecton-web-new.vercel.app](https://connecton-web-new.vercel.app/) +- [app.fanz.ee/staking](https://app.fanz.ee/staking) +- [testnet.pton.fi](https://testnet.pton.fi/) +- [tonft.app](https://tonft.app/) +- [cardify.casino](https://cardify.casino/) +- [4riends.org](https://4riends.org/#/) +- [tonflex.fi](https://tonflex.fi/swap) +- [soquest.xyz](https://soquest.xyz/) +- [app.evaa.finance](https://app.evaa.finance/) + +
+ +## 加入 TON 生态系统 + +为了将您的服务与TON生态系统连接起来,您需要执行以下操作: + +- **TON Connect**。在您的应用程序中集成TON Connect协议。 +- **交易**。使用TON库创建指定的交易消息。通过我们的全面指南深入了解[发送消息](/develop/dapps/ton-connect/message-builders)的过程。 +- **支付**。通过公共API([tonapi](https://tonapi.io/))或您自己的索引器,例如[gobycicle](http://github.com/gobicycle/bicycle)处理支付。从我们关于[资产处理](/develop/dapps/asset-processing)的详尽指南中了解更多信息。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/wallet.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/wallet.mdx new file mode 100644 index 0000000000..99811809f8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/ton-connect/wallet.mdx @@ -0,0 +1,19 @@ +import Button from '@site/src/components/button' + +# 连接钱包 + +如果您是一名钱包开发者,您可以将您的钱包连接到TON Connect,并使您的用户能够以安全便捷的方式与TON应用程序进行交互。 + +## 集成 + +使用以下步骤将您的钱包连接到TON Connect: + +1. 仔细阅读[协议规范](/develop/dapps/ton-connect/protocol/)。 +2. 使用其中一个[SDK](/develop/dapps/ton-connect/developers)实现协议。 +3. 通过拉取请求将您的钱包添加到[钱包列表](https://github.com/ton-blockchain/wallets-list)中。 + + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/overview.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/overview.mdx new file mode 100644 index 0000000000..20ef53dba7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/overview.mdx @@ -0,0 +1,17 @@ +# 概览 + +## 概念 + +阅读更多关于思想的内容: + +- [TON上的支付](https://blog.ton.org/ton-payments) +- [TON DNS和域名](/participate/web3/dns) +- [TON网站、TON WWW和TON代理](https://blog.ton.org/ton-sites) + +## 用例 + +- [为任何智能合约提供\*.ton用户友好域名](/participate/web3/dns) +- [使用TON代理连接到TON网站](/participate/web3/setting-proxy) +- [运行自己的TON代理以连接到TON网站](/participate/web3/sites-and-proxy) +- [将您的TON钱包或TON网站链接到一个域名](/participate/web3/site-management) +- [如何使用TON DNS智能合约创建子域名](/participate/web3/site-management#how-to-set-up-subdomains) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/dns.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/dns.md new file mode 100644 index 0000000000..6964dd0046 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/dns.md @@ -0,0 +1,45 @@ +# TON DNS和域名 + +TON DNS是一项服务,用于将易于人类阅读的域名(如`test.ton`或`mysite.temp.ton`)转换为TON智能合约地址、TON网络上运行的服务(如TON网站)所使用的ADNL地址等。 + +## 标准 + +[TON DNS标准](https://github.com/ton-blockchain/TIPs/issues/81)描述了域名的格式、解析域的过程、DNS智能合约的接口以及DNS记录的格式。 + +## SDK + +在JavaScript SDK [TonWeb](https://github.com/toncenter/tonweb) 和 [TonLib](https://ton.org/#/apis/?id=_2-ton-api)中实现了与TON DNS的交互。 + +```js +const address: Address = await tonweb.dns.getWalletAddress('test.ton'); + +// or + +const address: Address = await tonweb.dns.resolve('test.ton', TonWeb.dns.DNS_CATEGORY_WALLET); +``` + +`lite-client` 和 `tonlib-cli` 也支持DNS查询。 + +## 一级域名 + +目前,只有以`.ton`结尾的域名被认为是有效的TON DNS域名。 + +根DNS智能合约源代码 - https://github.com/ton-blockchain/dns-contract/blob/main/func/root-dns.fc。 + +将来这可能会改变。添加新的一级域名将需要新的根智能合约和改变[网络配置#4](https://ton.org/#/smart-contracts/governance?id=config)的通用投票。 + +## \*.ton域名 + +\*.ton域名以NFT的形式实现。由于它们实现了NFT标准,因此与常规NFT服务(例如NFT市场)和可以显示NFT的钱包兼容。 + +\*.ton域名源代码 - https://github.com/ton-blockchain/dns-contract。 + +.ton域名解析器实现了NFT集合接口,而.ton域名实现了NFT项接口。 + +\*.ton域名的首次销售通过https://dns.ton.org上的去中心化公开拍卖进行。源代码 - https://github.com/ton-blockchain/dns。 + +## 子域名 + +域名所有者可以通过在DNS记录`sha256("dns_next_resolver")`中设置负责解析子域名的智能合约地址来创建子域名。 + +它可以是任何实现DNS标准的智能合约。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/subresolvers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/subresolvers.md new file mode 100644 index 0000000000..6378cdbee4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-dns/subresolvers.md @@ -0,0 +1,445 @@ +# TON DNS 解析器 + +## 介绍 + +TON DNS 是一个强大的工具。它不仅允许将 TON 网站/存储包分配给域名,还可以设置子域名解析。 + +## 相关链接 + +1. [TON 智能合约地址系统](/learn/overviews/addresses) +2. [TEP-0081 - TON DNS 标准](https://github.com/ton-blockchain/TEPs/blob/master/text/0081-dns-standard.md) +3. [.ton DNS 集合的源代码](https://tonscan.org/address/EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz#source) +4. [.t.me DNS 集合的源代码](https://tonscan.org/address/EQCA14o1-VWhS2efqoh_9M1b_A9DtKTuoqfmkn83AbJzwnPi#source) +5. [域名合约搜索器](https://tonscan.org/address/EQDkAbAZNb4uk-6pzTPDO2s0tXZweN-2R08T2Wy6Z3qzH_Zp#source) +6. [简单子域名管理器代码](https://github.com/Gusarich/simple-subdomain/blob/198485bbc9f7f6632165b7ab943902d4e125d81a/contracts/subdomain-manager.fc) + +## 域名合约搜索器 + +子域名具有实际用途。例如,区块链浏览器目前没有提供通过名称查找域名合约的方法。让我们探索如何创建一个合约,提供查找这类域名的机会。 + +:::info +This contract is deployed at [EQDkAbAZNb4uk-6pzTPDO2s0tXZweN-2R08T2Wy6Z3qzH\_Zp](https://tonscan.org/address/EQDkAbAZNb4uk-6pzTPDO2s0tXZweN-2R08T2Wy6Z3qzH_Zp#source) and linked to `resolve-contract.ton`. To test it, you may write `.resolve-contract.ton` in the address bar of your favourite TON explorer and get to the page of TON DNS domain contract. Subdomains and .t.me domains are supported as well. + +您可以尝试通过访问 `resolve-contract.ton.resolve-contract.ton` 来查看解析器代码。不幸的是,这将不会显示子解析器(那是不同的智能合约),您将看到域名合约本身的页面。 +::: + +### dnsresolve() 代码 + +部分重复部分已省略。 + +```func +(int, cell) dnsresolve(slice subdomain, int category) method_id { + int subdomain_bits = slice_bits(subdomain); + throw_unless(70, (subdomain_bits % 8) == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; ;; assuming that 'subdomain' is not empty + if (starts_with_zero_byte) { + subdomain~load_uint(8); + if (subdomain.slice_bits() == 0) { ;; current contract has no DNS records by itself + return (8, null()); + } + } + + ;; we are loading some subdomain + ;; supported subdomains are "ton\0", "me\0t\0" and "address\0" + + slice subdomain_sfx = null(); + builder domain_nft_address = null(); + + if (subdomain.starts_with("746F6E00"s)) { + ;; we're resolving + ;; "ton" \0 \0 [subdomain_sfx] + subdomain~skip_bits(32); + + ;; reading domain name + subdomain_sfx = subdomain; + while (subdomain_sfx~load_uint(8)) { } + + subdomain~skip_last_bits(8 + slice_bits(subdomain_sfx)); + + domain_nft_address = get_ton_dns_nft_address_by_index(slice_hash(subdomain)); + } elseif (subdomain.starts_with("6164647265737300"s)) { + subdomain~skip_bits(64); + + domain_nft_address = subdomain~decode_base64_address_to(begin_cell()); + + subdomain_sfx = subdomain; + if (~ subdomain_sfx.slice_empty?()) { + throw_unless(71, subdomain_sfx~load_uint(8) == 0); + } + } else { + return (0, null()); + } + + if (slice_empty?(subdomain_sfx)) { + ;; example of domain being resolved: + ;; [initial, not accessible in this contract] "ton\0resolve-contract\0ton\0ratelance\0" + ;; [what is accessible by this contract] "ton\0ratelance\0" + ;; subdomain "ratelance" + ;; subdomain_sfx "" + + ;; we want the resolve result to point at contract of 'ratelance.ton', not its owner + ;; so we must answer that resolution is complete + "wallet"H is address of 'ratelance.ton' contract + + ;; dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord; + ;; _ (HashmapE 256 ^DNSRecord) = DNS_RecordSet; + + cell wallet_record = begin_cell().store_uint(0x9fd3, 16).store_builder(domain_nft_address).store_uint(0, 8).end_cell(); + + if (category == 0) { + cell dns_dict = new_dict(); + dns_dict~udict_set_ref(256, "wallet"H, wallet_record); + return (subdomain_bits, dns_dict); + } elseif (category == "wallet"H) { + return (subdomain_bits, wallet_record); + } else { + return (subdomain_bits, null()); + } + } else { + ;; subdomain "resolve-contract" + ;; subdomain_sfx "ton\0ratelance\0" + ;; we want to pass \0 further, so that next resolver has opportunity to process only one byte + + ;; next resolver is contract of 'resolve-contract<.ton>' + ;; dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; + cell resolver_record = begin_cell().store_uint(0xba93, 16).store_builder(domain_nft_address).end_cell(); + return (subdomain_bits - slice_bits(subdomain_sfx) - 8, resolver_record); + } +} +``` + +### dnsresolve() 解释 + +- 用户请求 `"stabletimer.ton.resolve-contract.ton"`。 +- 应用程序将其转换为 `"\0ton\0resolve-contract\0ton\0stabletimer\0"`(第一个零字节是可选的)。 +- 根 DNS 解析器将请求定向到 TON DNS 集合,剩余部分为 `"\0resolve-contract\0ton\0stabletimer\0"`。 +- TON DNS 集合将请求委托给特定域名,留下 `"\0ton\0stabletimer\0"`。 +- .TON DNS 域名合约将解析传递给编辑器指定的子解析器,子域名为 `"ton\0stabletimer\0"`。 + +**这是 dnsresolve() 被调用的点。** 分步解释其工作方式: + +1. 它将子域名和类别作为输入。 +2. 如果开头有零字节,则跳过。 +3. 检查子域名是否以 `"ton\0"` 开头。如果是, + 1. 跳过前32位(子域名 = `"resolve-contract\0"`) + 2. 设置 `subdomain_sfx` 的值为 `subdomain`,并读取直到零字节的字节 + 3. (子域名 = `"resolve-contract\0"`,subdomain_sfx = `""`) + 4. 从子域名切片的末尾裁剪零字节和 subdomain_sfx(子域名 = `"resolve-contract"`) + 5. 使用 slice_hash 和 get_ton_dns_nft_address_by_index 函数将域名转换为合约地址。您可以在 [[Subresolvers#Appendix 1. resolve-contract.ton 的代码|附录 1]] 中看到它们。 +4. 否则,dnsresolve() 检查子域名是否以 `"address\0"` 开头。如果是,它跳过该前缀并读取 base64 地址。 +5. 如果提供的用于解析的子域名与这些前缀都不匹配,函数通过返回 `(0, null())`(零字节前缀解析无 DNS 条目)表示失败。 +6. 然后检查子域名后缀是否为空。空后缀表示请求已完全满足。如果后缀为空: + 1. dnsresolve() 为域名的 "wallet" 子部分创建一个 DNS 记录,使用它检索到的 TON 域名合约地址。 + 2. 如果请求类别 0(所有 DNS 条目),则将记录包装在字典中并返回。 + 3. 如果请求类别为 "wallet"H,则按原样返回记录。 + 4. 否则,指定类别没有 DNS 条目,因此函数表示解析成功但未找到任何结果。 +7. 如果后缀不为空: + 1. 之前获得的合约地址用作下一个解析器。函数构建指向它的下一个解析器记录。 + 2. `"\0ton\0stabletimer\0"` 被传递给该合约:处理的位是子域名的位。 + +总结来说,dnsresolve() 要么: + +- 将子域名完全解析为 DNS 记录 +- 部分解析为解析器记录,以将解析传递给另一个合约 +- 为未知子域名返回“未找到域名”的结果 + +:::warning +实际上,base64 地址解析不起作用:如果您尝试输入 `.address.resolve-contract.ton`,您将收到一个错误,表明域名配置错误或不存在。原因是域名不区分大小写(从真实 DNS 继承的功能),因此会转换为小写,将您带到不存在的工作链的某个地址。 +::: + +### 绑定解析器 + +现在子解析器合约已部署,我们需要将域名指向它,即更改域名的 `dns_next_resolver` 记录。我们可以通过将以下 TL-B 结构的消息发送到域名合约来实现。 + +1. `change_dns_record#4eb1f0f9 query_id:uint64 record_key#19f02441ee588fdb26ee24b2568dd035c3c9206e11ab979be62e55558a1d17ff record:^[dns_next_resolver#ba93 resolver:MsgAddressInt]` + +## 创建自己的子域名管理器 + +子域名对普通用户来说可能有用 - 例如,将几个项目链接到单个域名,或链接到朋友的钱包。 + +### 合约数据 + +我们需要在合约数据中存储所有者的地址和 *域名*->*记录哈希*->*记录值* 字典。 + +```func +global slice owner; +global cell domains; + +() load_data() impure { + slice ds = get_data().begin_parse(); + owner = ds~load_msg_addr(); + domains = ds~load_dict(); +} +() save_data() impure { + set_data(begin_cell().store_slice(owner).store_dict(domains).end_cell()); +} +``` + +### 处理记录更新 + +```func +const int op::update_record = 0x537a3491; +;; op::update_record#537a3491 domain_name:^Cell record_key:uint256 +;; value:(Maybe ^Cell) = InMsgBody; + +() recv_internal(cell in_msg, slice in_msg_body) { + if (in_msg_body.slice_empty?()) { return (); } ;; simple money transfer + + slice in_msg_full = in_msg.begin_parse(); + if (in_msg_full~load_uint(4) & 1) { return (); } ;; bounced message + + slice sender = in_msg_full~load_msg_addr(); + load_data(); + throw_unless(501, equal_slices(sender, owner)); + + int op = in_msg_body~load_uint(32); + if (op == op::update_record) { + slice domain = in_msg_body~load_ref().begin_parse(); + (cell records, _) = domains.udict_get_ref?(256, string_hash(domain)); + + int key = in_msg_body~load_uint(256); + throw_if(502, key == 0); ;; cannot update "all records" record + + if (in_msg_body~load_uint(1) == 1) { + cell value = in_msg_body~load_ref(); + records~udict_set_ref(256, key, value); + } else { + records~udict_delete?(256, key); + } + + domains~udict_set_ref(256, string_hash(domain), records); + save_data(); + } +} +``` + +我们检查传入消息是否包含某些请求,不是弹回的,来自所有者,且请求为 `op::update_record`。 + +然后,我们从消息中加载域名。我们不能将域名按原样存储在字典中:它们可能有不同的长度,但 TVM 非前缀字典只能包含等长的键。因此,我们计算 `string_hash(domain)` - 域名的 SHA-256;域名保证有整数个八位字节,因此这是有效的。 + +之后,我们为指定域名更新记录,并将新数据保存到合约存储中。 + +### 解析域名 + +```func +(slice, slice) ~parse_sd(slice subdomain) { + ;; "test\0qwerty\0" -> "test" "qwerty\0" + slice subdomain_sfx = subdomain; + while (subdomain_sfx~load_uint(8)) { } ;; searching zero byte + subdomain~skip_last_bits(slice_bits(subdomain_sfx)); + return (subdomain, subdomain_sfx); +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + int subdomain_bits = slice_bits(subdomain); + throw_unless(70, subdomain_bits % 8 == 0); + if (subdomain.preload_uint(8) == 0) { subdomain~skip_bits(8); } + + slice subdomain_suffix = subdomain~parse_sd(); ;; "test\0" -> "test" "" + int subdomain_suffix_bits = slice_bits(subdomain_suffix); + + load_data(); + (cell records, _) = domains.udict_get_ref?(256, string_hash(subdomain)); + + if (subdomain_suffix_bits > 0) { ;; more than "\0" requested + category = "dns_next_resolver"H; + } + + int resolved = subdomain_bits - subdomain_suffix_bits; + + if (category == 0) { ;; all categories are requested + return (resolved, records); + } + + (cell value, int found) = records.udict_get_ref?(256, category); + return (resolved, value); +} +``` + +`dnsresolve` 函数检查请求的子域名是否包含整数个八位字节,跳过子域名切片开头的可选零字节,然后将其分割为最高级别的域和其他部分(`test\0qwerty\0` 被分割为 `test` 和 `qwerty\0`)。加载与请求的域名对应的记录字典。 + +如果存在非空子域名后缀,函数返回已解析的字节数和在 `"dns_next_resolver"H` 键下找到的下一个解析器记录。否则,函数返回已解析的字节数(即整个切片长度)和请求的记录。 + +可以通过更优雅地处理错误来改进此函数,但这不是绝对必需的。 + +## 附录 1. resolve-contract.ton 的代码 + +
+ +```func showLineNumbers +(builder, ()) ~store_slice(builder to, slice s) asm "STSLICER"; +int starts_with(slice a, slice b) asm "SDPFXREV"; + +const slice ton_dns_minter = "EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz"a; +cell ton_dns_domain_code() asm """ + B{} + B>boc + PUSHREF +"""; + +const slice tme_minter = "EQCA14o1-VWhS2efqoh_9M1b_A9DtKTuoqfmkn83AbJzwnPi"a; +cell tme_domain_code() asm """ + B{} + B>boc + PUSHREF +"""; + +cell calculate_ton_dns_nft_item_state_init(int item_index) inline { + cell data = begin_cell().store_uint(item_index, 256).store_slice(ton_dns_minter).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(ton_dns_domain_code()).store_dict(data).store_uint(0, 1).end_cell(); +} + +cell calculate_tme_nft_item_state_init(int item_index) inline { + cell config = begin_cell().store_uint(item_index, 256).store_slice(tme_minter).end_cell(); + cell data = begin_cell().store_ref(config).store_maybe_ref(null()).end_cell(); + return begin_cell().store_uint(0, 2).store_dict(tme_domain_code()).store_dict(data).store_uint(0, 1).end_cell(); +} + +builder calculate_nft_item_address(int wc, cell state_init) inline { + return begin_cell() + .store_uint(4, 3) + .store_int(wc, 8) + .store_uint(cell_hash(state_init), 256); +} + +builder get_ton_dns_nft_address_by_index(int index) inline { + cell state_init = calculate_ton_dns_nft_item_state_init(index); + return calculate_nft_item_address(0, state_init); +} + +builder get_tme_nft_address_by_index(int index) inline { + cell state_init = calculate_tme_nft_item_state_init(index); + return calculate_nft_item_address(0, state_init); +} + +(slice, builder) decode_base64_address_to(slice readable, builder target) inline { + builder addr_with_flags = begin_cell(); + repeat(48) { + int char = readable~load_uint(8); + if (char >= "a"u) { + addr_with_flags~store_uint(char - "a"u + 26, 6); + } elseif ((char == "_"u) | (char == "/"u)) { + addr_with_flags~store_uint(63, 6); + } elseif (char >= "A"u) { + addr_with_flags~store_uint(char - "A"u, 6); + } elseif (char >= "0"u) { + addr_with_flags~store_uint(char - "0"u + 52, 6); + } else { + addr_with_flags~store_uint(62, 6); + } + } + + slice addr_with_flags = addr_with_flags.end_cell().begin_parse(); + addr_with_flags~skip_bits(8); + addr_with_flags~skip_last_bits(16); + + target~store_uint(4, 3); + target~store_slice(addr_with_flags); + return (readable, target); +} + +slice decode_base64_address(slice readable) method_id { + (slice _remaining, builder addr) = decode_base64_address_to(readable, begin_cell()); + return addr.end_cell().begin_parse(); +} + +(int, cell) dnsresolve(slice subdomain, int category) method_id { + int subdomain_bits = slice_bits(subdomain); + + throw_unless(70, (subdomain_bits % 8) == 0); + + int starts_with_zero_byte = subdomain.preload_int(8) == 0; ;; assuming that 'subdomain' is not empty + if (starts_with_zero_byte) { + subdomain~load_uint(8); + if (subdomain.slice_bits() == 0) { ;; current contract has no DNS records by itself + return (8, null()); + } + } + + ;; we are loading some subdomain + ;; supported subdomains are "ton\0", "me\0t\0" and "address\0" + + slice subdomain_sfx = null(); + builder domain_nft_address = null(); + + if (subdomain.starts_with("746F6E00"s)) { + ;; we're resolving + ;; "ton" \0 \0 [subdomain_sfx] + subdomain~skip_bits(32); + + ;; reading domain name + subdomain_sfx = subdomain; + while (subdomain_sfx~load_uint(8)) { } + + subdomain~skip_last_bits(8 + slice_bits(subdomain_sfx)); + + domain_nft_address = get_ton_dns_nft_address_by_index(slice_hash(subdomain)); + } elseif (subdomain.starts_with("6D65007400"s)) { + ;; "t" \0 "me" \0 \0 [subdomain_sfx] + subdomain~skip_bits(40); + + ;; reading domain name + subdomain_sfx = subdomain; + while (subdomain_sfx~load_uint(8)) { } + + subdomain~skip_last_bits(8 + slice_bits(subdomain_sfx)); + + domain_nft_address = get_tme_nft_address_by_index(string_hash(subdomain)); + } elseif (subdomain.starts_with("6164647265737300"s)) { + subdomain~skip_bits(64); + + domain_nft_address = subdomain~decode_base64_address_to(begin_cell()); + + subdomain_sfx = subdomain; + if (~ subdomain_sfx.slice_empty?()) { + throw_unless(71, subdomain_sfx~load_uint(8) == 0); + } + } else { + return (0, null()); + } + + if (slice_empty?(subdomain_sfx)) { + ;; example of domain being resolved: + ;; [initial, not accessible in this contract] "ton\0resolve-contract\0ton\0ratelance\0" + ;; [what is accessible by this contract] "ton\0ratelance\0" + ;; subdomain "ratelance" + ;; subdomain_sfx "" + + ;; we want the resolve result to point at contract of 'ratelance.ton', not its owner + ;; so we must answer that resolution is complete + "wallet"H is address of 'ratelance.ton' contract + + ;; dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord; + ;; _ (HashmapE 256 ^DNSRecord) = DNS_RecordSet; + + cell wallet_record = begin_cell().store_uint(0x9fd3, 16).store_builder(domain_nft_address).store_uint(0, 8).end_cell(); + + if (category == 0) { + cell dns_dict = new_dict(); + dns_dict~udict_set_ref(256, "wallet"H, wallet_record); + return (subdomain_bits, dns_dict); + } elseif (category == "wallet"H) { + return (subdomain_bits, wallet_record); + } else { + return (subdomain_bits, null()); + } + } else { + ;; example of domain being resolved: + ;; [initial, not accessible in this contract] "ton\0resolve-contract\0ton\0resolve-contract\0ton\0ratelance\0" + ;; [what is accessible by this contract] "ton\0resolve-contract\0ton\0ratelance\0" + ;; subdomain "resolve-contract" + ;; subdomain_sfx "ton\0ratelance\0" + ;; and we want to pass \0 further, so that next resolver has opportunity to process only one byte + + ;; next resolver is contract of 'resolve-contract<.ton>' + ;; dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; + cell resolver_record = begin_cell().store_uint(0xba93, 16).store_builder(domain_nft_address).end_cell(); + return (subdomain_bits - slice_bits(subdomain_sfx) - 8, resolver_record); + } +} + +() recv_internal() { + return (); +} +``` + +
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/connect-with-ton-proxy.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/connect-with-ton-proxy.md new file mode 100644 index 0000000000..c9e1b260d1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/connect-with-ton-proxy.md @@ -0,0 +1,71 @@ +# 通过 TON 代理连接 + +## 公共入口 TON 代理 + +您可以使用以下公共入口TON代理之一: + +- `in1.ton.org` 端口 `8080` +- `in2.ton.org` 端口 `8080` +- `in3.ton.org` 端口 `8080` + +TON代理与常规HTTP代理兼容,因此您可以直接在浏览器或操作系统设置中使用它。 + +## Google Chrome + +根据您的操作系统,遵循Windows、macOS、Linux、iOS或Android的说明。 + +## Firefox + +设置 -> 常规 -> 网络设置 -> 配置 -> 手动代理设置 -> HTTP代理 + +在“HTTP代理”字段中,输入其中一个公共入口代理的地址,在“端口”字段中,输入“8080”(不带引号)。 + +点击“确定”。 + +## Safari + +根据您的操作系统,遵循Windows、macOS、Linux、iOS或Android的说明。 + +## iOS + +设置 -> WiFi -> 点击当前连接的网络 -> 代理设置 -> 手动 + +在“服务器”字段中,输入其中一个公共入口代理的地址,在“端口”字段中,输入“8080”(不带引号)。 + +点击“保存”。 + +## Android + +设置 -> WiFi -> 长按Wi-Fi网络名称 -> 修改网络 -> 高级选项 -> 手动 + +在“服务器”字段中,输入其中一个公共入口代理的地址,在“端口”字段中,输入“8080”(不带引号)。 + +点击“保存”。 + +## Windows + +点击“开始”按钮,然后选择设置 > 网络和互联网 > 代理。 + +在“手动代理设置”下,旁边的“使用代理服务器”选择“设置”。 + +在“编辑代理服务器对话框”中,执行以下操作: + +打开“使用代理服务器”。 + +输入其中一个公共入口代理的地址,在“端口”字段中,输入“8080”(不带引号)。 + +点击“保存”。 + +## MacOS + +设置 -> 网络 -> 高级 -> 代理 -> 网络代理(HTTP)。 + +在“网络代理服务器”字段中,输入其中一个公共入口代理的地址,冒号后面输入“8080”(不带引号)。 + +点击“确定”。 + +## Ubuntu + +设置 -> 网络 -> 网络代理按钮 -> 手动 + +在“HTTP代理”字段中,输入其中一个公共入口代理的地址,对于端口,输入“8080”(不带引号)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-open-any-ton-site.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-open-any-ton-site.md new file mode 100644 index 0000000000..cc2b4cecc1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-open-any-ton-site.md @@ -0,0 +1,47 @@ +# 如何打开任何 TON 网站? + +在这篇文章中,我们将看看从不同设备访问TON网站的最常用方法。 + +每种方法都有其优缺点,我们将在这里分析。 + +我们将从最简单的方法开始,最后介绍最高级的方法。 + +## 😄 简单方法 + +### 通过ton.run浏览 + +打开TON网站最简单的方法是通过[ton.run](https://ton.run)。您无需在设备上安装或设置任何东西 - 只需打开**ton.run**,您就可以探索TON网站。 + +对于偶尔浏览TON网站或进行一些检查,这种方法可能适合,但不适合常规使用,因为它也有缺点: + +- 您信任您的互联网流量给**ton.run** +- 它可能随时离线或出故障 +- 它可能被您的互联网提供商封锁 + +### TON Wallet 和 MyTonWallet 扩展 + +稍微困难一点但更好的方法是使用某些浏览器扩展,它将连接您到TON代理,并允许您在没有任何中间服务(如ton.run)的情况下浏览TON网站。 + +目前,TON代理已经在[MyTonWallet](https://mytonwallet.io/)扩展中可用,并且很快也将在[TON Wallet](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)扩展中可用。 + +这种方法也相当简单,但您需要在浏览器中安装一个扩展才能使其工作。它适合大多数用户。 + +### 连接到公共代理 + +如果您不想安装任何扩展,或者您正在使用移动设备,您可以使用此方法。您需要在设备上配置一些东西以连接到代理。 + +此方法在此处描述: + +- [通过TON代理连接](/participate/web3/setting-proxy/) + +## 🤓 高级方法 + +### 使用Tonutils-Proxy + +这是访问TON网站最安全的方式。 + +1. 从[这里](https://github.com/xssnick/Tonutils-Proxy#download-precompiled-version)下载最新版本 + +2. 启动它并按“启动网关” + +3. 完成! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-run-ton-site.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-run-ton-site.md new file mode 100644 index 0000000000..4c9f3cba37 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/how-to-run-ton-site.md @@ -0,0 +1,63 @@ +# 如何运行 TON 网站 + +## 👋 引言 + +[TON 网站](https://blog.ton.org/ton-sites)的工作方式几乎与普通网站相同,除了它们的安装。需要执行一些额外的操作来启动它们。在这篇教程中,我将向您展示如何做到这一点。 + +## 🖥 运行 TON 网站 + +安装 [Tonutils 反向代理](https://github.com/tonutils/reverse-proxy) 来使用 TON 代理为您的网站服务。 + +### 在任何 Linux 上的安装 + +##### 下载 + +```bash +wget https://github.com/ton-utils/reverse-proxy/releases/download/v0.2.0/tonutils-reverse-proxy-linux-amd64 +chmod 777 tonutils-reverse-proxy-linux-amd64 +``` + +##### 运行 + +用域配置运行,并按步骤操作: + +``` +./tonutils-reverse-proxy-linux-amd64 --domain your-domain.ton +``` + +使用 Tonkeeper、Tonhub 或任何其他钱包扫描你的终端中的 QR 码,执行交易。您的域将会链接到您的网站上。 + +###### 无域运行 + +作为替代,如果你没有 .ton 或 .t.me 域,你可以以简单模式运行,使用 .adnl 域: + +``` +./tonutils-reverse-proxy-linux-amd64 +``` + +##### 使用 + +现在任何人都可以访问您的 TON 网站了!使用 ADNL 地址或域名。 + +如果您想更改一些设置,如代理pass url - 打开 `config.json` 文件,编辑后重启代理。默认的代理pass url是 `http://127.0.0.1:80/` + +代理添加了额外的头部: +`X-Adnl-Ip` - 客户端的 IP 和 `X-Adnl-Id` - 客户端的 ADNL ID + +### 在任何其他操作系统上的安装 + +使用 `./build.sh` 从源代码构建,然后如第 2 步中的 Linux 一样运行。构建需要 Go 环境。 + +## 👀 后续步骤 + +### 🔍 检查网站的可用性 + +完成您选择的方法的所有步骤后,TON 代理应该已经启动。如果一切成功,您的网站将可在相应步骤收到的 ADNL 地址处访问。 + +您可以通过使用域 `.adnl` 打开这个地址来检查网站的可用性。另请注意,为了打开网站,您必须在浏览器中运行 TON 代理,例如通过扩展 [MyTonWallet](https://mytonwallet.io/)。 + +## 📌 参考资料 + +- [TON 网站、TON WWW 和 TON 代理](https://blog.ton.org/ton-sites) +- [Tonutils 反向代理](https://github.com/tonutils/reverse-proxy) +- 作者: [Andrew Burnosov](https://github.com/AndreyBurnosov) (TG: [@AndrewBurnosov](https://t.me/AndreyBurnosov)),[Daniil Sedov](https://gusarich.com) (TG: [@sedov](https://t.me/sedov)),[George Imedashvili](https://github.com/drforse) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/running-your-own-ton-proxy.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/running-your-own-ton-proxy.md new file mode 100644 index 0000000000..b2b5dd82db --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/running-your-own-ton-proxy.md @@ -0,0 +1,204 @@ +# 运行自己的 TON 代理 + +本文档旨在简要地介绍TON网站,即通过TON网络访问的网站。TON网站可以方便地作为进入其他TON服务的入口。特别是,从TON网站下载的HTML页面可能包含指向`ton://...` URI的链接,用户点击这些链接后,如果用户设备上安装了TON钱包,就可以执行支付操作。 + +从技术角度看,TON网站非常类似于标准网站,但它们是通过[TON网络](/learn/networking/overview)(互联网内的一个覆盖网络)而不是互联网访问的。更具体地说,它们拥有一个[ADNL](/learn/networking/adnl)地址(而不是更常见的IPv4或IPv6地址),并通过[RLDP](/learn/networking/rldp)协议(这是建立在ADNL之上的高级RPC协议,ADNL是TON网络的主要协议)接受HTTP查询,而不是常规的TCP/IP。所有加密由ADNL处理,所以如果入口代理托管在用户设备上,就没有必要使用HTTPS(即TLS)。 + +为了访问现有的网站和创建新的TON网站,需要特殊的网关来连接“普通”互联网和TON网络。本质上,通过在客户端机器上本地运行的HTTP->RLDP代理访问TON网站,并通过在远程Web服务器上运行的RLDP->HTTP代理来创建它们。 + +[了解更多关于TON网站、WWW和代理的信息](https://blog.ton.org/ton-sites) + +## 运行入口代理 + +为了访问现有的TON网站,你需要在你的电脑上运行一个RLDP-HTTP代理。 + +1. 从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载**rldp-http-proxy**。 + + 或者你可以按照这些[指示](/develop/howto/compile#rldp-http-proxy)自己编译**rldp-http-proxy**。 + +2. [下载](/develop/howto/compile#download-global-config)TON全局配置。 + +3. 运行**rldp-http-proxy** + + ```bash + rldp-http-proxy/rldp-http-proxy -p 8080 -c 3333 -C global.config.json + ``` + +在上面的例子中,`8080`是将在本地主机上监听传入HTTP查询的TCP端口,而`3333`是将用于所有出站和入站RLDP和ADNL活动的UDP端口(即通过TON网络连接到TON网站)。`global.config.json`是TON全局配置的文件名。 + +如果一切正确,入口代理将不会终止,而是会继续在终端运行。现在可以用它来访问TON网站。当你不再需要它时,可以通过按`Ctrl-C`或简单地关闭终端窗口来终止它。 + +你的入口代理将通过HTTP在`localhost`端口`8080`上可用。 + +## 在远程计算机上运行入口代理 + +1. 从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载**rldp-http-proxy**。 + + 或者你可以按照这些[指示](/develop/howto/compile#rldp-http-proxy)自己编译**rldp-http-proxy**。 + +2. [下载](/develop/howto/compile#download-global-config)TON全局配置。 + +3. 从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载**generate-random-id**。 + + 或者你可以按照这些[指示](/develop/howto/compile#generate-random-id)自己编译**generate-random-id**。 + +4. 为你的入口代理生成一个持久的ANDL地址 + + ```bash + mkdir keyring + + utils/generate-random-id -m adnlid + ``` + + 你会看到类似于 + + ``` + 45061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 + ``` + + 这是你新生成的持久ADNL地址,以十六进制和用户友好形式显示。相应的私钥保存在当前目录的文件`45061...2DB`中。将密钥移动到keyring目录 + + ```bash + mv 45061C1* keyring/ + ``` + +5. 运行**rldp-http-proxy** + + ``` + rldp-http-proxy/rldp-http-proxy -p 8080 -a :3333 -C global.config.json -A + ``` + + 其中``是你的公共IPv4地址,``是在上一步中生成的ADNL地址。 + + 示例: + + ``` + rldp-http-proxy/rldp-http-proxy -p 8080 -a 777.777.777.777:3333 -C global.config.json -A vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 + ``` + + 在上面的示例中,`8080`是将在本地主机上监听传入HTTP查询的TCP端口,而`3333`是将用于所有出站和入站RLDP和ADNL活动的UDP端口(即通过TON网络连接到TON网站)。`global.config.json`是TON全局配置的文件名。 + +如果你做得都对,代理不会终止,而是会继续在终端运行。现在可以用它来访问TON网站。当你不再需要它时,可以通过按`Ctrl-C`或简单地关闭终端窗口来终止它。你可以将这个运行为一个unix服务以永久运行。 + +你的入口代理将通过HTTP在``端口`8080`上可用。 + +## 访问TON网站 + +现在假设你在电脑上运行了一个RLDP-HTTP代理的实例,并且正在`localhost:8080`上监听传入的TCP连接,如[上面](#running-entry-proxy)所解释的。 + +使用诸如`curl`或`wget`之类的程序进行简单测试以确认一切正常运行是可行的。例如, + +``` +curl -x 127.0.0.1:8080 http://just-for-test.ton +``` + +尝试使用代理`127.0.0.1:8080`下载(TON)站点`just-for-test.ton`的主页。如果代理正常运行,你将看到类似于 + +```html + + + +TON Site + + +

TON Proxy Works!

+ + + +``` + +你还可以通过使用假域名`.adnl`通过它们的ADNL地址访问TON网站 + +```bash +curl -x 127.0.0.1:8080 http://utoljjye6y4ixazesjofidlkrhyiakiwrmes3m5hthlc6ie2h72gllt.adnl/ +``` + +目前获取的是同一个TON网页。 + +或者,你可以在浏览器中将`localhost:8080`设置为HTTP代理。例如,如果你使用Firefox,请访问[设置] -> 通用 -> 网络设置 -> 设置 -> 配置代理访问 -> 手动代理配置,并在“HTTP代理”字段中输入“127.0.0.1”,在“端口”字段中输入“8080”。 + +一旦你在浏览器中设置了`localhost:8080`作为HTTP代理,你就可以在浏览器的导航 + +## 运行TON网站 + +:::tip 教程找到了! +嘿!不要从初学者友好的教程[如何运行TON网站?](/develop/dapps/tutorials/how-to-run-ton-site)开始 +::: + +大多数人只需要访问现有的TON网站,而不是创建新的。然而,如果你想创建一个,你需要在你的服务器上运行RLDP-HTTP代理,以及像Apache或Nginx这样的常规Web服务器软件。 + +我们假设您已经知道如何建立一个普通网站,并且已经在服务器上配置了一个网站,接受 TCP 端口 `:80` 的 HTTP 连接,并在网络服务器配置中定义了所需的 TON 网络域名(例如 `example.ton`)作为网站的主域名或别名。 + +1. 从 [TON Auto Builds](https://github.com/ton-blockchain/ton/releases/latest) 下载 **rldp-http-proxy** 。 + + 或者你可以按照这个[指示](/develop/howto/compile#rldp-http-proxy)自己编译**rldp-http-proxy**。 + +2. [下载](/develop/howto/compile#download-global-config)TON全局配置。 + +3. 从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载**generate-random-id**。 + + 或者你可以按照这些[指示](/develop/howto/compile#generate-random-id)自己编译**generate-random-id**。 + +4. 为你的服务器生成一个持久的ANDL地址 + + ```bash + mkdir keyring + + utils/generate-random-id -m adnlid + ``` + + 你会看到类似于 + + ```bash + 45061C1D4EC44A937D0318589E13C73D151D1CEF5D3C0E53AFBCF56A6C2FE2BD vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 + ``` + + 这是你新生成的持久ADNL地址,以十六进制和用户友好形式显示。相应的私钥保存在当前目录的文件`45061...2DB`中。将密钥移动到keyring目录 + + ```bash + mv 45061C1* keyring/ + ``` + +5. 确保你的Web服务器接受带有`.ton`和`.adnl`域名的HTTP请求。 + + 例如,如果你使用带有配置`server_name example.com;`的nginx,你需要将其更改为`server_name _;`或`server_name example.com example.ton vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3.adnl;`。 + +6. 以反向模式运行代理 + + ```bash + rldp-http-proxy/rldp-http-proxy -a :3333 -L '*' -C global.config.json -A -d -l + ``` + + 其中``是你的服务器公共IPv4地址,``是在上一步中生成的ADNL地址。 + +如果你想让你的TON网站永久运行,你将不得不使用选项`-d`和`-l `。 + +示例: + +```bash +rldp-http-proxy/rldp-http-proxy -a 777.777.777.777:3333 -L '*' -C global.config.json -A vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 -d -l tonsite.log +``` + +如果一切正常工作,RLDP-HTTP代理将接受来自TON网络的传入HTTP查询,通过运行在UDP端口3333的IPv4地址``(特别是,如果你使用防火墙,请不要忘记允许`rldp-http-proxy`从该端口接收和发送UDP数据包)的RLDP/ADNL,它将把这些HTTP查询转发到所有主机(如果你只想转发特定主机,请将`-L '*'`更改为`-L `)的`127.0.0.1`TCP端口`80`(即你的常规Web服务器)。 + +你可以在客户端机器上的浏览器中访问TON网站`http://.adnl`(在这个示例中是`http://vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3.adnl`),如“访问TON网站”部分所解释的,并检查你的TON网站是否真的对公众开放。 + +如果你愿意,你可以[注册](/participate/web3/site-management)一个TON DNS域名,比如'example.ton',并为这个域名创建一个指向你TON网站的持久ADNL地址的`site`记录。然后,在客户端模式下运行的RLDP-HTTP代理将会解析http://example.ton为指向你的ADNL地址,并访问你的TON网站。 + +你还可以在一个单独的服务器上运行反向代理,并将你的Web服务器设置为远程地址。在这种情况下,请使用`-R '*'@:`替代`-L '*'`。 + +示例: + +```bash +rldp-http-proxy/rldp-http-proxy -a 777.777.777.777:3333 -R '*'@333.333.333.333:80 -C global.config.json -A vcqmha5j3ceve35ammfrhqty46rkhi455otydstv66pk2tmf7rl25f3 -d -l tonsite.log +``` + +在这种情况下,你的常规Web服务器应该在 `http://333.333.333.333:80` 上可用(这个IP不会对外暴露)。 + +### 建议 + +由于匿名功能将只在TON Proxy 2.0中可用,如果你不想公开你的Web服务器的IP地址,你可以通过以下两种方式实现: + +- 在单独的服务器上运行反向代理,并使用`-R`标志,如上所述。 + +- 制作一个带有你网站副本的重复服务器,并在本地运行反向代理。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/site-and-domain-management.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/site-and-domain-management.md new file mode 100644 index 0000000000..f91488cdba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-proxy-sites/site-and-domain-management.md @@ -0,0 +1,49 @@ +# 网站和域名管理 + +## 如何打开域名进行编辑 + +1. 在您的电脑上打开Google Chrome浏览器。 + +2. 从此[链接](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)安装Google Chrome的TON扩展。 + +3. 打开扩展,点击“导入钱包”,并导入存储域名的钱包。 + +> 恢复短语 +> +> 您的恢复短语是您在创建钱包时写下的24个单词。 +> +> 如果您丢失了短语,可以使用任何TON钱包进行恢复。 +> 在Tonkeeper中:设置 > 钱包保护 > 您的私钥。 +> +> 请务必记下这24个单词,并将它们保存在安全的地方。在紧急情况下,您只能通过恢复短语来恢复对钱包的访问。 +> 请严格保密您的恢复短语。任何获得您恢复短语的人都将完全控制您的资金。 + +4. 现在在https://dns.ton.org打开您的域名并点击“编辑”按钮。 + +## 如何将钱包链接到域名 + +您可以将钱包链接到域名,这样用户将能够通过输入域名作为接收地址来向该钱包发送币,而不是钱包地址。 + +1. 按上述方法打开域名进行编辑。 + +2. 将您的钱包地址复制到“Wallet address”字段中,然后点击“保存”。 + +3. 在扩展中确认发送交易。 + +## 如何将 TON 网站链接到域名 + +1. 按上述方法打开域名进行编辑。 + +2. 将您的TON网站的ADNL地址以HEX格式复制到“Site”字段中,然后点击“保存”。 + +3. 在扩展中确认发送交易。 + +## 如何设置子域名 + +1. 在网络上创建一个智能合约,用于管理您网站或服务的子域名。您可以使用现成的[manual-dns](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/dns-manual-code.fc)或[auto-dns](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/dns-auto-code.fc)智能合约,或任何实现TON DNS接口的其他智能合约。 + +2. 按上述方法打开域名进行编辑。 + +3. 将子域名的智能合约地址复制到“Subdomains”字段中,然后点击“保存”。 + +4. 在扩展中确认发送交易。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-daemon.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-daemon.md new file mode 100644 index 0000000000..c9c1485deb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-daemon.md @@ -0,0 +1,159 @@ +# 存储守护程序 + +*存储守护程序是用于在TON网络中下载和共享文件的程序。`storage-daemon-cli`控制台程序用于管理正在运行的存储守护程序。* + +当前版本的存储守护程序可以在[Testnet](https://github.com/ton-blockchain/ton/tree/testnet)分支中找到。 + +## 硬件要求 + +- 至少1GHz和2核CPU +- 至少2 GB RAM +- 至少2 GB SSD(不计算种子文件占用空间) +- 10 Mb/s网络带宽,具有静态IP + +## 二进制文件 + +您可以从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载适用于Linux/Windows/MacOS的`storage-daemon`和`storage-daemon-cli`二进制文件。 + +## 从源代码编译 + +您可以使用此[说明](/develop/howto/compile#storage-daemon)从源代码编译`storage-daemon`和`storage-daemon-cli`。 + +## 关键概念 + +- *文件包*或*包* - 通过TON存储分发的文件集合 +- TON存储的网络部分基于类似于种子的技术,因此术语*种子*、*文件包*和*包*将互换使用。但重要的是要注意一些区别:TON存储通过[ADNL](/learn/networking/adnl)通过[RLDP](/learn/networking/rldp)协议传输数据,每个*包*通过其自己的网络覆盖层分发,merkle结构可以存在两个版本 - 用于高效下载的大块和用于高效所有权证明的小块,以及[TON DHT](/learn/networking/ton-dht)网络用于查找节点。 +- *文件包*由*种子信息*和数据块组成。 +- 数据块以*种子头*开头 - 包含文件列表及其名称和大小的结构。文件本身紧随在数据块中。 +- 数据块被划分为块(默认为128 KB),并且在这些块的 SHA256 散列上构建了一个 *merkle 树*(由 TVM cell构成)。这允许构建和验证单个块的 *merkle 证明*,以及通过仅交换修改块的证明来高效重建 *包*。 +- *种子信息*包含*merkle根*: + - 块大小(数据块) + - 块大小列表 + - Hash *merkle树* + - 描述 - 种子创建者指定的任何文本 +- *种子信息*被序列化为TVM cell。此cell的哈希称为*BagID*,它唯一标识*包*。 +- *包元数据*是一个包含*种子信息*和*种子头*的文件。\*这是`.torrent`文件的类比。 + +## 启动存储守护程序和storage-daemon-cli + +### 启动存储守护程序的示例命令: + +`storage-daemon -v 3 -C global.config.json -I :3333 -p 5555 -D storage-db` + +- `-v` - 详细程度(INFO) +- `-C` - 全局网络配置([下载全局配置](/develop/howto/compile#download-global-config)) +- `-I` - ADNL的IP地址和端口 +- `-p` - 控制台接口的TCP端口 +- `-D` - 存储守护程序数据库的目录 + +### storage-daemon-cli管理 + +它的启动方式如下: + +``` +storage-daemon-cli -I 127.0.0.1:5555 -k storage-db/cli-keys/client -p storage-db/cli-keys/server.pub +``` + +- `-I` - 守护程序的IP地址和端口(端口与上面的`-p`参数相同) +- `-k` 和 `-p`- 这是客户端的私钥和服务器的公钥(类似于`validator-engine-console`)。这些密钥在守护程序第一次运行时生成,并放置在`/cli-keys/`中。 + +### 命令列表 + +`storage-daemon-cli`命令列表可以使用`help`命令获取。 + +命令有位置参数和标志。带空格的参数应用引号(`'`或`"`)括起来,也可以转义空格。还可以使用其他转义,例如: + +``` +create filename\ with\ spaces.txt -d "Description\nSecond line of \"description\"\nBackslash: \" +``` + +`--`后的所有参数都是位置参数。可以用它来指定以破折号开头的文件名: + +``` +create -d "Description" -- -filename.txt +``` + +`storage-daemon-cli` 可以通过传递要执行的命令来以非交互模式运行: + +``` +storage-daemon-cli ... -c "add-by-meta m" -c "list --hashes" +``` + +## 添加文件包 + +要下载 *文件包*,您需要知道其 `BagID` 或拥有一个元文件。以下命令可用于添加下载 *包*: + +``` +add-by-hash -d directory +add-by-meta -d directory +``` + +*包* 将被下载到指定的目录。您可以省略它,然后它将被保存到存储守护程序目录中。 + +:::info +哈希以十六进制形式指定(长度 - 64个字符)。 +::: + +通过元文件添加 *包* 时,有关 *包* 的信息将立即可用:大小,描述,文件列表。通过哈希添加时,您将必须等待这些信息被加载。 + +## 管理添加的包 + +- `list` 命令输出 *包* 列表。 +- `list --hashes` 输出带有完整哈希的列表。 + +在所有后续命令中,`` 要么是哈希(十六进制),要么是会话中 *包* 的序号(可以在 `list` 命令中看到的数字)。*包* 的序号不会在 storage-daemon-cli 重启之间保存,并且在非交互模式下不可用。 + +### 方法 + +- `get ` - 输出有关 *包* 的详细信息:描述,大小,下载速度,文件列表。 +- `get-peers ` - 输出对方节点列表。 +- `download-pause `、`download-resume ` - 暂停或恢复下载。 +- `upload-pause `、`upload-resume ` - 暂停或恢复上传。 +- `remove ` - 移除 *包*。`remove --remove-files` 还会删除 *包* 的所有文件。请注意,如果 *包* 保存在内部存储守护程序目录中,无论如何都会删除文件。 + +## 部分下载,优先级 + +:::info +添加 *包* 时,您可以指定您想从中下载哪些文件: +::: + +``` +add-by-hash -d dir --partial file1 file2 file3 +add-by-meta -d dir --partial file1 file2 file3 +``` + +### 优先级 + +*包文件* 中的每个文件都有一个优先级,从 0 到 255 的数字。优先级 0 表示文件不会被下载。`--partial` 标志位将指定的文件设置为优先级 1,其余文件设置为 0。 + +可以使用以下命令更改已添加 *包* 中的优先级: + +- `priority-all ` - 对所有文件。 +- `priority-idx ` - 根据数字为单个文件设置(见 `get` 命令)。 +- `priority-name ` - 根据名称为单个文件设置。 + 即使在文件列表下载之前,也可以设置优先级。 + +## 创建文件包 + +要创建 *包* 并开始分发它,请使用 `create` 命令: + +``` +create +``` + +`` 可以指向单个文件或目录。创建 *包* 时,您可以指定描述: + +``` +create -d "Bag of Files description" +``` + +创建 *包* 后,控制台将显示有关它的详细信息(包括哈希,即通过该哈希标识 *包* 的 `BagID`),并且守护程序将开始分发种子。`create` 的额外选项: + +- `--no-upload` - 守护程序不会向对方节点分发文件。可以使用 `upload-resume` 启动上传。 +- `--copy` - 文件将被复制到存储守护程序的内部目录中。 + +要下载 *包*,其他用户只需知道其哈希即可。您还可以保存种子元文件: + +``` +get-meta +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-faq.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-faq.md new file mode 100644 index 0000000000..1b121164cf --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-faq.md @@ -0,0 +1,55 @@ +# TON存储常见问题解答 + +## 如何将TON域名分配给TON存储的文件包 + +1. [上传](/participate/ton-storage/storage-daemon#creating-a-bag-of-files)文件包到网络并获取Bag ID。 + +2. 在您的电脑上打开Google Chrome浏览器。 + +3. 为Google Chrome安装[TON扩展](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)。 + 您也可以使用[MyTonWallet](https://chrome.google.com/webstore/detail/mytonwallet/fldfpgipfncgndfolcbkdeeknbbbnhcc)。 + +4. 打开扩展,点击“导入钱包”,使用恢复短语导入拥有该域名的钱包。 + +5. 现在在https://dns.ton.org打开您的域名并点击“编辑”。 + +6. 将您的Bag ID复制到“存储”字段并点击“保存”。 + +## 如何在TON存储中托管静态TON网站 + +1. [创建](/participate/ton-storage/storage-daemon#creating-a-bag-of-files)一个文件夹的包,其中包含网站文件,将其上传到网络并获取Bag ID。文件夹必须包含`index.html`文件。 + +2. 在您的电脑上打开Google Chrome浏览器。 + +3. 为Google Chrome安装[TON扩展](https://chrome.google.com/webstore/detail/ton-wallet/nphplpgoakhhjchkkhmiggakijnkhfnd)。 + 您也可以使用[MyTonWallet](https://chrome.google.com/webstore/detail/mytonwallet/fldfpgipfncgndfolcbkdeeknbbbnhcc)。 + +4. 打开扩展,点击“导入钱包”,使用恢复短语导入拥有该域名的钱包。 + +5. 现在在https://dns.ton.org打开您的域名并点击“编辑”。 + +6. 将您的Bag ID复制到“网站”字段,选中“在TON存储中托管”复选框并点击“保存”。 + +## 如何将TON NFT内容迁移到TON存储 + +如果您为您的收藏使用了[标准NFT智能合约](https://github.com/ton-blockchain/token-contract/blob/main/nft/nft-collection-editable.fc),您需要从收藏所有者的钱包向收藏智能合约发送[消息](https://github.com/ton-blockchain/token-contract/blob/2d411595a4f25fba43997a2e140a203c140c728a/nft/nft-collection-editable.fc#L132),带有新的URL前缀。 + +例如,如果URL前缀曾经是`https://mysite/my_collection/`,新前缀将是`tonstorage://my_bag_id/`。 + +## 如何将TON域名分配给TON存储的文件包(低层级) + +您需要将以下值分配给TON域的sha256("storage") DNS记录: + +``` +dns_storage_address#7473 bag_id:uint256 = DNSRecord; +``` + +## 如何在TON存储中托管静态TON网站(低层级) + +[创建](/participate/ton-storage/storage-daemon#creating-a-bag-of-files)一个文件夹的包,其中包含网站文件,将其上传到网络并获取Bag ID。文件夹必须包含`index.html`文件。 + +您需要将以下值分配给您TON域的 sha256("site") DNS 记录: + +``` +dns_storage_address#7473 bag_id:uint256 = DNSRecord; +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-provider.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-provider.md new file mode 100644 index 0000000000..d344ae8031 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/v3/guidelines/web3/ton-storage/storage-provider.md @@ -0,0 +1,150 @@ +# 存储提供商 + +*存储提供商*是一项服务,用于收费存储文件。 + +## 二进制文件 + +您可以从[TON自动构建](https://github.com/ton-blockchain/ton/releases/latest)下载适用于Linux/Windows/MacOS的`storage-daemon`和`storage-daemon-cli`二进制文件。 + +## 从源代码编译 + +您可以使用此[说明](/develop/howto/compile#storage-daemon)从源代码编译`storage-daemon`和`storage-damon-cli`。 + +## 关键概念 + +它由一个智能合约组成,该合约接受存储请求并管理来自客户的支付,以及一个上传和向客户提供文件的应用程序。以下是它的工作原理: + +1. 提供商的所有者启动`storage-daemon`,部署主智能合约,并设置参数。合约的地址与潜在客户共享。 +2. 使用`storage-daemon`,客户端创建一个包含其文件的包并向提供商的智能合约发送特殊的内部消息。 +3. 提供商的智能合约创建一个存储合约来处理这个特定包。 +4. 提供商在区块链中找到请求后,下载包并激活存储合约。 +5. 客户端可以向存储合约转账支付存储费用。为了接收支付,提供商定期向合约提供证明,证明他们仍在存储该包。 +6. 如果存储合约上的资金用尽,合约将被视为非活动状态,提供商不再需要存储该包。客户端可以重新填充合约或检索其文件。 + +:::info +客户端也可以随时通过向存储合约提供所有权证明来检索其文件。合约随后将文件释放给客户端并停用自身。 +::: + +## 智能合约 + +[智能合约源代码](https://github.com/ton-blockchain/ton/tree/master/storage/storage-daemon/smartcont)。 + +## 客户使用提供商 + +要使用存储提供商,您需要知道其智能合约的地址。客户端可以使用`storage-daemon-cli`中的以下命令获取提供商的参数: + +``` +get-provider-params
+``` + +### 提供商的参数: + +- 是否接受新的存储合约。 +- 单个包的最小和最大大小(以字节为单位)。 +- 价格 - 存储费用。以每天每兆字节nanoTON计。 +- 最大间隔 - 提供商应该多久提交一次包存储证明。 + +### 存储请求 + +您需要创建一个包并生成以下命令的消息: + +``` +new-contract-message --query-id 0 --provider
+``` + +### 信息: + +执行此命令可能需要一些时间来处理大型包。消息正文将保存到``(不是整个内部消息)。查询ID可以是0到`2^64-1`的任何数字。消息包含提供商的参数(价格和最大间隔)。这些参数将在执行命令后打印出来,因此应在发送前进行双重检查。如果提供商的所有者更改参数,消息将被拒绝,因此新存储合约的条件将完全符合客户的预期。 + +然后,客户端必须将带有此 body 的消息发送到提供商的地址。如果出现错误,消息将返回给发件人(弹回)。否则,将创建一个新的存储合约,客户端将收到来自它的消息,其中包含[`op=0xbf7bd0c1`](https://github.com/ton-blockchain/ton/tree/testnet/storage/storage-daemon/smartcont/constants.fc#L3)和相同的查询ID。 + +此时,合约尚未激活。一旦提供商下载了包,它将激活存储合约,客户端将收到来自存储合约的[`op=0xd4caedcd`](https://github.com/SpyCheese/ton/blob/tonstorage/storage/storage-daemon/smartcont/constants.fc#L4)消息。 + +存储合约有一个“客户端余额” - 这是客户端转移给合约的资金,尚未支付给提供商。资金以每天每兆字节的速率逐渐从此余额中扣除。初始余额是客户端随创建存储合约的请求一起转移的金额。然后,客户端可以通过对存储合约进行简单转账来补充余额(可以从任何地址进行)。剩余客户端余额可通过[`get_storage_contract_data`](https://github.com/ton-blockchain/ton/tree/testnet/storage/storage-daemon/smartcont/storage-contract.fc#L222) get方法返回,作为第二个值(`balance`)。 + +### 合约可能因以下情况关闭: + +:::info +如果存储合约关闭,客户端将收到带有剩余余额的消息和[`op=0xb6236d63`](https://github.com/ton-blockchain/ton/tree/testnet/storage/storage-daemon/smartcont/constants.fc#L6)。 +::: + +- 在创建后立即,激活前,如果提供商拒绝接受合约(提供商的限额超出或其他错误)。 +- 客户端余额降至0。 +- 提供商可以自愿关闭合约。 +- 客户端可以通过从其地址发送带有[`op=0x79f937ea`](https://github.com/ton-blockchain/ton/tree/testnet/storage/storage-daemon/smartcont/constants.fc#L2)的消息和任何查询ID来自愿关闭合约。 + +## 运行和配置提供商 + +存储提供商是`storage-daemon`的一部分,并由`storage-daemon-cli`管理。需要以`-P`标志启动`storage-daemon`。 + +### 创建主智能合约 + +您可以在`storage-daemon-cli`中执行此操作: + +``` +deploy-provider +``` + +:::info 重要! +您将被要求向指定地址发送1 TON的不可弹回消息以初始化提供商。您可以使用`get-provider-info`命令检查合约是否已创建。 +::: + +默认情况下,合约设置为不接受新的存储合约。在激活它之前,您需要配置提供商。提供商的设置由配置(存储在`storage-daemon`中)和合约参数(存储在区块链中)组成。 + +### 配置: + +- `max contracts` - 同时可以存在的最大存储合约数量。 +- `max total size` - 存储合约中*包*的最大总大小。 + 您可以使用`get-provider-info`查看配置值,并使用以下命令更改它们: + +``` +set-provider-config --max-contracts 100 --max-total-size 100000000000 +``` + +### 合约参数: + +- `accept` - 是否接受新的存储合约。 +- `max file size`、`min file size` - 单个*包*的大小限制。 +- `rate` - 存储成本(以每天每兆字节nanoTON计)。 +- `max span` - 提供商将不得不提交存储证明的频率。 + +您可以使用`get-provider-info`查看参数,并使用以下命令更改它们: + +``` +set-provider-params --accept 1 --rate 1000000000 --max-span 86400 --min-file-size 1024 --max-file-size 1000000000 +``` + +### 值得注意的是 + +注意:在`set-provider-params`命令中,您可以仅指定部分参数。其他参数将从当前参数中获取。由于区块链中的数据不是立即更新的,因此连续几个`set-provider-params`命令可能导致意外结果。 + +建议最初在提供商的余额上放置超过1 TON,以便有足够的资金支付与存储合约相关的手续费。但不要在第一个不可反弹消息中发送太多TON。 + +在将`accept`参数设置为`1`后,智能合约将开始接受客户端的请求并创建存储合约,同时存储守护程序将自动处理它们:下载和分发*包*,生成存储证明。 + +## 进一步使用提供商 + +### 现有存储合约列表 + +``` +get-provider-info --contracts --balances +``` + +每个存储合约的`Client$`和`Contract$`余额都列出来了;差额可以通过`withdraw
`命令提取到主提供商合约。 + +命令`withdraw-all`将从所有至少有`1 TON`可用的合约中提取资金。 + +可以使用`close-contract
`命令关闭任何存储合约。这也将把资金转移到主合约。当客户端余额耗尽时,也会自动发生这种情况。这种情况下,*包*文件将被删除(除非有其他合约使用相同的*包*)。 + +### 转账 + +您可以将资金从主智能合约转移到任何地址(金额以nanoTON指定): + +``` +send-coins
+send-coins
--message "Some message" +``` + +:::info +提供商存储的所有*包*都可以通过命令`list`获得,并且可以像往常一样使用。为避免干扰提供商的运营,请不要删除它们或使用此存储守护程序处理任何其他*包*。 +:::