diff --git a/docs.json b/docs.json index 9a202df42d6..d0e2d6a0400 100644 --- a/docs.json +++ b/docs.json @@ -108,383 +108,923 @@ } ] }, - "tabs": [ + "languages": [ { - "tab": "Docs", - "icon": "book", - "groups": [ + "language": "en", + "tabs": [ { - "group": "Getting Started", - "pages": [ - "getting-started/what-is-near", - "getting-started/create-account", - "getting-started/faucet", - "getting-started/hackathons", - "getting-started/tools-for-ai" - ] - }, - { - "group": "Docs", - "pages": [ + "tab": "Docs", + "icon": "book", + "groups": [ { - "group": "Protocol", - "icon": "layers", + "group": "Getting Started", + "pages": [ + "getting-started/what-is-near", + "getting-started/create-account", + "getting-started/faucet", + "getting-started/hackathons", + "getting-started/tools-for-ai" + ] + }, + { + "group": "Docs", "pages": [ { - "group": "Accounts / Contracts", - "pages": [ - "protocol/accounts-contracts/account-model", - "protocol/accounts-contracts/account-id", - "protocol/accounts-contracts/access-keys" - ] - }, - { - "group": "Transactions", + "group": "Protocol", + "icon": "layers", "pages": [ - "protocol/transactions/index", - "protocol/transactions/transaction-anatomy", - "protocol/transactions/gas", - "protocol/transactions/transaction-execution", - "protocol/transactions/meta-tx" + { + "group": "Accounts / Contracts", + "pages": [ + "protocol/accounts-contracts/account-model", + "protocol/accounts-contracts/account-id", + "protocol/accounts-contracts/access-keys" + ] + }, + { + "group": "Transactions", + "pages": [ + "protocol/transactions/index", + "protocol/transactions/transaction-anatomy", + "protocol/transactions/gas", + "protocol/transactions/transaction-execution", + "protocol/transactions/meta-tx" + ] + }, + { + "group": "Data Flow", + "pages": [ + "protocol/data-flow/near-data-flow", + "protocol/data-flow/token-transfer-flow" + ] + }, + { + "group": "Tokens", + "pages": [ + "protocol/network/tokens", + "protocol/network/token-loss" + ] + }, + { + "group": "Storage", + "pages": [ + "protocol/storage/storage-staking" + ] + }, + { + "group": "Network", + "pages": [ + "protocol/network/architecture", + "protocol/network/validators", + "protocol/network/networks", + "protocol/network/epoch", + "protocol/network/runtime" + ] + } ] }, { - "group": "Data Flow", + "group": "Smart Contracts", + "icon": "file-badge", + "root": "smart-contracts/what-is", "pages": [ - "protocol/data-flow/near-data-flow", - "protocol/data-flow/token-transfer-flow" + "smart-contracts/quickstart", + { + "group": "Concepts", + "pages": [ + { + "group": "Anatomy of a Contract", + "pages": [ + "smart-contracts/anatomy/anatomy", + "smart-contracts/anatomy/functions", + "smart-contracts/anatomy/storage", + "smart-contracts/anatomy/types", + "smart-contracts/anatomy/collections", + "smart-contracts/anatomy/environment", + "smart-contracts/anatomy/actions", + "smart-contracts/anatomy/crosscontract", + "smart-contracts/anatomy/yield-resume" + ] + }, + "smart-contracts/global-contracts", + { + "group": "Deploy, Update & Lock", + "pages": [ + "smart-contracts/release/deploy", + "smart-contracts/release/upgrade", + "smart-contracts/release/lock" + ] + }, + { + "group": "Advanced", + "pages": [ + "smart-contracts/anatomy/best-practices", + "smart-contracts/anatomy/serialization", + "smart-contracts/anatomy/serialization-interface", + "smart-contracts/anatomy/reduce-size", + "smart-contracts/anatomy/reproducible-builds" + ] + }, + { + "group": "Test the Contract", + "pages": [ + "smart-contracts/testing/introduction", + "smart-contracts/testing/unit-test", + "smart-contracts/testing/integration-test" + ] + }, + { + "group": "Security", + "pages": [ + "smart-contracts/security/introduction", + "smart-contracts/security/checklist", + "smart-contracts/security/storage", + "smart-contracts/security/callbacks", + "smart-contracts/security/one_yocto", + "smart-contracts/security/sybil", + "smart-contracts/security/frontrunning", + "smart-contracts/security/reentrancy", + "smart-contracts/security/random" + ] + } + ] + }, + { + "group": "Tutorials", + "pages": [ + "smart-contracts/tutorials/basic-contracts", + { + "group": "Advanced", + "pages": [ + "smart-contracts/tutorials/advanced/near-drop", + "smart-contracts/tutorials/advanced/update" + ] + }, + { + "group": "Cross Contracts", + "pages": [ + "smart-contracts/tutorials/cross-contracts/xcc", + "smart-contracts/tutorials/cross-contracts/advanced-xcc" + ] + }, + { + "group": "Factories", + "pages": [ + "smart-contracts/tutorials/factories/factory", + "smart-contracts/tutorials/factories/global-contracts" + ] + }, + { + "group": "Zero to Hero", + "pages": [ + "smart-contracts/tutorials/zero-to-hero/fts", + "smart-contracts/tutorials/zero-to-hero/nfts", + "smart-contracts/tutorials/zero-to-hero/nfts-js" + ] + } + ] + } ] }, { - "group": "Tokens", + "group": "App Development", + "icon": "terminal", + "root": "web3-apps/what-is", "pages": [ - "protocol/network/tokens", - "protocol/network/token-loss" + "web3-apps/quickstart", + { + "group": "Concepts", + "pages": [ + "web3-apps/concepts/web-login", + "web3-apps/concepts/data-types" + ] + }, + { + "group": "Tutorials", + "pages": [ + "web3-apps/tutorials/wallet-login", + "web3-apps/tutorials/frontend-multiple-contracts", + "web3-apps/backend/backend", + { + "group": "Testing on Localnet", + "pages": [ + "web3-apps/tutorials/localnet/introduction", + "web3-apps/tutorials/localnet/run" + ] + }, + "web3-apps/tutorials/meta-transactions", + { + "group": "Mastering NEAR", + "pages": [ + "web3-apps/tutorials/mastering-near/0-intro", + { + "group": "Smart Contracts 101", + "pages": [ + "web3-apps/tutorials/mastering-near/1.1-basic", + "web3-apps/tutorials/mastering-near/1.2-testing", + "web3-apps/tutorials/mastering-near/1.3-deploy" + ] + }, + { + "group": "Frontends 101", + "pages": [ + "web3-apps/tutorials/mastering-near/2.1-frontend", + "web3-apps/tutorials/mastering-near/2.2-indexing" + ] + }, + { + "group": "Using Primitives", + "pages": [ + "web3-apps/tutorials/mastering-near/3.1-nft", + "web3-apps/tutorials/mastering-near/3.2-ft", + "web3-apps/tutorials/mastering-near/3.3-new-frontend" + ] + }, + "web3-apps/tutorials/mastering-near/4-factory" + ] + } + ] + } ] }, { - "group": "Storage", + "group": "Multi-Chain", + "icon": "link-2", + "root": "chain-abstraction/what-is", "pages": [ - "protocol/storage/storage-staking" + { + "group": "Multi-Chain Accounts", + "pages": [ + "chain-abstraction/chain-signatures", + { + "group": "Tutorials", + "pages": [ + "chain-abstraction/chain-signatures/implementation", + { + "group": "Controlling NEAR Accounts", + "pages": [ + "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction", + "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup", + "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer" + ] + }, + { + "group": "Cross-Chain DAO Governance", + "pages": [ + "chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro", + "chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request", + "chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing", + "chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting" + ] + } + ] + } + ] + }, + "chain-abstraction/intents/overview", + { + "group": "Multi-Chain Bridge", + "pages": [ + "chain-abstraction/omnibridge/overview", + "chain-abstraction/omnibridge/how-it-works", + "chain-abstraction/omnibridge/implementation", + "chain-abstraction/omnibridge/roadmap" + ] + } ] }, { - "group": "Network", - "pages": [ - "protocol/network/architecture", - "protocol/network/validators", - "protocol/network/networks", - "protocol/network/epoch", - "protocol/network/runtime" - ] - } - ] - }, - { - "group": "Smart Contracts", - "icon": "file-badge", - "root": "smart-contracts/what-is", - "pages": [ - "smart-contracts/quickstart", - { - "group": "Concepts", + "group": "Tokens & Primitives", + "icon": "coins", + "root": "primitives/what-is", "pages": [ { - "group": "Anatomy of a Contract", + "group": "Fungible Tokens (FT)", "pages": [ - "smart-contracts/anatomy/anatomy", - "smart-contracts/anatomy/functions", - "smart-contracts/anatomy/storage", - "smart-contracts/anatomy/types", - "smart-contracts/anatomy/collections", - "smart-contracts/anatomy/environment", - "smart-contracts/anatomy/actions", - "smart-contracts/anatomy/crosscontract", - "smart-contracts/anatomy/yield-resume" + "primitives/ft/standard", + "primitives/ft/ft", + "primitives/ft/sdk-contract-tools" ] }, - "smart-contracts/global-contracts", { - "group": "Deploy, Update & Lock", + "group": "Non-Fungible Tokens (NFT)", "pages": [ - "smart-contracts/release/deploy", - "smart-contracts/release/upgrade", - "smart-contracts/release/lock" + "primitives/nft/standard", + "primitives/nft/nft", + "primitives/nft/sdk-contract-tools" ] }, { - "group": "Advanced", + "group": "Linkdrops", "pages": [ - "smart-contracts/anatomy/best-practices", - "smart-contracts/anatomy/serialization", - "smart-contracts/anatomy/serialization-interface", - "smart-contracts/anatomy/reduce-size", - "smart-contracts/anatomy/reproducible-builds" + "primitives/linkdrop/standard", + "primitives/linkdrop/linkdrop" ] }, { - "group": "Test the Contract", + "group": "Staking", "pages": [ - "smart-contracts/testing/introduction", - "smart-contracts/testing/unit-test", - "smart-contracts/testing/integration-test" + "protocol/network/staking" ] }, { - "group": "Security", + "group": "DeFi", "pages": [ - "smart-contracts/security/introduction", - "smart-contracts/security/checklist", - "smart-contracts/security/storage", - "smart-contracts/security/callbacks", - "smart-contracts/security/one_yocto", - "smart-contracts/security/sybil", - "smart-contracts/security/frontrunning", - "smart-contracts/security/reentrancy", - "smart-contracts/security/random" + "primitives/oracles", + "primitives/dao", + "primitives/dex" ] - } - ] - }, - { - "group": "Tutorials", - "pages": [ - "smart-contracts/tutorials/basic-contracts", + }, { - "group": "Advanced", + "group": "Identity", "pages": [ - "smart-contracts/tutorials/advanced/near-drop", - "smart-contracts/tutorials/advanced/update" + "primitives/didnear" ] }, { - "group": "Cross Contracts", + "group": "Lockup Contracts", "pages": [ - "smart-contracts/tutorials/cross-contracts/xcc", - "smart-contracts/tutorials/cross-contracts/advanced-xcc" + "primitives/lockup/introduction", + "primitives/lockup/lockup" ] }, { - "group": "Factories", + "group": "Liquid Staking", "pages": [ - "smart-contracts/tutorials/factories/factory", - "smart-contracts/tutorials/factories/global-contracts" + "primitives/liquid-staking/liquid-staking", + "primitives/liquid-staking/deploy-your-own-contract" ] - }, + } + ] + }, + { + "group": "Data Infrastructure", + "icon": "database", + "root": "data-infrastructure/what-is", + "pages": [ + "data-infrastructure/data-services", + "data-infrastructure/data-api", + "data-infrastructure/big-query", { - "group": "Zero to Hero", + "group": "Indexers", "pages": [ - "smart-contracts/tutorials/zero-to-hero/fts", - "smart-contracts/tutorials/zero-to-hero/nfts", - "smart-contracts/tutorials/zero-to-hero/nfts-js" + "data-infrastructure/indexers", + { + "group": "NEAR Lake Framework", + "pages": [ + "data-infrastructure/near-lake-framework", + "data-infrastructure/tutorials/near-lake-framework", + "data-infrastructure/tutorials/near-lake-state-changes-indexer" + ] + }, + { + "group": "NEAR Indexer", + "pages": [ + "data-infrastructure/near-indexer", + "data-infrastructure/tutorials/near-indexer", + { + "group": "Building a Data Lake", + "pages": [ + "data-infrastructure/tutorials/running-near-lake/run-near-lake", + "data-infrastructure/tutorials/running-near-lake/lake-start-options", + "data-infrastructure/tutorials/running-near-lake/credentials" + ] + } + ] + } ] } ] } ] + } + ] + }, + { + "tab": "Dev ToolKit", + "icon": "code", + "groups": [ + { + "group": "Apps DevKit", + "pages": [ + "tools/near-connect", + "tools/near-api", + "tools/cli" + ] + }, + { + "group": "Contracts DevKit", + "pages": [ + "tools/sdk", + "tools/contracts-list", + "tools/clear-state" + ] + } + ] + }, + { + "tab": "RPC API", + "icon": "server", + "groups": [ + { + "group": "Overview", + "pages": [ + "api/rpc/introduction", + "api/rpc/providers", + "api/rpc/block-chunk", + "api/rpc/gas", + "api/rpc/protocol", + "api/rpc/contracts" + ] + }, + { + "group": "Query", + "openapi": "/openapi.json", + "pages": [ + "POST /query" + ] + }, + { + "group": "Transactions", + "openapi": "/openapi.json", + "pages": [ + "POST /send_tx", + "POST /tx", + "POST /EXPERIMENTAL_tx_status", + "POST /EXPERIMENTAL_receipt", + "POST /broadcast_tx_async", + "POST /broadcast_tx_commit" + ] + }, + { + "group": "Accounts", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_account", + "POST /changes", + "POST /EXPERIMENTAL_changes" + ] + }, + { + "group": "Access Keys", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_access_key_list", + "POST /EXPERIMENTAL_view_access_key" + ] + }, + { + "group": "Smart Contracts", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_code", + "POST /EXPERIMENTAL_view_state", + "POST /EXPERIMENTAL_call_function" + ] }, { - "group": "App Development", - "icon": "terminal", - "root": "web3-apps/what-is", + "group": "Gas Price", + "openapi": "/openapi.json", + "pages": [ + "POST /gas_price" + ] + }, + { + "group": "Blocks & Chunks", + "openapi": "/openapi.json", + "pages": [ + "POST /block", + "POST /chunk", + "POST /block_effects", + "POST /EXPERIMENTAL_changes_in_block" + ] + }, + { + "group": "Network", + "openapi": "/openapi.json", + "pages": [ + "POST /status", + "POST /health", + "POST /network_info", + "POST /validators", + "POST /client_config", + "POST /EXPERIMENTAL_validators_ordered" + ] + }, + { + "group": "Gas & Config", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_protocol_config", + "POST /genesis_config", + "POST /EXPERIMENTAL_genesis_config", + "POST /EXPERIMENTAL_congestion_level" + ] + }, + { + "group": "Light Client", + "openapi": "/openapi.json", + "pages": [ + "POST /light_client_proof", + "POST /next_light_client_block", + "POST /EXPERIMENTAL_light_client_proof", + "POST /EXPERIMENTAL_light_client_block_proof" + ] + }, + { + "group": "Maintenance & Storage", + "openapi": "/openapi.json", + "pages": [ + "POST /maintenance_windows", + "POST /EXPERIMENTAL_maintenance_windows", + "POST /EXPERIMENTAL_split_storage_info" + ] + } + ] + } + ] + }, + { + "language": "zh", + "tabs": [ + { + "tab": "文档", + "icon": "book", + "groups": [ + { + "group": "快速入门", + "pages": [ + "zh/getting-started/what-is-near", + "zh/getting-started/create-account", + "zh/getting-started/faucet", + "zh/getting-started/hackathons", + "zh/getting-started/tools-for-ai" + ] + }, + { + "group": "文档", "pages": [ - "web3-apps/quickstart", { - "group": "Concepts", + "group": "协议", + "icon": "layers", "pages": [ - "web3-apps/concepts/web-login", - "web3-apps/concepts/data-types" + { + "group": "账户 / 合约", + "pages": [ + "zh/protocol/accounts-contracts/account-model", + "zh/protocol/accounts-contracts/account-id", + "zh/protocol/accounts-contracts/access-keys" + ] + }, + { + "group": "交易", + "pages": [ + "zh/protocol/transactions/index", + "zh/protocol/transactions/transaction-anatomy", + "zh/protocol/transactions/gas", + "zh/protocol/transactions/transaction-execution", + "zh/protocol/transactions/meta-tx" + ] + }, + { + "group": "数据流", + "pages": [ + "zh/protocol/data-flow/near-data-flow", + "zh/protocol/data-flow/token-transfer-flow" + ] + }, + { + "group": "代币", + "pages": [ + "zh/protocol/network/tokens", + "zh/protocol/network/token-loss" + ] + }, + { + "group": "存储", + "pages": [ + "zh/protocol/storage/storage-staking" + ] + }, + { + "group": "网络", + "pages": [ + "zh/protocol/network/architecture", + "zh/protocol/network/validators", + "zh/protocol/network/networks", + "zh/protocol/network/epoch", + "zh/protocol/network/runtime" + ] + } ] }, { - "group": "Tutorials", + "group": "智能合约", + "icon": "file-badge", + "root": "zh/smart-contracts/what-is", "pages": [ - "web3-apps/tutorials/wallet-login", - "web3-apps/tutorials/frontend-multiple-contracts", - "web3-apps/backend/backend", + "zh/smart-contracts/quickstart", { - "group": "Testing on Localnet", + "group": "概念", "pages": [ - "web3-apps/tutorials/localnet/introduction", - "web3-apps/tutorials/localnet/run" + { + "group": "合约解析", + "pages": [ + "zh/smart-contracts/anatomy/anatomy", + "zh/smart-contracts/anatomy/functions", + "zh/smart-contracts/anatomy/storage", + "zh/smart-contracts/anatomy/types", + "zh/smart-contracts/anatomy/collections", + "zh/smart-contracts/anatomy/environment", + "zh/smart-contracts/anatomy/actions", + "zh/smart-contracts/anatomy/crosscontract", + "zh/smart-contracts/anatomy/yield-resume" + ] + }, + "zh/smart-contracts/global-contracts", + { + "group": "部署、更新与锁定", + "pages": [ + "zh/smart-contracts/release/deploy", + "zh/smart-contracts/release/upgrade", + "zh/smart-contracts/release/lock" + ] + }, + { + "group": "高级", + "pages": [ + "zh/smart-contracts/anatomy/best-practices", + "zh/smart-contracts/anatomy/serialization", + "zh/smart-contracts/anatomy/serialization-interface", + "zh/smart-contracts/anatomy/reduce-size", + "zh/smart-contracts/anatomy/reproducible-builds" + ] + }, + { + "group": "测试合约", + "pages": [ + "zh/smart-contracts/testing/introduction", + "zh/smart-contracts/testing/unit-test", + "zh/smart-contracts/testing/integration-test" + ] + }, + { + "group": "安全", + "pages": [ + "zh/smart-contracts/security/introduction", + "zh/smart-contracts/security/checklist", + "zh/smart-contracts/security/storage", + "zh/smart-contracts/security/callbacks", + "zh/smart-contracts/security/one_yocto", + "zh/smart-contracts/security/sybil", + "zh/smart-contracts/security/frontrunning", + "zh/smart-contracts/security/reentrancy", + "zh/smart-contracts/security/random" + ] + } ] }, - "web3-apps/tutorials/meta-transactions", { - "group": "Mastering NEAR", + "group": "教程", "pages": [ - "web3-apps/tutorials/mastering-near/0-intro", + "zh/smart-contracts/tutorials/basic-contracts", { - "group": "Smart Contracts 101", + "group": "高级", "pages": [ - "web3-apps/tutorials/mastering-near/1.1-basic", - "web3-apps/tutorials/mastering-near/1.2-testing", - "web3-apps/tutorials/mastering-near/1.3-deploy" + "zh/smart-contracts/tutorials/advanced/near-drop", + "zh/smart-contracts/tutorials/advanced/update" ] }, { - "group": "Frontends 101", + "group": "跨合约", "pages": [ - "web3-apps/tutorials/mastering-near/2.1-frontend", - "web3-apps/tutorials/mastering-near/2.2-indexing" + "zh/smart-contracts/tutorials/cross-contracts/xcc", + "zh/smart-contracts/tutorials/cross-contracts/advanced-xcc" ] }, { - "group": "Using Primitives", + "group": "工厂合约", "pages": [ - "web3-apps/tutorials/mastering-near/3.1-nft", - "web3-apps/tutorials/mastering-near/3.2-ft", - "web3-apps/tutorials/mastering-near/3.3-new-frontend" + "zh/smart-contracts/tutorials/factories/factory", + "zh/smart-contracts/tutorials/factories/global-contracts" ] }, - "web3-apps/tutorials/mastering-near/4-factory" + { + "group": "从零到英雄", + "pages": [ + "zh/smart-contracts/tutorials/zero-to-hero/fts", + "zh/smart-contracts/tutorials/zero-to-hero/nfts", + "zh/smart-contracts/tutorials/zero-to-hero/nfts-js" + ] + } ] } ] - } - ] - }, - { - "group": "Multi-Chain", - "icon": "link-2", - "root": "chain-abstraction/what-is", - "pages": [ + }, { - "group": "Multi-Chain Accounts", + "group": "应用开发", + "icon": "terminal", + "root": "zh/web3-apps/what-is", "pages": [ - "chain-abstraction/chain-signatures", + "zh/web3-apps/quickstart", { - "group": "Tutorials", + "group": "概念", "pages": [ - "chain-abstraction/chain-signatures/implementation", + "zh/web3-apps/concepts/web-login", + "zh/web3-apps/concepts/data-types" + ] + }, + { + "group": "教程", + "pages": [ + "zh/web3-apps/tutorials/wallet-login", + "zh/web3-apps/tutorials/frontend-multiple-contracts", + "zh/web3-apps/backend/backend", { - "group": "Controlling NEAR Accounts", + "group": "在本地网络测试", "pages": [ - "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction", - "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup", - "chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer" + "zh/web3-apps/tutorials/localnet/introduction", + "zh/web3-apps/tutorials/localnet/run" ] }, + "zh/web3-apps/tutorials/meta-transactions", { - "group": "Cross-Chain DAO Governance", + "group": "掌握 NEAR", "pages": [ - "chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro", - "chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request", - "chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing", - "chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting" + "zh/web3-apps/tutorials/mastering-near/0-intro", + { + "group": "智能合约 101", + "pages": [ + "zh/web3-apps/tutorials/mastering-near/1.1-basic", + "zh/web3-apps/tutorials/mastering-near/1.2-testing", + "zh/web3-apps/tutorials/mastering-near/1.3-deploy" + ] + }, + { + "group": "前端 101", + "pages": [ + "zh/web3-apps/tutorials/mastering-near/2.1-frontend", + "zh/web3-apps/tutorials/mastering-near/2.2-indexing" + ] + }, + { + "group": "使用原语", + "pages": [ + "zh/web3-apps/tutorials/mastering-near/3.1-nft", + "zh/web3-apps/tutorials/mastering-near/3.2-ft", + "zh/web3-apps/tutorials/mastering-near/3.3-new-frontend" + ] + }, + "zh/web3-apps/tutorials/mastering-near/4-factory" ] } ] } ] }, - "chain-abstraction/intents/overview", - { - "group": "Multi-Chain Bridge", - "pages": [ - "chain-abstraction/omnibridge/overview", - "chain-abstraction/omnibridge/how-it-works", - "chain-abstraction/omnibridge/implementation", - "chain-abstraction/omnibridge/roadmap" - ] - } - ] - }, - { - "group": "Tokens & Primitives", - "icon": "coins", - "root": "primitives/what-is", - "pages": [ - { - "group": "Fungible Tokens (FT)", - "pages": [ - "primitives/ft/standard", - "primitives/ft/ft", - "primitives/ft/sdk-contract-tools" - ] - }, - { - "group": "Non-Fungible Tokens (NFT)", - "pages": [ - "primitives/nft/standard", - "primitives/nft/nft", - "primitives/nft/sdk-contract-tools" - ] - }, - { - "group": "Linkdrops", - "pages": [ - "primitives/linkdrop/standard", - "primitives/linkdrop/linkdrop" - ] - }, { - "group": "Staking", + "group": "多链", + "icon": "link-2", + "root": "zh/chain-abstraction/what-is", "pages": [ - "protocol/network/staking" - ] - }, - { - "group": "DeFi", - "pages": [ - "primitives/oracles", - "primitives/dao", - "primitives/dex" - ] - }, - { - "group": "Identity", - "pages": [ - "primitives/didnear" - ] - }, - { - "group": "Lockup Contracts", - "pages": [ - "primitives/lockup/introduction", - "primitives/lockup/lockup" + { + "group": "多链账户", + "pages": [ + "zh/chain-abstraction/chain-signatures", + { + "group": "教程", + "pages": [ + "zh/chain-abstraction/chain-signatures/implementation", + { + "group": "控制 NEAR 账户", + "pages": [ + "zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction", + "zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup", + "zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer" + ] + }, + { + "group": "跨链 DAO 治理", + "pages": [ + "zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro", + "zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request", + "zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing", + "zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting" + ] + } + ] + } + ] + }, + "zh/chain-abstraction/intents/overview", + { + "group": "多链桥接", + "pages": [ + "zh/chain-abstraction/omnibridge/overview", + "zh/chain-abstraction/omnibridge/how-it-works", + "zh/chain-abstraction/omnibridge/implementation", + "zh/chain-abstraction/omnibridge/roadmap" + ] + } ] }, { - "group": "Liquid Staking", + "group": "代币与原语", + "icon": "coins", + "root": "zh/primitives/what-is", "pages": [ - "primitives/liquid-staking/liquid-staking", - "primitives/liquid-staking/deploy-your-own-contract" - ] - } - ] - }, - { - "group": "Data Infrastructure", - "icon": "database", - "root": "data-infrastructure/what-is", - "pages": [ - "data-infrastructure/data-services", - "data-infrastructure/data-api", - "data-infrastructure/big-query", - { - "group": "Indexers", - "pages": [ - "data-infrastructure/indexers", { - "group": "NEAR Lake Framework", + "group": "同质化代币 (FT)", + "pages": [ + "zh/primitives/ft/standard", + "zh/primitives/ft/ft", + "zh/primitives/ft/sdk-contract-tools" + ] + }, + { + "group": "非同质化代币 (NFT)", + "pages": [ + "zh/primitives/nft/standard", + "zh/primitives/nft/nft", + "zh/primitives/nft/sdk-contract-tools" + ] + }, + { + "group": "链接空投", + "pages": [ + "zh/primitives/linkdrop/standard", + "zh/primitives/linkdrop/linkdrop" + ] + }, + { + "group": "质押", + "pages": [ + "zh/protocol/network/staking" + ] + }, + { + "group": "DeFi", + "pages": [ + "zh/primitives/oracles", + "zh/primitives/dao", + "zh/primitives/dex" + ] + }, + { + "group": "身份", "pages": [ - "data-infrastructure/near-lake-framework", - "data-infrastructure/tutorials/near-lake-framework", - "data-infrastructure/tutorials/near-lake-state-changes-indexer" + "zh/primitives/didnear" ] }, { - "group": "NEAR Indexer", + "group": "锁仓合约", "pages": [ - "data-infrastructure/near-indexer", - "data-infrastructure/tutorials/near-indexer", + "zh/primitives/lockup/introduction", + "zh/primitives/lockup/lockup" + ] + }, + { + "group": "流动性质押", + "pages": [ + "zh/primitives/liquid-staking/liquid-staking", + "zh/primitives/liquid-staking/deploy-your-own-contract" + ] + } + ] + }, + { + "group": "数据基础设施", + "icon": "database", + "root": "zh/data-infrastructure/what-is", + "pages": [ + "zh/data-infrastructure/data-services", + "zh/data-infrastructure/data-api", + "zh/data-infrastructure/big-query", + { + "group": "索引器", + "pages": [ + "zh/data-infrastructure/indexers", { - "group": "Building a Data Lake", + "group": "NEAR Lake 框架", "pages": [ - "data-infrastructure/tutorials/running-near-lake/run-near-lake", - "data-infrastructure/tutorials/running-near-lake/lake-start-options", - "data-infrastructure/tutorials/running-near-lake/credentials" + "zh/data-infrastructure/near-lake-framework", + "zh/data-infrastructure/tutorials/near-lake-framework", + "zh/data-infrastructure/tutorials/near-lake-state-changes-indexer" + ] + }, + { + "group": "NEAR 索引器", + "pages": [ + "zh/data-infrastructure/near-indexer", + "zh/data-infrastructure/tutorials/near-indexer", + { + "group": "构建数据湖", + "pages": [ + "zh/data-infrastructure/tutorials/running-near-lake/run-near-lake", + "zh/data-infrastructure/tutorials/running-near-lake/lake-start-options", + "zh/data-infrastructure/tutorials/running-near-lake/credentials" + ] + } ] } ] @@ -494,147 +1034,147 @@ ] } ] - } - ] - }, - { - "tab": "Dev ToolKit", - "icon": "code", - "groups": [ - { - "group": "Apps DevKit", - "pages": [ - "tools/near-connect", - "tools/near-api", - "tools/cli" - ] - }, - { - "group": "Contracts DevKit", - "pages": [ - "tools/sdk", - "tools/contracts-list", - "tools/clear-state" - ] - } - ] - }, - { - "tab": "RPC API", - "icon": "server", - "groups": [ - { - "group": "Overview", - "pages": [ - "api/rpc/introduction", - "api/rpc/providers", - "api/rpc/block-chunk", - "api/rpc/gas", - "api/rpc/protocol", - "api/rpc/contracts" - ] - }, - { - "group": "Query", - "openapi": "/openapi.json", - "pages": [ - "POST /query" - ] - }, - { - "group": "Transactions", - "openapi": "/openapi.json", - "pages": [ - "POST /send_tx", - "POST /tx", - "POST /EXPERIMENTAL_tx_status", - "POST /EXPERIMENTAL_receipt", - "POST /broadcast_tx_async", - "POST /broadcast_tx_commit" - ] - }, - { - "group": "Accounts", - "openapi": "/openapi.json", - "pages": [ - "POST /EXPERIMENTAL_view_account", - "POST /changes", - "POST /EXPERIMENTAL_changes" - ] - }, - { - "group": "Access Keys", - "openapi": "/openapi.json", - "pages": [ - "POST /EXPERIMENTAL_view_access_key_list", - "POST /EXPERIMENTAL_view_access_key" - ] - }, - { - "group": "Smart Contracts", - "openapi": "/openapi.json", - "pages": [ - "POST /EXPERIMENTAL_view_code", - "POST /EXPERIMENTAL_view_state", - "POST /EXPERIMENTAL_call_function" - ] - }, - { - "group": "Gas Price", - "openapi": "/openapi.json", - "pages": [ - "POST /gas_price" - ] - }, - { - "group": "Blocks & Chunks", - "openapi": "/openapi.json", - "pages": [ - "POST /block", - "POST /chunk", - "POST /block_effects", - "POST /EXPERIMENTAL_changes_in_block" - ] - }, - { - "group": "Network", - "openapi": "/openapi.json", - "pages": [ - "POST /status", - "POST /health", - "POST /network_info", - "POST /validators", - "POST /client_config", - "POST /EXPERIMENTAL_validators_ordered" - ] - }, - { - "group": "Gas & Config", - "openapi": "/openapi.json", - "pages": [ - "POST /EXPERIMENTAL_protocol_config", - "POST /genesis_config", - "POST /EXPERIMENTAL_genesis_config", - "POST /EXPERIMENTAL_congestion_level" - ] }, { - "group": "Light Client", - "openapi": "/openapi.json", - "pages": [ - "POST /light_client_proof", - "POST /next_light_client_block", - "POST /EXPERIMENTAL_light_client_proof", - "POST /EXPERIMENTAL_light_client_block_proof" + "tab": "开发工具包", + "icon": "code", + "groups": [ + { + "group": "应用开发工具包", + "pages": [ + "zh/tools/near-connect", + "zh/tools/near-api", + "zh/tools/cli" + ] + }, + { + "group": "合约开发工具包", + "pages": [ + "zh/tools/sdk", + "zh/tools/contracts-list", + "zh/tools/clear-state" + ] + } ] }, { - "group": "Maintenance & Storage", - "openapi": "/openapi.json", - "pages": [ - "POST /maintenance_windows", - "POST /EXPERIMENTAL_maintenance_windows", - "POST /EXPERIMENTAL_split_storage_info" + "tab": "RPC API", + "icon": "server", + "groups": [ + { + "group": "概述", + "pages": [ + "zh/api/rpc/introduction", + "zh/api/rpc/providers", + "zh/api/rpc/block-chunk", + "zh/api/rpc/gas", + "zh/api/rpc/protocol", + "zh/api/rpc/contracts" + ] + }, + { + "group": "查询", + "openapi": "/openapi.json", + "pages": [ + "POST /query" + ] + }, + { + "group": "交易", + "openapi": "/openapi.json", + "pages": [ + "POST /send_tx", + "POST /tx", + "POST /EXPERIMENTAL_tx_status", + "POST /EXPERIMENTAL_receipt", + "POST /broadcast_tx_async", + "POST /broadcast_tx_commit" + ] + }, + { + "group": "账户", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_account", + "POST /changes", + "POST /EXPERIMENTAL_changes" + ] + }, + { + "group": "访问密钥", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_access_key_list", + "POST /EXPERIMENTAL_view_access_key" + ] + }, + { + "group": "智能合约", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_view_code", + "POST /EXPERIMENTAL_view_state", + "POST /EXPERIMENTAL_call_function" + ] + }, + { + "group": "Gas 价格", + "openapi": "/openapi.json", + "pages": [ + "POST /gas_price" + ] + }, + { + "group": "区块与分片", + "openapi": "/openapi.json", + "pages": [ + "POST /block", + "POST /chunk", + "POST /block_effects", + "POST /EXPERIMENTAL_changes_in_block" + ] + }, + { + "group": "网络", + "openapi": "/openapi.json", + "pages": [ + "POST /status", + "POST /health", + "POST /network_info", + "POST /validators", + "POST /client_config", + "POST /EXPERIMENTAL_validators_ordered" + ] + }, + { + "group": "Gas 与配置", + "openapi": "/openapi.json", + "pages": [ + "POST /EXPERIMENTAL_protocol_config", + "POST /genesis_config", + "POST /EXPERIMENTAL_genesis_config", + "POST /EXPERIMENTAL_congestion_level" + ] + }, + { + "group": "轻客户端", + "openapi": "/openapi.json", + "pages": [ + "POST /light_client_proof", + "POST /next_light_client_block", + "POST /EXPERIMENTAL_light_client_proof", + "POST /EXPERIMENTAL_light_client_block_proof" + ] + }, + { + "group": "维护与存储", + "openapi": "/openapi.json", + "pages": [ + "POST /maintenance_windows", + "POST /EXPERIMENTAL_maintenance_windows", + "POST /EXPERIMENTAL_split_storage_info" + ] + } ] } ] diff --git a/zh/ai/shade-agents/getting-started/introduction.mdx b/zh/ai/shade-agents/getting-started/introduction.mdx new file mode 100644 index 00000000000..7dcaa0b5888 --- /dev/null +++ b/zh/ai/shade-agents/getting-started/introduction.mdx @@ -0,0 +1,9 @@ +--- +title: Shade Agents(已弃用) +description: Shade Agents(已弃用)。 +hidden: true +--- + +自 2026 年 4 月 19 日起,Shade Agent Framework 将不再作为独立的开发者工具进行维护。其文档已迁移至 Shade Agent 单体仓库,可在此处访问:https://github.com/NearDeFi/shade-agent-framework/tree/main/docs。 + +虽然该框架仍以开源形式存在,但将不再进行积极维护。选择使用该框架的团队应计划自行承担维护、更新及必要修复的责任。 diff --git a/zh/api/rpc/block-chunk.mdx b/zh/api/rpc/block-chunk.mdx new file mode 100644 index 00000000000..b6bf7399142 --- /dev/null +++ b/zh/api/rpc/block-chunk.mdx @@ -0,0 +1,459 @@ +--- +title: 区块 / 分片 +description: "了解如何通过 NEAR RPC API 获取区块和分片的详细信息。" +--- + +RPC API 允许您查询网络并获取特定区块或分片的详细信息。 + +## 快速参考 + +| 方法 | 描述 | 参数 | +|--------|-------------|------------| +| [`block`](#block-details) | 通过高度、哈希或最终性获取区块详情 | `finality` 或 `block_id` | +| [`block_effects`](#block-effects) | 获取特定区块中的变更 | `finality` 或 `block_id` | +| [`chunk`](#chunk-details) | 通过 chunk_id 或 block_id + shard_id 获取分片详情 | `chunk_id` 或 `block_id` + `shard_id` | + +--- + +## 区块详情 + +查询网络并返回指定高度或哈希的区块。您也可以使用 `finality` 参数返回最新区块详情。 + +**注意**:您只能通过特定区块_或_最终性来搜索,不能同时选择两者。 + +- **method**: `block` +- **params**: `finality` 或 `block_id` + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block", + "params": { + "finality": "final" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const response = await provider.block({ + finality: 'final', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block \ + params:='{"finality": "final"}' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block", + "params": { + "block_id": 187310138 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.block({ + blockId: 187310138, + }); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block \ + params:='{"block_id": 187310138}' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block", + "params": { + "block_id": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.block({ + blockId: '6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w', + }); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block \ + params:='{"block_id": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w"}' + ``` + + + + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "author": "node2", + "chunks": [ + { + "chunk_hash": "CzPafxtJmM1FnRoasKWAVhceJzZzkz9RKUBQQ4kY9V1v", + "shard_id": 0, + "gas_limit": 1000000000000000, + "gas_used": 0, + "height_created": 187310138, + "height_included": 187310138 + } + ], + "header": { + "hash": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w", + "height": 187310138, + "timestamp": 1739254177539033760, + "gas_price": "100000000", + "latest_protocol_version": 73 + } + }, + "id": "dontcare" +} +``` + + +--- + +## 区块变更 + +返回指定区块高度或哈希的区块变更。包含 `account_touched`、`access_key_touched`、`data_touched`、`contract_code_touched` 等变更类型。 + +- **method**: `block_effects` +- **params**: `finality` 或 `block_id` + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block_effects", + "params": { + "finality": "final" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const response = await provider.blockChanges({ + finality: 'final', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block_effects \ + params:='{"finality": "final"}' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block_effects", + "params": { + "block_id": 187310138 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.blockChanges({ + blockId: 187310138, + }); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block_effects \ + params:='{"block_id": 187310138}' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "block_effects", + "params": { + "block_id": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.blockChanges({ + blockId: '6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w', + }); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=block_effects \ + params:='{"block_id": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w"}' + ``` + + + + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "block_hash": "6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w", + "block_effects": [ + { "account_id": "account.rpc-examples.testnet", "type": "account_touched" }, + { "account_id": "ping-account.testnet", "type": "account_touched" }, + { "account_id": "account.rpc-examples.testnet", "type": "access_key_touched" }, + { "account_id": "dev2-nsp.testnet", "type": "data_touched" } + ] + }, + "id": "dontcare" +} +``` + + +--- + +## 分片详情 + +返回特定分片的详细信息。您可以先运行[区块详情](#block-details)查询以获取有效的分片哈希。 + +- **method**: `chunk` +- **params**: `chunk_id` 或 `block_id` + `shard_id` + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "chunk", + "params": { + "chunk_id": "CzPafxtJmM1FnRoasKWAVhceJzZzkz9RKUBQQ4kY9V1v" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.chunk( + 'CzPafxtJmM1FnRoasKWAVhceJzZzkz9RKUBQQ4kY9V1v', + ); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=chunk \ + params:='{"chunk_id": "CzPafxtJmM1FnRoasKWAVhceJzZzkz9RKUBQQ4kY9V1v"}' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "chunk", + "params": { + "block_id": 187310138, + "shard_id": 0 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.chunk([187310138, 0]); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=chunk \ + params:='{"block_id": 187310138, "shard_id": 0}' + ``` + + + + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "author": "kiln.pool.f863973.m0", + "header": { + "chunk_hash": "CzPafxtJmM1FnRoasKWAVhceJzZzkz9RKUBQQ4kY9V1v", + "shard_id": 0, + "gas_limit": 1000000000000000, + "gas_used": 0, + "height_created": 187310138, + "height_included": 187310138 + }, + "receipts": [], + "transactions": [ + { + "hash": "J3KbUXF9YPu2eGnbDCACxGvmMDZMdP7acGYhVLHGu9y2", + "signer_id": "account.rpc-examples.testnet", + "receiver_id": "contract.rpc-examples.testnet" + } + ] + }, + "id": "dontcare" +} +``` + + +--- + +## 错误处理 + +| 错误代码 | 描述 | 解决方案 | +|------------|-------------|----------| +| `UNKNOWN_BLOCK` | 区块未找到或已被垃圾回收 | 检查区块有效性;对旧区块使用归档节点 | +| `UNKNOWN_CHUNK` | 数据库中未找到分片 | 验证分片 ID;对旧分片使用归档节点 | +| `INVALID_SHARD_ID` | 分片 ID 不存在 | 为现有分片提供有效的分片 ID | +| `NOT_SYNCED_YET` | 节点仍在同步中 | 等待同步完成或使用其他节点 | +| `PARSE_ERROR` | 请求参数无效 | 检查参数格式和完整性 | +| `INTERNAL_ERROR` | 服务器端问题 | 重试请求或尝试其他 RPC 端点 | diff --git a/zh/api/rpc/contracts.mdx b/zh/api/rpc/contracts.mdx new file mode 100644 index 00000000000..246d282c8ed --- /dev/null +++ b/zh/api/rpc/contracts.mdx @@ -0,0 +1,380 @@ +--- +title: 账户 / 合约 +description: "了解如何使用 NEAR RPC API 查询账户和合约信息。" +--- + +RPC API 允许您查看账户和合约的详细信息,以及执行合约调用。 + +## 快速参考 + +| 方法 | 用途 | +|--------|---------| +| [`view_account`](#view-account) | 获取基本账户信息 | +| [`view_account_changes`](#view-account-changes) | 监控账户状态变更 | +| [`view_code`](#view-contract-code) | 获取已部署合约的 WASM 代码 | +| [`view_state`](#view-contract-state) | 获取合约存储数据 | +| [`data_changes`](#view-contract-state-changes) | 监控合约状态变更 | +| [`contract_code_changes`](#view-contract-code-changes) | 监控合约部署情况 | +| [`call_function`](#call-a-contract-function) | 执行只读合约方法 | + +--- + +## 查看账户 + +返回基本账户信息。 + +- **method**: `query` +- **params**: `request_type: view_account`、`finality` 或 `block_id`、`account_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "view_account", + "finality": "final", + "account_id": "account.rpc-examples.testnet" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const response = await provider.query({ + request_type: 'view_account', + finality: 'final', + account_id: 'account.rpc-examples.testnet', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=query \ + params:='{"request_type":"view_account","finality":"final","account_id":"account.rpc-examples.testnet"}' + ``` + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "amount": "999788200694421800000000", + "block_hash": "56xEo2LorUFVNbkFhCncFSWNiobdp1kzm14nZ47b5JVW", + "block_height": 187440904, + "code_hash": "11111111111111111111111111111111", + "locked": "0", + "storage_usage": 410 + }, + "id": "dontcare" +} +``` + + +--- + +## 查看账户变更 + +返回给定账户中交易产生的账户变更。 + +- **method**: `changes` +- **params**: `changes_type: account_changes`、`account_ids`、`finality` 或 `block_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "changes", + "params": { + "changes_type": "account_changes", + "account_ids": ["contract.rpc-examples.testnet"], + "block_id": 187310139 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://archival-rpc.testnet.near.org" }); + + const response = await provider.accountChanges( + ['contract.rpc-examples.testnet'], + { blockId: 187310139 } + ); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=changes \ + params:='{"changes_type":"account_changes","account_ids":["contract.rpc-examples.testnet"],"block_id":187310139}' + ``` + + + +--- + +## 查看合约代码 + +返回部署到账户的合约代码(Wasm 二进制文件)。返回的代码以 base64 编码。 + +- **method**: `query` +- **params**: `request_type: view_code`、`finality` 或 `block_id`、`account_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "view_code", + "finality": "final", + "account_id": "contract.rpc-examples.testnet" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const response = await provider.query({ + request_type: 'view_code', + finality: 'final', + account_id: 'contract.rpc-examples.testnet', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=query \ + params:='{"request_type":"view_code","finality":"final","account_id":"contract.rpc-examples.testnet"}' + ``` + + + +--- + +## 查看合约状态 + +根据键前缀(base64 编码)返回合约的状态(键值对)。将 `prefix_base64` 传入空字符串以返回完整状态。 + +- **method**: `query` +- **params**: `request_type: view_state`、`finality` 或 `block_id`、`account_id`、`prefix_base64` + + +默认情况下,RPC 节点最多返回 **50kB** 的合约状态。如果合约状态超过此限制,查询将返回错误。要查询更大的状态,请使用归档节点并通过 `prefix_base64` 进行分页查询。 + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "view_state", + "finality": "final", + "account_id": "contract.rpc-examples.testnet", + "prefix_base64": "" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const response = await provider.query({ + request_type: 'view_state', + finality: 'final', + account_id: 'contract.rpc-examples.testnet', + prefix_base64: '', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=query \ + params:='{"request_type":"view_state","finality":"final","account_id":"contract.rpc-examples.testnet","prefix_base64":""}' + ``` + + + +--- + +## 查看合约状态变更 + +根据键前缀(base64 编码)返回合约的状态变更详情。 + +- **method**: `changes` +- **params**: `changes_type: data_changes`、`account_ids`、`key_prefix_base64`、`finality` 或 `block_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "changes", + "params": { + "changes_type": "data_changes", + "account_ids": ["contract.rpc-examples.testnet"], + "key_prefix_base64": "", + "block_id": 187310139 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://archival-rpc.testnet.near.org" }); + + const response = await provider.contractStateChanges( + ['contract.rpc-examples.testnet'], + { blockId: 187310139 }, + '' + ); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=changes \ + params:='{"changes_type":"data_changes","account_ids":["contract.rpc-examples.testnet"],"key_prefix_base64":"","block_id":187310139}' + ``` + + + +--- + +## 查看合约代码变更 + +返回部署合约时产生的代码变更。变更以 base64 编码的 WASM 文件形式返回。 + +- **method**: `changes` +- **params**: `changes_type: contract_code_changes`、`account_ids`、`finality` 或 `block_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "changes", + "params": { + "changes_type": "contract_code_changes", + "account_ids": ["contract.rpc-examples.testnet"], + "block_id": 187309439 + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://archival-rpc.testnet.near.org" }); + + const response = await provider.contractCodeChanges( + ['contract.rpc-examples.testnet'], + { blockId: 187309439 } + ); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=changes \ + params:='{"changes_type":"contract_code_changes","account_ids":["contract.rpc-examples.testnet"],"block_id":187309439}' + ``` + + + +--- + +## 调用合约函数 + +允许您以视图函数(只读)方式调用合约方法。 + +- **method**: `query` +- **params**: `request_type: call_function`、`finality` 或 `block_id`、`account_id`、`method_name`、`args_base64` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "query", + "params": { + "request_type": "call_function", + "finality": "final", + "account_id": "contract.rpc-examples.testnet", + "method_name": "get_greeting", + "args_base64": "" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const response = await provider.query({ + request_type: 'call_function', + finality: 'final', + account_id: 'contract.rpc-examples.testnet', + method_name: 'get_greeting', + args_base64: '', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 id=dontcare method=query \ + params:='{"request_type":"call_function","finality":"final","account_id":"contract.rpc-examples.testnet","method_name":"get_greeting","args_base64":""}' + ``` + + + +--- + +## 错误处理 + +| 错误代码 | 描述 | 解决方案 | +|------------|-------------|----------| +| `UnknownAccount` | 账户不存在 | 检查账户 ID 拼写 | +| `InvalidAccount` | 账户格式无效 | 使用有效的账户 ID(例如 `account.near`) | +| `UnknownBlock` | 区块未找到 | 使用有效的区块哈希或高度 | +| `GarbageCollectedBlock` | 区块过旧 | 使用归档节点或更近期的区块 | +| `NoContractCode` | 未部署合约 | 验证账户是否已部署合约 | +| `MethodNotFound` | 合约方法不存在 | 检查方法名称和合约 ABI | +| `InvalidArgs` | 方法参数无效 | 验证参数格式和编码 | diff --git a/zh/api/rpc/gas.mdx b/zh/api/rpc/gas.mdx new file mode 100644 index 00000000000..ae9087ae4bd --- /dev/null +++ b/zh/api/rpc/gas.mdx @@ -0,0 +1,148 @@ +--- +title: Gas +description: "使用 NEAR RPC API 查询特定区块或哈希的 Gas 价格。" +--- + +RPC API 允许您查询特定区块或哈希的 Gas 价格。 + +## 快速参考 + +| 参数 | 类型 | 描述 | +| --- | --- | --- | +| `block_height` | `number` | 要查询 Gas 价格的特定区块高度 | +| `block_hash` | `string` | 要查询 Gas 价格的特定区块哈希 | +| `null` | `null` | 返回最新区块的 Gas 价格 | + +--- + +## Gas 价格 + +返回特定 `block_height` 或 `block_hash` 的 Gas 价格。使用 `[null]` 将返回最新区块的 Gas 价格。 + +- **method**: `gas_price` +- **params**: `[block_height]`、`["block_hash"]` 或 `[null]` + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "gas_price", + "params": [null] + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const response = await provider.gasPrice(null); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=gas_price \ + params:='[null]' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "gas_price", + "params": [187310138] + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.gasPrice(187310138); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=gas_price \ + params:='[187310138]' + ``` + + + + + + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "gas_price", + "params": ["6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w"] + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://archival-rpc.testnet.near.org", + }); + + const response = await provider.gasPrice( + '6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w', + ); + ``` + + + ```bash + http POST https://archival-rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=gas_price \ + params:='["6RWmTYhXCzjMjoY3Mz1rfFcnBm8E6XeDDbFEPUA4sv1w"]' + ``` + + + + + + + +```json +{ + "jsonrpc": "2.0", + "id": "dontcare", + "result": { + "gas_price": "100000000" + } +} +``` + diff --git a/zh/api/rpc/introduction.mdx b/zh/api/rpc/introduction.mdx new file mode 100644 index 00000000000..397be6e0086 --- /dev/null +++ b/zh/api/rpc/introduction.mdx @@ -0,0 +1,43 @@ +--- +title: "协议调用参考" +description: "与 NEAR 协议交互的工具和方法概述。" +sidebarTitle: "简介" +--- + +NEAR 协议提供了多种与网络交互的方式——从底层 JSON-RPC 调用到可视化浏览器和钱包。 + +## 工具 + + + + 浏览主网和测试网上的交易、账户、区块和合约。 + + + 创建和管理 NEAR 账户、发送代币并与 dApp 交互。 + + + +## RPC API + +NEAR 提供了 JSON-RPC 2.0 API,用于以编程方式访问网络。所有请求均以 `POST` 方式发送至提供商端点,方法名称编码在 URL 路径中。 + +```bash +curl -X POST https://rpc.mainnet.near.org/status \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +请参阅 [RPC 提供商](/api/rpc/providers) 以获取完整的公共端点列表。 + +## 可用方法 + +| 类别 | 方法 | +|----------|---------| +| **交易** | `send_tx`, `tx`, `broadcast_tx_async`, `broadcast_tx_commit` | +| **区块和分片** | `block`, `chunk`, `block_effects`, `changes` | +| **账户和密钥** | `query`(查看账户、代码、状态、访问密钥、调用函数) | +| **网络** | `status`, `health`, `network_info`, `validators` | +| **Gas 和配置** | `gas_price`, `genesis_config`, `EXPERIMENTAL_protocol_config` | +| **轻客户端** | `light_client_proof`, `next_light_client_block` | + +在侧边栏中查阅完整的交互式参考文档,或从 [RPC 提供商](/api/rpc/providers) 开始配置您的端点。 diff --git a/zh/api/rpc/protocol.mdx b/zh/api/rpc/protocol.mdx new file mode 100644 index 00000000000..c49499a7326 --- /dev/null +++ b/zh/api/rpc/protocol.mdx @@ -0,0 +1,155 @@ +--- +title: 协议 +description: "了解如何使用 NEAR RPC API 获取创世配置和当前协议配置。" +--- + +RPC API 允许您获取当前的创世配置和协议配置。 + +## 快速参考 + +| 方法 | 参数 | 描述 | +| --- | --- | --- | +| [`genesis_config`](#genesis-config) | _无_ | 返回当前创世配置 | +| [`EXPERIMENTAL_protocol_config`](#protocol-config) | `finality` 或 `block_id` | 返回最新或特定区块的协议配置 | + +--- + +## 创世配置 + +返回当前创世配置。 + +- **method**: `genesis_config` +- **params**: _无_ + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "genesis_config" + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const response = await provider.experimental_protocolConfig({ + sync_checkpoint: 'genesis', + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=genesis_config + ``` + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "chain_id": "testnet", + "epoch_length": 43200, + "gas_limit": 1000000000000000, + "genesis_height": 42376888, + "genesis_time": "2020-07-31T03:39:42.911378Z", + "min_gas_price": "5000", + "num_block_producer_seats": 200, + "protocol_version": 29, + "total_supply": "2089646653180081825096998107194444" + }, + "id": "dontcare" +} +``` + + +--- + +## 协议配置 + +返回最新的协议配置或特定查询区块的协议配置。适用于查找当前存储和交易成本。 + +- **method**: `EXPERIMENTAL_protocol_config` +- **params**: `finality` 或 `block_id` + + + + ```json + { + "jsonrpc": "2.0", + "id": "dontcare", + "method": "EXPERIMENTAL_protocol_config", + "params": { + "finality": "final" + } + } + ``` + + + ```js + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const response = await provider.experimental_protocolConfig({ + finality: "final" + }); + ``` + + + ```bash + http POST https://rpc.testnet.near.org \ + jsonrpc=2.0 \ + id=dontcare \ + method=EXPERIMENTAL_protocol_config \ + params:='{"finality": "final"}' + ``` + + + + +```json +{ + "jsonrpc": "2.0", + "result": { + "chain_id": "testnet", + "epoch_length": 43200, + "gas_limit": 1000000000000000, + "protocol_version": 73, + "runtime_config": { + "storage_amount_per_byte": "10000000000000000000", + "transaction_costs": { + "action_receipt_creation_config": { + "execution": 108059500000, + "send_not_sir": 108059500000, + "send_sir": 108059500000 + } + } + } + }, + "id": "dontcare" +} +``` + + +--- + +## 最佳实践 + +- 使用 `finality: "final"` 获取最新已确认的协议配置 +- 当需要特定区块的协议配置时,使用具体的 `block_id` +- 对协议配置结果进行缓存,因为其变更频率较低 +- 使用协议配置计算当前的存储和交易成本 diff --git a/zh/api/rpc/providers.mdx b/zh/api/rpc/providers.mdx new file mode 100644 index 00000000000..ad9d5b966e1 --- /dev/null +++ b/zh/api/rpc/providers.mdx @@ -0,0 +1,66 @@ +--- +title: "RPC 提供商" +description: "NEAR 协议的公共 RPC 端点列表。" +--- + +NEAR 协议提供了用于与网络交互的 JSON-RPC API。您可以使用以下任意提供商,也可以自行运行节点。 + +## RPC 提供商 + +FastNear 维护了一个[综合仪表板](https://grafana.fastnear.com/public-dashboards/577b37c6cfe84b2bae23af471d27cade),显示了大多数可用端点的响应时间。 + +### 主网 + +| 提供商 | 端点 | 公共端点 | 归档节点 | 免费套餐 | 付费计划 | +| --- | --- | :---: | :---: | :---: | :---: | +| [FASTNEAR](https://fastnear.com) | `https://free.rpc.fastnear.com` | ✅ | 仅付费 | ✅ | ✅ | +| [NEAR](https://near.org) | `https://archival-rpc.mainnet.near.org` | ✅ | ✅ | 严格限速 | ❌ | +| [1RPC](https://1rpc.io) | `https://1rpc.io/near` | ✅ | ❌ | ✅ | ✅ | +| [All That Node](https://www.allthatnode.com/protocol/near.dsrv) | N/A | ❌ | ✅ | ✅ | ✅ | +| [Ankr](https://www.ankr.com/rpc/near/) | `https://rpc.ankr.com/near` | ❌ | ❌ | ✅ | ✅ | +| [BlockPI Network](https://blockpi.io/chain/near) | `https://near.blockpi.network/v1/rpc/public` | ✅ | ❌ | ✅ | ✅ | +| [dRPC](https://drpc.org/chainlist/near-mainnet-rpc) | `https://near.drpc.org` | ✅ | ❌ | ✅ | ✅ | +| [GetBlock](https://getblock.io/nodes/near/) | `https://go.getblock.io/{api-key}` | ❌ | ❌ | ✅ | ✅ | +| [Intear RPC](https://intea.rs/) | `https://rpc.intea.rs` | ✅ | ❌ | ✅ | ❌ | +| [Lava Network](https://www.lavanet.xyz/lava-public-rpc) | N/A | ❌ | ✅ | ❌ | ✅ | +| [Lavender.Five Nodes](https://www.lavenderfive.com/tools/near/overview) | N/A | ❌ | ❌ | ✅ | ✅ | +| [NodeReal](https://nodereal.io/api-marketplace/near-rpc) | `https://near-mainnet.nodereal.io/v1/{api-key}` | ❌ | ❌ | ✅ | ✅ | +| [NOWNodes](https://nownodes.io/near) | `https://near.nownodes.io/{api-key}` | ❌ | ❌ | ✅ | ✅ | +| [QuickNode](https://www.quicknode.com/chains/near) | N/A | ❌ | ✅ | ✅ | ✅ | +| [Shitzu](https://shitzuapes.xyz/) | `https://rpc.shitzuapes.xyz` | ✅ | ❌ | ✅ | ❌ | +| [Tatum](https://tatum.io/chain/near/) | N/A | ❌ | ❌ | ✅ | ✅ | +| [ZAN](https://zan.top/service/apikeys) | `https://api.zan.top/node/v1/near/mainnet/` | ✅ | ❌ | ✅ | ✅ | +| [Zeeve](https://www.zeeve.io/blockchain-protocols/deploy-near-node/) | N/A | ❌ | ✅ | ❌ | ✅ | + +### 测试网 + +| 提供商 | 端点 | 公共端点 | 归档节点 | 免费套餐 | 付费计划 | +| --- | --- | :---: | :---: | :---: | :---: | +| [FASTNEAR](https://fastnear.com) | `https://test.rpc.fastnear.com` | ✅ | 仅付费 | ✅ | ✅ | +| [NEAR](https://near.org) | `https://archival-rpc.testnet.near.org` | ✅ | ✅ | 严格限速 | ❌ | +| [All That Node](https://www.allthatnode.com/protocol/near.dsrv) | N/A | ❌ | ✅ | ✅ | ✅ | +| [dRPC](https://drpc.org/chainlist/near-testnet-rpc) | `https://near-testnet.drpc.org` | ✅ | ❌ | ✅ | ✅ | +| [Intear RPC](https://intea.rs/) | `https://testnet-rpc.intea.rs` | ✅ | ❌ | ✅ | ❌ | +| [Lava Network](https://www.lavanet.xyz/lava-public-rpc) | N/A | ❌ | ✅ | ❌ | ✅ | +| [QuickNode](https://www.quicknode.com/chains/near) | N/A | ❌ | ✅ | ✅ | ✅ | +| [Tatum](https://tatum.io/chain/near/) | N/A | ❌ | ❌ | ✅ | ✅ | +| [ZAN](https://zan.top/service/apikeys) | `https://api.zan.top/node/ws/v1/near/testnet` | ❌ | ❌ | ✅ | ✅ | +| [Zeeve](https://www.zeeve.io/blockchain-protocols/deploy-near-node/) | N/A | ❌ | ❌ | ❌ | ✅ | + +## 发起请求 + +所有端点均接受 JSON-RPC 2.0 `POST` 请求。NEAR RPC 使用非标准格式,方法名称编码在 URL 路径中而非请求体中。 + +```bash +curl -X POST https://rpc.mainnet.near.org/status \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +## 运行自己的节点 + +要运行本地 RPC 节点,请参阅 [NEAR 节点文档](https://near-nodes.io/rpc/hardware-rpc)。 + + + 测试网代币没有实际价值,可以从 [NEAR 水龙头](/getting-started/faucet) 获取。 + diff --git a/zh/chain-abstraction/chain-signatures.mdx b/zh/chain-abstraction/chain-signatures.mdx new file mode 100644 index 00000000000..8102bf5ff95 --- /dev/null +++ b/zh/chain-abstraction/chain-signatures.mdx @@ -0,0 +1,148 @@ +--- +title: 什么是链签名? +sidebarTitle: 链签名 +description: "了解链签名如何使 NEAR 账户能够使用多方计算(MPC)在多个区块链上签署和执行交易,实现安全的跨链操作。" +--- + +链签名使 NEAR 账户(包括智能合约)能够在众多区块链协议上签署和执行交易。 + +这通过向每个 NEAR 账户赋予对多样化资产、跨链账户和数据的所有权,开启了区块链互操作性的新高度。 + +![chain-signatures](/assets/docs/welcome-pages/chain-signatures-overview.png) +_NEAR 链签名示意图_ + + + +虽然您可以使用 Eddsa 或 Ecdsa 密钥为任何网络签署交易,但每条链签署交易的方式各不相同。 + +我们的示例[实现](./chain-signatures/implementation)向您展示如何为以下网络签署交易:比特币、Solana、Cosmos、XRP、Aptos、Sui 以及 EVM 网络(以太坊、Base、BNB Chain、Avalanche、Polygon、Arbitrum 等)。 + + + +## 优势 + +集成链签名为 Web3 开发者带来诸多好处: + +- 单一账户,多链操作:开发者可以通过一个 NEAR 账户管理与外部区块链的交互。这简化了密钥管理,减少了对多个钱包或地址的需求,提升了用户体验和安全性。 + +- 降低跨链开发的复杂性:链签名消除了跨不同区块链管理交易的复杂性。开发者可以在 NEAR 上编写智能合约,直接为跨链交易签名,减少代码冗余和潜在故障点。 + +- 安全的交易签名:使用[多方计算(MPC)](#multi-party-computation-service),开发者可获得去中心化的多链交易签名流程。这意味着没有任何单一实体控制签名密钥,降低了中心化托管相关的风险。 + + + +请注意,链签名是一种"单向"解决方案,用于签署和执行发生在其他区块链上的出站交易。 +如果您希望访问外部区块链上的状态,应查看 Omnibridge。 + + + +## 互操作性 + +NEAR 的链抽象栈使开发者能够在不同区块链间利用强大的互操作性选项。开发者可以在 NEAR 的可扩展区块链基础设施上执行业务逻辑并部署智能合约,同时原生地控制外部账户(如比特币、以太坊、Base)和资产。 + +这种集成方式将外部区块链资产与 NEAR 的可扩展性相结合,使开发者能够构建提供卓越性能和优化用户体验的原生 dApp。通过结合不同生态系统的优势,NEAR 协议在不牺牲去中心化或可扩展性的前提下,开启了一系列变革性可能性。 + +## 用例 + +链签名架构提供了一种去中心化方法,可从一个 NEAR 账户与多个区块链交互。借助链签名,区块链用例得到了新的拓展。 + +### 比特币 DeFi + +开发者可以在 NEAR 上构建去中心化金融(DeFi)应用程序,如去中心化交易所(DEX)、借贷平台或流动性挖矿协议,同时直接利用比特币流动性。业务逻辑位于 NEAR 上,而 BTC 用于实际支付。 + +#### 示例 + +- 原子交换:促进比特币与其他加密货币之间的无信任即时兑换,提高流动性并降低对手方风险。 +- 接收比特币支付,并原生转账到链签名衍生账户。 +- 使用链签名控制支付流程并执行比特币交易,例如锁定或转移资产。 +- 使用 NEAR 上的智能合约封装业务逻辑,如利息计算、借贷、订单处理、奖励分发和还款。 + +### 跨链 NFT 平台 + +开发者可以在 NEAR 上创建 NFT 市场,用户可以使用外部加密货币(如比特币)购买 NFT。该市场可以处理: + +- 通过链签名和 Omnibridge 进行 BTC 支付。 +- NEAR 上的 NFT 铸造和交易逻辑。(_得益于链签名,NFT 也可以在多个区块链上铸造_) + +--- + +## 工作原理 + +得益于三个要素之间的交互,控制其他区块链平台上的账户及其资产成为可能: + +1. [**衍生路径**](#derivation-paths-one-account-multiple-chains) - 从一个 NEAR 账户确定性地衍生外部地址的方式 +2. [**多链智能合约**](#multichain-smart-contract) - 接收为其他区块链签署交易的请求 +3. [**多方计算服务**](#multi-party-computation-service) - 向合约提供签名的第三方服务 + +![Chain Signatures](/assets/docs/chain-abstraction/chain-abstract-2.webp) +_链签名流程_ + +### 衍生路径:一个账户,多条链 + +链签名使用[加法密钥衍生](https://eprint.iacr.org/2021/1330)(一种从单个主密钥衍生多个子密钥的简单机制)将 NEAR 账户与其他区块链上的地址关联起来。这些密钥使用`衍生路径`(简称`路径`)生成。 + +`衍生路径`只是一个字符串(如 `ethereum-1`、`ethereum-2` 等),它与 NEAR 账户结合,在目标区块链上衍生出唯一地址。 + +例如,我们可以通过使用不同路径从 `example.near` 衍生出多个以太坊地址: + + 1. `example.near` + `ethereum-1` = `0x1b48b83a308ea4beb845db088180dc3389f8aa3b` + 2. `example.near` + `ethereum-2` = `0x99c5d3025dc736541f2d97c3ef3c90de4d221315` + 3. `example.near` + `...` = `0x...` + +值得注意的是,这允许我们发现可以控制的外部账户的**公开地址**。要实际控制外部账户,我们需要向 MPC 服务请求签名。 + + +实际上,外部地址是使用 NEAR 地址(`example.near`)、路径(`ethereum-1`)和 MPC 服务的公钥确定性衍生的 + + +### 多链智能合约 + +已部署的多链智能合约([v1.signer](https://nearblocks.io/address/v1.signer))用于请求为其他区块链上的交易签名。 + +该合约有[一个 `sign` 方法](https://github.com/near/mpc/blob/01f33ed0a2a2c4c24ef49a2f36df3b20aa400816/libs/chain-signatures/contract/src/lib.rs#L242),接受以下三个参数: + + 1. 要为目标区块链签名的 `payload`(交易或交易哈希)。 + 2. 标识用于签署交易的账户的 `path`。 + 3. 作为整数的 `domain_id`,标识用于生成签名的签名方案。目前可以是 `0`(Secp256k1)或 `1`(Ed25519)。 + +例如,用户可以请求使用 `ethereum-1` 账户(**路径**)和 `0`(Secp256k1)作为**域 ID** 来签署`向 0x060f1... 发送 0.1 ETH`(**交易**)。 + +发出请求后,`sign` 方法将暂停执行,等待 [MPC 签名服务](#multi-party-computation-service)签署交易。 + +签名准备就绪后,合约恢复计算并将其返回给用户。该签名是一个有效的已签名交易,可以直接发送到目标区块链执行。 + + +`sign` 方法目前支持 Secp256k1 和 Ed25519 两种签名方案,这使得能够为绝大多数知名区块链签署交易,包括比特币、以太坊、Solana、BNB Chain、Ton 或 Stellar。未来,MPC 参与者可以通过 `vote_add_domains` 方法添加更多签名方案。 + + +### 多方计算服务 + +多方计算(MPC)的本质是使独立方能够在私人信息上进行共享计算,而不向彼此泄露秘密。在实践中,该系统可与区块链平台结合使用,安全地代表用户签署交易,而无需暴露私钥。 + +NEAR 的 MPC 服务由多个独立节点组成,**其中任何一个都无法单独签名**,而是创建**签名份额**,这些份额通过**多轮聚合**来**共同**签署交易。目前,该服务由 8 个独立节点组成。但是,如果足够多的活跃节点投票支持,可以通过 `v1.signer` 智能合约的 `vote_new_parameters` 方法扩展参与节点集合。 + +该服务持续监听签名请求(即用户调用 `v1.signer` 智能合约上的 `sign` 方法),当检测到调用时,MPC 服务会: + + 1. 要求其节点使用由 `path` 标识的账户共同为 `payload` 衍生签名 + 2. 完成后,调用 `v1.signer` 合约存储生成的 `Signature` + + +**自定义 MPC 服务** +通常,MPC 签名服务通过共享主密钥来工作,每次节点加入或离开时都需要重新创建主密钥。 + +NEAR 的 MPC 服务允许节点安全地加入和离开,而无需重新衍生主密钥。 + + + +想了解更多关于 MPC 的数学原理?[**查看这篇精彩文章**](https://www.zellic.io/blog/mpc-from-scratch/)。 + + +--- + +## 总结 + +链签名是一个强大的工具,允许 NEAR 账户控制其他区块链上的账户。这是实现跨链数据和资产真正所有权的根本性一步。 + +对于用户而言,该过程完全**在链上**完成,因为他们只需调用智能合约并等待响应。 + +得益于`衍生路径`,单个 NEAR 账户可以控制不同区块链上的**多个账户**;得益于 MPC 服务,用户可以确信**除他们自己以外,没有人**能为这些账户请求签名。 diff --git a/zh/chain-abstraction/chain-signatures/getting-started.mdx b/zh/chain-abstraction/chain-signatures/getting-started.mdx new file mode 100644 index 00000000000..2eeb8999404 --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/getting-started.mdx @@ -0,0 +1,138 @@ +--- +title: 链签名入门 +description: "了解如何签署和执行跨链交易" +--- + +链签名允许所有 NEAR 账户(包括智能合约)为其他区块链(如比特币、以太坊或 Solana)签署交易。 + +![img](https://pages.near.org/wp-content/uploads/2024/02/acct-abstraction-blog-1.png) + +这一创新利用多方计算(MPC)和分布式节点运营商网络,从任意有效载荷创建联合签名,允许 NEAR 账户控制外部区块链账户。 + +在任何区块链上衍生地址、在比特币上构建 DeFi、用一个合约创建多链应用,或在所有链上实现账户抽象——所有这些无需传统桥接或包装代币。 + +### 链签名与传统桥接的对比 + +| 特性 | 链签名 | 传统桥接 | +|----------------------------|--------------------------------------|---------------------------------| +| **安全模型** | 去中心化 MPC 节点 | 受信任验证者或中继器 | +| **资产类型** | 原生代币(真实 BTC、ETH) | 包装代币(wBTC、wETH) | +| **智能合约支持** | 是,合约可以签署交易 | 通常仅限用户 | +| **支持的链** | 任何区块链 | 仅特定配对 | +| **桥接风险** | 无锁定资金,无桥接漏洞 | 数十亿被锁定,频繁被黑 | +| **用户体验** | 无缝,单一账户 | 需要多个钱包 | + +--- + +## 工作原理 + +当 NEAR 账户——可以是用户或**智能合约**——想要与外部区块链交互时,只需遵循四个简单步骤。 + +![Chain Signatures](/assets/docs/chain-abstraction/chain-abstract-2.webp) +_链签名流程_ + +#### 1. 衍生外部地址 + +链签名使用[衍生路径](../chain-signatures#derivation-paths-one-account-multiple-chains)来表示外部区块链上的账户 + +NEAR 账户的名称和衍生路径用于在外部区块链上数学推导用户的唯一地址 + + + +使用相同的衍生路径,NEAR 账户将始终在外部区块链上衍生出相同的地址 + +请注意,由于外部地址是从 NEAR 账户名称衍生的,其他 NEAR 账户不可能控制同一地址 + + + +#### 2. 创建交易 + +客户端构建要签署的外部交易,具体内容因目标区块链而异 + + + +您可以使用 [Omni Transaction](https://github.com/near/omni-transaction-rs) Rust 库,在 NEAR 合约和 Rust 客户端中轻松为不同区块链(如比特币和以太坊)构建交易。 + + + +#### 3. 请求签名 + +NEAR 账户或合约调用 MPC 智能合约([v1.signer](https://nearblocks.io/address/v1.signer))的 sign 方法来签署交易,并等待我们的 MPC 服务生成签名 + +#### 4. 中继签名 + +签名准备就绪后,客户端使用签名重建已签名的交易,并将其广播到目标区块链 + + + +使用链签名,开发者可以构建具有无缝用户体验的跨链 DeFi 应用程序,消除对传统桥接的需求。这一过程消除了对传统桥接的需求,使开发者能够构建具有无缝用户体验的创新跨链 DeFi 应用程序。 + + + +--- + +## 用例 + +链签名可用于构建利用区块链互操作性的广泛应用程序。以下是一些示例: + +1. **比特币(及其他无智能合约链)上的 DeFi** + * 链签名允许 NEAR 智能合约对比特币上的资产进行编程 + * 构建借贷、兑换、符文发射台、基于通行密钥的比特币钱包等 +2. **链无关应用程序** + * 由于链签名可以为所有区块链签署交易,开发者只需一个智能合约就能支持每一条链 + * 多链 DEX、借贷协议、预言机、衍生品等 +3. **多链账户抽象** + * 用户可以只用 NEAR 账户控制所有链上的资产,并可以在任何链上利用账户抽象功能,包括通行密钥、密钥轮换等 + * 使用多链 Gas 中继器,用户可以使用 USDC 支付任何链上的 Gas 费用 +4. **隐私** + * 链签名可用于以编程方式加密和解密信息 + * 这使得隐私应用程序成为可能,甚至可以根据资产/NFT 的所有权解密信息 + +--- + +## 如何开始? + +1. **熟悉链签名:** + * 了解[链签名的基础知识](/chain-abstraction/chain-signatures)以及它们如何简化区块链交互。 + * 查阅技术[解释文章](https://near.org/blog/unlocking-web3-usability-with-account-aggregation)。 +2. **探索用例:** + * 查看[链签名用例示例](https://pages.near.org/blog/unlocking-multichain-web3-with-near-chain-signatures/),如多链 DAO、多链 NFT 铸造器和比特币符文空投。 +3. **获取资源和文档:** + * 访问[链签名文档](/chain-abstraction/chain-signatures)获取详细的技术信息和代码片段。 + * 查看[链签名 Linktree](https://linktr.ee/chainsignatures) 获取各种资源,包括演示和教程。 +4. **尝试演示:** + * 使用[基于命令行的演示](https://github.com/near-examples/chainsig-script)在比特币、以太坊、狗狗币和瑞波币上衍生账户并发送交易。 + * 查看 [Web 应用演示](https://github.com/near-examples/near-multichain/tree/main)。 +5. **参与社区:** + * 加入 [Telegram 上的链抽象开发者频道](https://t.me/chain\_abstraction),与其他开发者交流并获取支持。 + +--- + +## 常见问题 + +### 支持哪些区块链? +比特币、以太坊、狗狗币、瑞波币、Cosmos 链,以及任何支持 ECDSA 或 EdDSA 签名的区块链。持续添加更多链。 + +### 我需要在桥中锁定代币吗? +不需要。链签名直接控制目标区块链上的原生资产。无包装代币,无桥接风险。 + +### 智能合约可以使用链签名吗? +是的!NEAR 智能合约可以为其他区块链签署交易,实现可编程的跨链应用程序。 + +### MPC 系统有多安全? +MPC 网络在多个节点运营商之间去中心化。没有单个节点可以访问完整的私钥。需要阈值共识才能签名。 + +### 与账户抽象有什么区别? +链签名*实现了*跨所有链的账户抽象。您的 NEAR 账户(带通行密钥、双因素认证等)现在可以控制比特币、以太坊等链上的资产。 + +### 费用是多少? +您可以免费在 NEAR 上签署交易,但外部账户仍需要代币来支付目标区块链上的 Gas 费用。 + +--- + +## 如何深入了解? + +要深入了解链签名及其应用,您可以探索以下资源: + +- **技术博客和深度解析:** 阅读 NEAR 博客上的[高级用例](https://pages.near.org/blog/unlocking-multichain-web3-with-near-chain-signatures)和技术[解释文章](https://near.org/blog/unlocking-web3-usability-with-account-aggregation) +* **社区和支持:** 在 Twitter 等社交媒体平台上参与 NEAR 社区,并参与讨论以了解最新动态 diff --git a/zh/chain-abstraction/chain-signatures/implementation.mdx b/zh/chain-abstraction/chain-signatures/implementation.mdx new file mode 100644 index 00000000000..97cf6ca0cbb --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/implementation.mdx @@ -0,0 +1,411 @@ +--- +title: 实现链签名 +description: "了解如何跨多个区块链签署交易。" +--- + +import { Github } from '/snippets/github.jsx' + +链签名使 NEAR 账户(包括智能合约)能够在众多区块链协议上签署和执行交易。 + +这通过向单个 NEAR 账户赋予对多样化资产、跨链账户和数据的所有权,开启了区块链互操作性的新高度。 + + + +虽然您可以使用 Eddsa 或 Ecdsa 密钥为任何网络签署交易,但每条链签署交易的方式各不相同。 + +我们的示例实现向您展示如何为以下网络签署交易:比特币、Solana、Cosmos、XRP、Aptos、Sui 以及 EVM 网络(以太坊、Base、BNB Chain、Avalanche、Polygon、Arbitrum 等)。 + + + +--- + +## 创建链签名 + +创建链签名共有五个步骤: + +1. [衍生外部地址](#1-deriving-the-foreign-address) - 衍生将在目标区块链上控制的地址。 +2. [创建交易](#2-creating-the-transaction) - 创建要签署的交易或消息。 +3. [请求签名](#3-requesting-the-signature) - 调用 NEAR MPC 合约请求其签署交易。 +4. [格式化签名](#4-formatting-the-signature) - 格式化来自 MPC 合约的签名并将其添加到交易中。 +5. [中继已签名交易](#5-relaying-the-signed-transaction) - 将已签名交易发送到目标链执行。 + +![chain-signatures](/assets/docs/welcome-pages/chain-signatures-overview.png) +_NEAR 链签名示意图_ + +[chainsig.js](https://github.com/NearDeFi/chainsig.js) 库为完成每个步骤提供了便捷的接口。 + + +要在用 Rust 编写的 NEAR 智能合约内构建交易,您可以使用 [Omni Transaction](https://github.com/near/omni-transaction-rs) 库,为不同区块链(如比特币和以太坊)轻松构建交易。 + + + +**MPC 合约** + +在 `mainnet` 和 `testnet` 上都有可用的 [MPC 合约](https://github.com/Near-One/mpc/tree/main/libs/chain-signatures/contract): + +- 主网:`v1.signer` +- 测试网:`v1.signer-prod.testnet` + +MPC 网络由 8 个节点组成。 + + + +--- + +## 链签名合约 + +要与链签名库交互,首先需要实例化一个 `ChainSignaturesContract`。 + + + +`networkId` 和 `contractId` 根据您所在的网络设置为上一节中指定的值。 + +--- + +## 链适配器 + +要与特定链交互,您需要实例化相关的 `chainAdapter`。 + + + + + + EVM 链适配器以 `ChainSignaturesContract` 作为参数,以及由 EVM RPC URL 构建的 `publicClient`。 + + + 要使用不同的 EVM 网络,只需指定所需网络的 RPC URL。 + 该示例演示了与多种 EVM 兼容网络的兼容性,包括以太坊、Base、BNB Chain、Avalanche、Polygon、Arbitrum、zkSync 等。 + 您可以在 [ChainList](https://chainlist.org/?testnets=true) 找到各种网络的 RPC URL。 + + + + + + + + 比特币链适配器以 `ChainSignaturesContract` 作为参数,以及 `network`("mainnet"、"testnet" 或 "regtest")和处理与比特币网络通信的 `btcRpcAdapter`。 + + + + + + + Solana 链适配器以 `ChainSignaturesContract` 作为参数,以及由 Solana RPC URL 构建的 `connection`。如果要使用主网,则需要选择主网 RPC。 + + + + + + + XRP 链适配器以 `ChainSignaturesContract` 作为参数,以及 XRP Ledger 的 `rpcUrl` 和 `rpcURl` 规范。对于测试网,请使用 `https://s.altnet.rippletest.net:51234`。 + + + + + + + SUI 链适配器以 `ChainSignaturesContract` 作为参数,以及由 SUI RPC URL 构建的 SuiClient 的 `connection`。 + + + + + + + Aptos 链适配器以 `ChainSignaturesContract` 作为参数,以及 Aptos 网络的 `nodeUrl` 和 `network` 规范。 + + + + + +--- + +## 1. 衍生外部地址 + +链签名使用[`衍生路径`](../chain-signatures#derivation-paths-one-account-multiple-chains)来表示目标区块链上的账户。要控制的外部地址可以从以下内容确定性地衍生: + +- 调用 MPC 合约的 NEAR 账户(如 `example.near`、`example.testnet` 等) +- 衍生路径(如 `ethereum-1`、`ethereum-2` 等字符串) +- MPC 服务的主公钥(我们使用的库已定义,无需担心)。 + +要衍生地址,请调用 `deriveAddressAndPublicKey` 方法,传递正在衍生地址的 NEAR 账户 ID 和衍生路径。 + + + + + + + + + + + + + + + + 在 Solana 上,您的地址与您的公钥相同。 + + + + + + + + + + + + + + + + + + + + +相同的 NEAR 账户和路径将始终在目标区块链上产生相同的地址。 + +- `example.near` + `ethereum-1` = `0x1b48b83a308ea4beb845db088180dc3389f8aa3b` +- `example.near` + `ethereum-2` = `0x99c5d3025dc736541f2d97c3ef3c90de4d221315` + + +--- + +## 2. 创建交易 + +要构建要签署的交易,请使用 `prepareTransactionForSigning` 方法。 + + + + 构建转移 ETH 的交易非常简单。`value` 是以 Wei 为单位的 ETH 数量,类型为 BigInt(1 ETH = 1018 Wei)。 + + + 该方法返回`未签名交易`和交易`哈希`(也称为`有效载荷`)。 + + + + 构建转移 BTC 的交易非常简单。`value` 是以聪为单位的 BTC 数量,类型为字符串(1 BTC = 100,000,000 聪)。 + + + 该方法返回`未签名交易`和交易`哈希`(也称为`有效载荷`)。 + + + + 构建转移 SOL 的交易非常简单。`value` 是以 lamports 为单位的 SOL 数量,类型为 BigInt(1 SOL = 1,000,000,000 lamports)。 + + + 该方法返回`未签名交易`。 + + + 构建转移 XRP 的交易非常简单。`value` 是以 drops 为单位的 XRP 数量,类型为字符串(1 XRP = 1,000,000 drops)。 + + + 该方法返回`未签名交易`和交易`哈希`(也称为`有效载荷`)。 + + + + 构建转移 SUI 的交易非常简单。`value` 是以 MIST 为单位的 SUI 数量,类型为 BigInt(1 SUI = 1,000,000,000 MIST)。 + + + 该方法返回`未签名交易`和交易`哈希`(也称为`有效载荷`)。 + + + + 构建转移 APT 的交易非常简单。`value` 是以 octas 为单位的 APT 数量,类型为 BigInt(1 APT = 100,000,000 octas)。 + + + 该方法返回`未签名交易`和交易`哈希`(也称为`有效载荷`)。 + + + + + +要调用智能合约上的函数,我们需要合约的 ABI,在我们的仓库中,这定义在 [config.js](https://github.com/near-examples/near-multichain/blob/main/src/config.js#L23-L63) 文件中(可以从 Remix 或使用 Etherscan 获取)。 + +然后使用 `ethers` 库定义一个 `Contract` 对象 + + + +然后构建交易 + + + +这种方法允许您通过编码函数数据并将其包含在交易中来调用智能合约函数。 + + + +--- + +## 3. 请求签名 + +一旦交易创建并准备好签名,通过调用 MPC 智能合约上的 `sign` 发出签名请求。 + +该方法需要四个参数: + + 1. 要为目标区块链签名的 `payloads`(或哈希) + 2. 我们想用于签署交易的账户的衍生 `path` + 3. `keyType`,`Ecdsa` 对应 `Secp256k1` 签名,`Eddsa` 对应 `Ed25519` 签名。 + 4. `signerAccount`,包含正在签名的 `accountId` 和来自 [Near Connect](../../web3-apps/tutorials/wallet-login) 的 `signAndSendTransactions` 函数。 + + + + + + + + + + +对于比特币,在发送单个交易时通常有多个 UTXO 需要签名。我们为每个 UTXO 创建一个 NEAR 交易(调用 MPC 合约上的 `sign`),并逐一发送以由 MPC 签名。然后从每个交易结果中解析每个签名,生成签名数组。 + + + + + 要获取有效载荷,将交易序列化为 `uint8Array`,然后转换为十六进制。 + + + + + + + + + + + + + + + + + +--- + +## 4. 格式化签名 + +一旦从 MPC 返回签名,需要对其进行格式化并添加到交易中以生成已签名的交易。 + + + + + + + + + + +对于比特币,签名数组被添加到交易中以生成完整的已签名交易。 + + + + + + + + + + + + + + + + + + + + + + +--- + +## 5. 中继已签名交易 + +现在我们有了已签名的交易,可以使用 `broadcastTx` 将其中继到目标网络。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +该方法返回一个交易哈希,可用于在区块浏览器上定位该交易。 + + +⭐️ 要深入了解链签名的概念,请参阅[什么是链签名?](../chain-signatures) + +⭐️ 有关在前端中使用链签名的 NEAR 账户的完整示例,请参阅我们的 [Web 应用示例](https://github.com/near-examples/near-multichain)。 + + diff --git a/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction.mdx b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction.mdx new file mode 100644 index 00000000000..4467bfb5d12 --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/0-introduction.mdx @@ -0,0 +1,29 @@ +--- +title: 控制 NEAR 账户 +sidebarTitle: 介绍 +description: "学习如何使用多方计算安全地控制 NEAR 账户。" +--- + +想象一下代表另一个 Near 账户行事——却从不持有其私钥。听起来像是未来的事情?今天就可以实现,得益于[链抽象和多方计算(MPC)](/chain-abstraction/what-is),本教程将一步一步带您完成代表另一个 Near 账户转移 0.1 NEAR 的整个过程。 + +## 工作原理 + +在幕后,智能合约代表用户构建有效的交易,并将其转发给 MPC 合约进行签名。 + +这种方法的关键优势在于没有任何单一实体持有私钥——确保了增强的安全性和去中心化。 + + + +本教程中使用的智能合约的完整源代码,以及用于模拟演练的脚本,可在 [GitHub 仓库](https://github.com/nearuaguild/control-near-account-with-mpc-example)中找到。 + +该合约已在测试网上部署,地址为 `broken-rock.testnet`,如果您不想深入研究自行部署,可以自由使用它。 + + + +## 您将学到什么 + +在本教程中,您将学习如何: + +- [准备 Near 账户](/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup)以通过 MPC 完全控制 +- [构建转移交易并获得签名](/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer)(在智能合约中) +- [将已签名交易广播](/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer)到网络(通过 RPC) diff --git a/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup.mdx b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup.mdx new file mode 100644 index 00000000000..538f1eaca6f --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/1-setup.mdx @@ -0,0 +1,105 @@ +--- +title: 设置由 MPC 控制的 Near 账户 +sidebarTitle: 设置可控 Near 账户 +description: "通过衍生公钥并将其添加为访问密钥,设置由 MPC 安全控制的 NEAR 账户。" +--- + +NEAR 协议的一个强大特性是其灵活的[访问密钥架构](/protocol/accounts-contracts/access-keys)。一个账户可以持有多个访问密钥,每个密钥具有不同级别的权限。这使我们能够在不影响安全性的情况下,将交易签名功能授予外部密钥。 + +要通过 MPC 实现安全的外部控制,我们必须从 MPC 智能合约衍生一个有效的 `secp256k1` 公钥。该密钥属于一个密钥对,将在教程后面用于安全地签署交易。 + +在继续之前,让我们创建一个我们将代表其行事的账户。 + +## 创建账户 + +如果您已经有一个想要通过 MPC 控制的 NEAR 账户,可以跳过此步骤。 +否则,使用以下命令: + +```sh +near account create-account fund-myself +``` + +## 从 MPC 衍生公钥 + +正如您从[链签名文档](/chain-abstraction/chain-signatures#how-it-works)中了解到的,`derivation_path` 是允许生成多个不同密钥对的关键组件。与 `predecessor_id` 结合时,形成一个唯一且安全的对。 + +在我们的示例中,智能合约充当用户和 MPC 之间的代理,`用户账户 <-> 合约代理 <-> 请求签名的合约`,这意味着合约本身在衍生过程中成为 `predecessor`:`predecessor: contract_proxy_address`。 + +如果合约代理直接部署在用户账户上,`用户账户 <-> 请求签名的合约`,则用户账户将成为衍生过程中的 `predecessor`:`predecessor: user_account_address`。 + +要了解 `derivation_path` 的构建方式,让我们查看[智能合约实现](https://github.com/nearuaguild/control-near-account-with-mpc-example/blob/97883851483c05d4c9206afb6efeac02ec9c2541/contract/src/lib.rs#L64-L68): + +```rust +let derivation_path = format!( + "{}-{}", + env::predecessor_account_id().to_string(), + args.signer_id.to_string() +); +``` + +- `args.signer_id` 是我们代表其行事的账户 +- `env::predecessor_account_id().to_string()` 指调用合约的账户,请不要将其与之前提到的 `predecessor_id` 混淆,后者指的是智能合约地址 + +现在我们了解了每个输入的计算方式,让我们将它们组合起来,实际衍生一个公钥。 + + + + +```ts +const contractId = "broken-rock.testnet"; // 智能合约地址 +const adminAccountId = "admin.testnet"; // 签署交易以代表 `controllableAccountId` 行事 +const controllableAccountId = "controllable.testnet"; // 我们将代表其行事的账户 + +// admin.testnet-controllable.testnet +const derivationPath = `${adminAccountId}-${controllableAccountId}`; + +// secp256k1:m2CUiQw9f5nN8sAszkHrbok6apRz7j6LkpFZ6vT6zzxLHh2C54udU9Ue7eRy7FRK42pD796nNSwEdsqXzLb96PR +const derivedPublicKey = await deriveKey(contractId, derivationPath); +``` + + + + +## 向账户添加密钥 + +获得公钥后,最后一步是将其添加到 NEAR 账户。 + + + + +```sh +near account add-key controllable.testnet grant-full-access use-manually-provided-public-key secp256k1:m2CUiQw9f5nN8sAszkHrbok6apRz7j6LkpFZ6vT6zzxLHh2C54udU9Ue7eRy7FRK42pD796nNSwEdsqXzLb96PR network-config testnet sign-with-legacy-keychain send +``` + + + + +```ts +const path = require("node:path"); +const { homedir } = require("node:os"); +const { keyStores, connect } = require("near-api-js"); + +const CREDENTIALS_DIR = ".near-credentials"; +const credentialsPath = path.join(homedir(), CREDENTIALS_DIR); +const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); + +const config = { + keyStore, + networkId: "testnet", + nodeUrl: "https://rpc.testnet.near.org", +}; + +const near = await connect(config); + +const account = await near.account("controllable.testnet"); + +// 使用上一步中的 derivedPublicKey +await account.addKey(derivedPublicKey); +``` + + + + +在我们的示例中,我们将此公钥添加为[**全访问密钥**](/protocol/accounts-contracts/access-keys#full-access-keys),意味着它对账户拥有完全控制权,这允许我们无限制地执行所有交易。但是,也可以将密钥添加为具有有限权限的**函数调用访问密钥**。 + +下一步是代表 `controllable.testnet` 创建、签署并最终发送转移交易。继续阅读[下一章节](/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer)了解操作方法。 diff --git a/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer.mdx b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer.mdx new file mode 100644 index 00000000000..79526367bac --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/controlling-near-accounts/2-transfer.mdx @@ -0,0 +1,89 @@ +--- +title: 代表受控账户转移 Near 代币 +sidebarTitle: 代表可控账户行事 +description: "构建交易参数,请求 MPC 签名,并安全广播已签名的 NEAR 代币转移。" +--- + +在本教程的这一部分,我们将深入探讨如何构建交易参数、如何根据参数从智能合约请求签名,以及如何将已签名的交易广播到网络。 + +## 构建交易参数 + +既然我们已经设置了账户并衍生了公钥,现在是时候构建交易参数了。 + +要使交易有效,我们必须附加 `nonce` 和 `recent_block_hash` ——这些值确保交易的唯一性并防止重放攻击。 + +首先获取 `nonce`: + +```ts +const accessKey = await near.connection.provider.query({ + request_type: "view_access_key", + account_id: controllableAccountId, // "controllable.testnet" + public_key: derivedPublicKey, // 在上一章节中衍生 + finality: "optimistic", +}); + +const nonce = accessKey.nonce; +``` + +然后获取 `recent_block_hash`: + +```ts +const block = await near.connection.provider.block({ + finality: "final", +}); + +const blockHash = block.header.hash; +``` + +现在我们有了所需的一切,让我们构建交易参数: + +```ts +const transactionArgs = { + signer_id: controllableAccountId, // "controllable.testnet" + signer_pk: derivedPublicKey, // 在上一章节中衍生 + nonce: (nonce + 1).toString(), + block_hash: blockHash, +}; +``` + +## 从智能合约请求签名 + +构建交易参数后,下一步是从智能合约请求签名。 + +```ts +const adminAccount = await near.account("admin.testnet"); + +const outcome = await adminAccount.functionCall({ + contractId: contractId, // "broken-rock.testnet" + methodName: "transfer_on_behalf_of", + args: { + args: transactionArgs, + }, + gas: "300000000000000", + attachedDeposit: "100000000000000000000000", // 0.1 NEAR 在大多数情况下足以支付 MPC 费用 +}); +``` + +## 广播交易 + +有了签名,我们现在准备将其发送到网络! + +```ts +// 从结果中获取已签名的交易 +result = providers.getTransactionLastResult(outcome); +const signedTx = new Uint8Array(result); + +// 发送已签名的交易 +const transferOutcome = await near.connection.provider.sendJsonRpc( + "broadcast_tx_commit", + [Buffer.from(signedTx).toString("base64")] +); + +console.log( + `https://nearblocks.io/txns/${transferOutcome.transaction_outcome.id}` +); +``` + +## 最终思考 + +至此,我们已经成功完成了安全地代表另一个 Near 账户行事的完整旅程,并从可控账户向自己转移了 0.1 NEAR。通过遵循此流程,您现在对如何使用多方计算(MPC)在 Near 上管理外部账户有了扎实的理解。 diff --git a/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro.mdx b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro.mdx new file mode 100644 index 00000000000..25629c50489 --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/0-intro.mdx @@ -0,0 +1,70 @@ +--- +title: Near 多链 DAO 治理 +sidebarTitle: 介绍 +description: "了解 Abstract DAO 如何使 NEAR 上的单次投票能够在多个 EVM 链上执行操作。" +--- + +许多多链组织在多个 EVM 链上部署同一智能合约的副本,这些副本需要保持同步。 + +通常,这通过处理多个 DAO 来解决——每条链一个——在每个 DAO 上同时对相同操作进行投票,这一过程不仅耗时,而且成本高昂且容易出错。 + +![multi chain flow](/assets/docs/chain-abstraction/chain-signatures/tutorials/multichain-dao/multi-dao-flow.png) + +为了解决这个问题,我们构建了 [Abstract DAO](https://github.com/nearuaguild/abstract-dao),它使组织能够在 NEAR 上**投票一次**,然后在**多条链**上**执行相同操作**。 + +![multi chain flow](/assets/docs/chain-abstraction/chain-signatures/tutorials/multichain-dao/near-dao-flow.png) + + +Abstract DAO 是一个示例,因此尚未经过审计,使用风险自负。 + + +--- + +### 工作原理 + +[Abstract DAO](https://github.com/nearuaguild/abstract-dao) 充当 NEAR DAO 和 EVM 链之间的中介。 + +投票和执行操作的过程如下: + +1. **创建 EIP-1559 有效载荷**:您创建将在所有链上执行的交易,指定接收地址、nonce、金额和交易数据。仅 Gas 参数未设置,因为它们会因链而异。 + +2. **选择单一授权账户**:作为投票过程的一部分,您的组织选择一个"授权账户",该成员负责设置 Gas 参数,并为所有链生成签名。 + +3. **对请求进行投票**:您的去中心化组织在 NEAR 上进行一次投票以批准此请求。每个成员可以查看交易,然后投票确认或拒绝。 + +4. **生成签名**:一旦请求获得足够的确认,交易即被批准。"授权账户"现在可以与 [Abstract DAO](https://github.com/nearuaguild/abstract-dao) 交互,为任意多个 EVM 兼容链生成签名。 + +其结果是大幅简化的治理流程(一次投票,一次确认)以及以协调方式在多条链上签署和执行交易的能力。 + + +**处理 Gas** +由于 Gas 价格在各链之间差异很大,交易的 Gas 价格在生成签名之前不会设置。 + +这意味着"授权账户"将负责为每个正在签署的交易设置每条链的 Gas 价格。 + + +--- + +## 前提条件 + +要成功完成本教程,您需要安装 [Near CLI](/tools/cli#installation),并大致了解[链签名](/chain-abstraction/chain-signatures/getting-started)的工作原理。 + +--- + +## 下一步 + +准备好开始了吗?让我们跳到第一步,了解 Abstract DAO 合约的工作原理。 + +--- + + +**本文章的版本信息** + +- near-cli: `0.12.0` +- rustc: `1.78.0` +- cargo: `1.80.1` +- cargo-near: `0.6.2` +- rustc: `1.78.0` +- node: `21.6.1` + + diff --git a/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request.mdx b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request.mdx new file mode 100644 index 00000000000..c69f12f191f --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/1-request.mdx @@ -0,0 +1,96 @@ +--- +title: "Abstract DAO:请求" +description: "了解如何在 Abstract DAO 中创建签名请求,以在外部 EVM 链上执行操作。" +--- + +Abstract DAO 合约充当 NEAR 去中心化组织和 EVM 网络之间的中介。为了更好地理解其工作原理,最好先单独使用它,暂时不使用 DAO。 + +让我们一起探索如何在 Abstract DAO 合约中创建请求,该请求随后将用于为外部 EVM 链衍生签名。 + + +我们已在两个环境中部署了 Abstract DAO: + +1. 测试网:`abstract-dao.testnet` +2. 开发版(不稳定):`dev.abstract-dao.testnet` + + +--- + +## 以太坊函数调用 + +假设我们的组织同意更改[我们在 Sepolia 以太坊上部署的一个简单计数器](https://sepolia.etherscan.io/address/0xe2a01146FFfC8432497ae49A7a6cBa5B9Abd71A3)中的一个值,现在想要在 Abstract DAO 中记录此意图。 + +为此,我们将在 Abstract DAO 上调用 **`register_signature_request`** 函数,表示: + +*我们允许 **executor.testnet** 为我们的一个以太坊账户请求签名,将计数器设置为 `1000`*。 + +以下是参数,请先快速浏览,因为我们将逐一介绍: + +```js +{ + "request": { + "derivation_seed_number": 0, + "allowed_account_id": "executor.testnet", + "transaction_payload": { + "to": "0xe2a01146FFfC8432497ae49A7a6cBa5B9Abd71A3", + "nonce": "0", + "function_data": { + "function_abi": { + "inputs": [ + { + "internalType": "uint256", + "name": "_num", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + "arguments": [ + { + "Uint": "A97" + } + ] + } + }, + } +} +``` + +上述调用中有 3 个参数:`derivation_seed_number`、`transaction_payload` 和 `allowed_account_id`,让我们深入了解它们。 + +### 衍生路径 + +参数 `derivation seed number` 将用于衍生我们将请求签名的外部地址,地址的衍生方式如下: + +`DAO 地址` + `衍生路径` + `合约地址` = `EVM 地址` + +例如,如果我们使用地址 `...` 和衍生路径 `0` 注册请求,我们将获得对 `0x...` 账户的控制权。 + + +在[此处](/chain-abstraction/chain-signatures)了解更多关于衍生路径的信息 + + +### 交易有效载荷 + +`transaction_payload` 包含我们想要执行的交易的所有信息,特别是: + + - `to`:交易的接收地址 + - `nonce`:交易 nonce,用于确保唯一性 + - `function_data`:(可选)定义将在接收方合约上调用的函数,包括: + - `function_abi`:被调用函数的 ABI + - `arguments`:函数的输入参数,全部经过 ABI 编码(如整数使用 `base64`) + +关于此 `transaction_payload`,有几点重要提示: + +- **可读的有效载荷:** 这些参数使任何人都能快速理解将在外部执行什么交易。Abstract DAO 设计为透明且易于审计,将创建交易的复杂性抽象化。 + +- **我们正在设置 Nonce:** 通过设置 nonce,我们确保该交易只会有效一次,因为未来的交易需要更高的 `nonce` + +- **我们未设置 GAS:** Gas 价格预计在不同 EVM 之间差异很大,因此为所有网络设置固定的 Gas 数量和价格没有意义,这就是为什么我们使用最后一个参数 `allowed_account` + +### 授权账户 + +在这种情况下,`allowed_account` 将负责生成签名。在生成签名时,该账户还将按每条链设置交易的 `gas_price`。 diff --git a/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing.mdx b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing.mdx new file mode 100644 index 00000000000..970027f6a0e --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/2-signing.mdx @@ -0,0 +1,52 @@ +--- +title: "Abstract DAO:签名" +description: "了解如何为不同链签署 Abstract DAO 请求,并将其中继到目标 EVM 网络。" +--- + +在上一节中,我们看到了如何在 Abstract DAO 合约上注册签名请求。现在,是时候为不同链签署交易并将其中继到目标 EVM 网络了。 + +--- + +## 签署交易 + +要为特定链签署交易,授权账户需要调用 `get_signature` 函数,传递 `request_id`(在上一节中生成)以及完成创建交易签名所需的所有必要信息。 + +例如,要为 Sepolia 测试网签署交易,可以使用以下命令: + +```bash +near contract call-function as-transaction abstract-dao.testnet get_signature json-args '{ + "request_id": 1, + "other_payload": { + "chain_id": 11155111, + "max_fee_per_gas": "1000000000", + "max_priority_fee_per_gas": "100000000" + } +}' prepaid-gas '300.0 Tgas' attached-deposit '0.05 NEAR' sign-as executor.testnet network-config testnet +``` + +请注意,我们现在指定的是 `chain_id`(用于标识目标链)、`max_fee_per_gas` 和 `max_priority_fee_per_gas`(用于设置交易费用)。 + +被授权调用 `get_signature` 的账户——在本例中为 `executor.testnet`——除了按每条链设置 Gas 费用外,不能更改正在签署的交易的任何参数。 + +--- + +## 签名响应 + +签名响应将如下所示: + +```json +{ + "big_r": { + "signature": { + "affine_point": "02D532992B0ECBF67800DB14E04530D9BA55609AD31213CC7ABDB554E8FDA986D3" + }, + "recovery_id": 1, + "s": { + "scalar": "40E81711B8174712B9F34B2540EE0F642802387D15543CBFC84211BB04B83AC3" + } + }, + "tx": "0x02f85083aa36a702850485034c878517a4eb0789829dd094e2a01146fffc8432497ae49a7a6cba5b9abd71a380a460fe47b1000000000000000000000000000000000000000000000000000000000000a84bc0" +} +``` + +如我们所见,这不是已签名的交易本身,而是我们重建它所需的数据。我们创建了一个[脚本](https://github.com/nearuaguild/multichain-dao-scripts)来自动化这个过程,以及中继到目标 EVM 网络。 diff --git a/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting.mdx b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting.mdx new file mode 100644 index 00000000000..e185ad61713 --- /dev/null +++ b/zh/chain-abstraction/chain-signatures/tutorials/multichain-dao/3-voting.mdx @@ -0,0 +1,97 @@ +--- +title: 多签投票 +description: "了解如何部署多签合约并使用 Abstract DAO 对多链提案进行投票。" +--- + +现在我们了解了 Abstract DAO 的工作原理,是时候在组织内使用它了。让我们看看如何部署一个多签合约,用户可以在其中对多链提案进行投票。 + +--- + +## 创建多签合约 + +作为第一步,我们需要创建一个账户并在其上部署多签合约,以便用户可以开始创建提案并对其进行投票。 + + +**部署多签合约** +您可以从 near 仓库下载[已编译的多签合约](https://github.com/near/core-contracts/raw/refs/heads/master/multisig2/res/multisig2.wasm) + + +```bash +near create-account --useFaucet + +near deploy multisig2.wasm +``` + + + 查看 GitHub 仓库以获取有关如何[初始化多签合约](https://github.com/near/core-contracts/tree/master/multisig2)的说明 + + +--- + +## 在多签合约上创建请求 + +要在多链 DAO 治理合约上调用 `register_signature_request`,您需要通过多签合约提交请求。这确保了生成签名的决定由必要的成员确认。 + +```bash +near contract call-function as-transaction multisignature.testnet add_request json-args '{ + "request": { + "receiver_id": "abstract-dao.testnet", + "actions": [ + { + "type": "FunctionCall", + "method_name": "register_signature_request", + "args": { + "request": { + "allowed_account_id": "executor.testnet", + "derivation_seed_number": 0, + "transaction_payload": { + "to": "0xe2a01146FFfC8432497ae49A7a6cBa5B9Abd71A3", + "nonce": "0", + "function_data": { + "function_abi": { + "inputs": [ + { + "internalType": "uint256", + "name": "_num", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + "arguments": [ + { + "Uint": "A97" + } + ] + } + } + } + }, + "gas": "100000000000000", + "deposit": "0.1" + } + ] + } +}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' sign-as executor.testnet network-config testnet +``` + +--- + +## 对请求进行投票 + +一旦请求提交,多签合约的成员有一定时间投票确认或拒绝该请求。每个成员需要使用以下命令投票: + +```bash +near contract call-function as-transaction multisignature.testnet confirm json-args '{"request_id": 1}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' sign-as account.testnet network-config testnet +``` + + +将提供的 `request_id` 替换为创建请求时从响应中获取的值 + + +一旦请求收到足够的确认,它将自动执行。此时,签名请求已成功注册在多链 DAO 治理合约上。 + +现在,授权账户(在请求中指定)可以像我们在[上一节](./2-signing)中看到的那样为交易生成签名。 diff --git a/zh/chain-abstraction/data-availability.mdx b/zh/chain-abstraction/data-availability.mdx new file mode 100644 index 00000000000..7c27214f3f8 --- /dev/null +++ b/zh/chain-abstraction/data-availability.mdx @@ -0,0 +1,147 @@ +--- +title: Rollup 数据可用性 +description: "了解 NEAR 面向 Rollup 的数据可用性层,包括 Blob 存储合约、轻客户端、RPC 节点以及与 Polygon CDK 和 Optimism 等 L2 解决方案的集成。" +--- + +每条单体区块链都有一个数据可用性层。NEAR 的数据可用性(DA)代表了一项开创性举措,旨在将数据可用性层从 NEAR 区块链中模块化,使其可作为其他链上构建者的 Rollup 解决方案使用。 + +该基础设施由智能合约、轻客户端和远程过程调用(RPC)节点组成。智能合约设计用于接受 Blob 数据,这些数据随后经过 NEAR 的共识处理。RPC 节点作为服务节点运行,用户可以向其传输数据。最后,轻客户端作为一个节点运行,Rollup 可通过它验证数据的可用性。 + +- [Blob 存储合约](#blob-store-contract):一个为任意 DA Blob 提供存储的合约。 +- [轻客户端](#light-client):一个具有 DA 功能的 NEAR 无信任链下轻客户端。 +- [RPC 客户端](#da-rpc):向 NEAR 提交数据 Blob 的事实标准客户端。 +- [集成](#integrations):与 L2 Rollup 集成的概念验证工作。 + +NEAR DA 的成本极低,原因在于以下几个关键因素: +- NEAR 每个分片提供大量区块空间,确保高效利用。 +- NEAR 通过避免不必要的密码学膨胀来优化空间,确保分配的每 4MB 恰好等于 4MB 的可用数据。 +- NEAR 的可扩展性无与伦比,可以根据需求增长随时进行重新分片和扩展,而竞争对手则需要构建 Rollup 或侧链,从而始终保持充足且具有成本效益的数据可用性解决方案。 + + +有关最新信息,请查看 [Near DA](https://github.com/near/rollup-data-availability/) 仓库。 + + +--- + +## 系统背景 + +以下概述了我们构建的系统组件及其与外部组件的交互方式。 + +红线表示承诺的外部流向。 +白线表示 Blob 数据的流向。 + + +`Fisherman` 只是一个示例,展示 Rollup 在 DA 初始阶段如何与轻客户端协作,直到我们实现更非交互式的方法(如 KZG)。 + + +```mermaid +C4Context + title NEAR Data Availability System Context + + Enterprise_Boundary(b1, "Ethereum") { + System_Ext(SystemEth, "Ethereum") + + System_Boundary(b2, "Rollup") { + System_Ext(SystemRollup, "Rollup", "Derives blocks, execute transactions, posts commitments & sequence data") + System(SystemNearDa, "NEAR DA Client", "Submits/Gets blob data, creates commitments") + } + BiRel(SystemRollup, SystemEth, "Posts sequences, proofs of execution, DA frame references") + BiRel(SystemRollup, SystemNearDa, "Post batches, retrieves commitments") + Rel(fisherman, SystemEth, "Looks for commitments, posts results") + } + + Enterprise_Boundary(b0, "NEAR") { + + System(SystemLc, "Light Client", "Syncs headers, provides inclusion proofs") + System(SystemNear, "NEAR Protocol", "NEAR validators, archival nodes") + + Rel(SystemLc, SystemNear, "Syncs headers") + Rel(SystemNearDa, SystemNear, "Submits/Gets blob") + + %% This doesn't exist yet + %% System(SystemDas, "Data Availability Sampling", "Data redundancy, retrieval, sample responses") + %% BiRel(SystemDas, SystemLc, "Commitments") + } + + Person_Ext(fisherman, "Fisherman") + Rel(fisherman, SystemLc, "Requests inclusion proofs, validates inclusion proofs") + + + UpdateRelStyle(fisherman, SystemEth, $offsetY="-10" $lineColor="red") + UpdateRelStyle(fisherman, SystemLc, $offsetY="-10", $lineColor="red") + UpdateRelStyle(SystemRollup, SystemEth, $offsetY="-30", $lineColor="white") + UpdateElementStyle(fisherman, $bgColor="grey", $borderColor="red") + + UpdateRelStyle(SystemRollup, SystemNearDa, $offsetX="-200", $lineColor="white", $textColor="white") + UpdateRelStyle(SystemNearDa, SystemNear, $textColor="white", $lineColor="white", $offsetY="10") + UpdateRelStyle(SystemNearLc, SystemNear, $offsetX="30") +``` + +--- + +## [Blob 存储合约](https://github.com/near/rollup-data-availability/tree/main/contracts/blob-store) + +[Blob 存储合约](https://github.com/near/rollup-data-availability/tree/main/contracts/blob-store)为任意 DA Blob 提供存储。实际上,这些"Blob"是来自 Rollup 的排序数据,但可以是任何数据。 + +NEAR 区块链状态存储相当便宜。撰写本文时,100KiB 的统一费用为 1 NEAR。为了进一步降低 NEAR 存储成本,我们不将 Blob 数据存储在区块链状态中。 + +其工作原理是利用 NEAR 围绕收据的共识。当区块生产者处理收据时,存在围绕收据的共识。但是,一旦区块被处理并包含在区块中,收据不再需要用于共识,可以被剪枝。剪枝时间至少为 3 个 NEAR 纪元,每个纪元为 12 小时;实际上约为 5 个纪元。一旦收据被剪枝,归档节点负责保留交易数据,我们甚至可以从索引器获取数据。 + +我们可以通过检查 Blob 承诺来验证 Blob 是以提交格式从生态系统参与者处检索的。Blob 承诺目前需要提高效率并将改进,但它使我们受益,因为任何人都可以用有限的专业知识和工具来构建它。其创建方式是:取一个 Blob,将其分成 256 字节的块,创建一棵 Merkle 树,其中每个叶子是该分片的 Sha-256 哈希。Merkle 树的根即为 Blob 承诺,作为 [transaction_id ++ commitment] 提供给 L1 合约,共 64 字节。 + +这意味着: +- NEAR 验证者围绕 Blob 的提交提供共识 +- 函数输入数据由全节点存储至少三天 +- 归档节点可以存储更长时间的数据 +- 我们不会用超出必要的数据占用共识 +- 索引器也可以使用,该数据目前已被 NEAR 所有主要浏览器索引 +- 承诺长期可用,且承诺创建简单明了 + +--- + +## [轻客户端](https://github.com/near/rollup-data-availability/tree/main/) + +一个具有 DA 功能的 NEAR 无信任链下轻客户端,支持 KZG 承诺、Reed-Solomon 纠删码和存储连接器等功能。 + +轻客户端提供对区块或区块分片内交易和收据包含证明的便捷访问。这对于检查可能未提交的可疑 Blob,或验证 Blob 已提交到 NEAR 非常有用。 + +可以通过以下方式验证 Blob 提交: + +- 从以太坊获取 Blob 承诺的 NEAR 交易 ID。 +- 向轻客户端请求交易 ID 的包含证明,或者如果您希望更精确,可以请求收据 ID;这将为您提供交易/收据的 Merkle 包含证明。 +- 获得包含证明后,您可以请求轻客户端为您验证证明,或高级用户可以手动自行验证。 +- 有了这些知识,Rollup 提供者可以与轻客户端进行高级集成,并围绕其构建证明系统。 + +将来,我们将为轻客户端提供扩展,使得可以为 Blob 承诺和其他数据可用性功能提供非交互式证明。 + +轻客户端也有可能在链上进行区块头同步和包含证明验证,但目前这不是优先事项。 + +--- + +## DA RPC +该客户端是向 NEAR 提交 Blob 的事实标准客户端。这些 crate 允许客户端与 Blob 存储交互。它可以被视为一个"黑盒",Blob 进入,`[transaction_id ++ commitment]` 输出。 + +有多个版本: +- [`da-rpc` crate](https://github.com/near/rollup-data-availability/tree/main/crates/da-rpc) 是 Rust 客户端,任何人在应用程序中偏好 Rust 时都可以使用。 +该客户端的职责是为与 NEAR DA 的交互提供简单接口。 +- [`da-rpc-sys` crate](https://github.com/near/rollup-data-availability/tree/main/crates/da-rpc-sys) 是供非 Rust 应用程序使用的 FFI 客户端绑定。它调用 `da-rpc` 与 Blob 存储交互,并附加一些黑盒功能来处理指针管理等问题。 +- [`da-rpc-go` 包](https://github.com/near/rollup-data-availability/tree/main/gopkg/da-rpc) 是供非 Rust 应用程序使用的 Go 客户端绑定,它调用 `da-rpc-sys`,为绑定的轻松交互提供另一个应用程序级层。 + + +另请参阅[该图](https://github.com/near/near-cli-rs/blob/main/docs/da_rpc_client.md) + + +--- + +## 集成 + +我们已开发了一些与 L2 Rollup 集成的概念验证工作: + +- [CDK 栈](https://github.com/firatNEAR/cdk-validium-node/tree/near):我们已与 Polygon CDK 栈集成。使用 Sequence Sender 向 NEAR 提交数据。 +- [Optimism](https://github.com/near/optimism):我们已与 Optimism OP 栈集成。使用 `Batcher` 向 NEAR 提交数据,并使用提议者向以太坊提交 NEAR 承诺数据。 + +- [Arbitrum Nitro](https://github.com/near/nitro):我们已在 DAC daserver 中集成了一个小型插件。这与我们的 HTTP 侧车类似,提供了对 NEAR DA 的高度模块化集成,同时支持 Arbitrum DAC。 + + +将来,`Arbitrum Nitro` 集成可能是支持 NEAR DA 的最简单方式,因为它作为独立侧车运行,可以按需扩展。这也意味着 DAC 可以选择加入或退出 NEAR DA,降低其基础设施负担。通过这种方式,DAC 委员会成员只需拥有一个"哑"签名服务,存储由 NEAR 支持。 + diff --git a/zh/chain-abstraction/fastauth-sdk.mdx b/zh/chain-abstraction/fastauth-sdk.mdx new file mode 100644 index 00000000000..ad0445a9c20 --- /dev/null +++ b/zh/chain-abstraction/fastauth-sdk.mdx @@ -0,0 +1,13 @@ +--- +title: FastAuth SDK +sidebarTitle: FastAuth(邮箱登录) +description: "了解 FastAuth SDK,这是一个密钥管理系统,使用户能够通过电子邮件地址恢复或注册 NEAR 账户。" +--- + +FastAuth 是一个密钥管理系统,允许用户使用其**电子邮件地址****恢复或注册** NEAR 账户。 + + + +当前版本的 FastAuth 已弃用;使用 MPC 和 Auth0 的改进版工具正在开发中。请关注后续更新。 + + diff --git a/zh/chain-abstraction/intents/overview.mdx b/zh/chain-abstraction/intents/overview.mdx new file mode 100644 index 00000000000..6528379c6c1 --- /dev/null +++ b/zh/chain-abstraction/intents/overview.mdx @@ -0,0 +1,52 @@ +--- +title: NEAR 意图 +sidebarTitle: 多链兑换 +description: "了解意图协议的工作原理" +--- + +NEAR 意图是一种多链交易协议,用户指定他们想要的结果,让第三方竞相提供最优解决方案。这适用于从代币兑换到外卖配送的一切场景,在加密货币和传统服务之间创建了一个通用市场。 + +![NEAR Intents Overview](/assets/docs/chain-abstraction/intents-overview.png) + + + 阅读官方文档,了解有关 NEAR 意图及其使用方法的更多信息 + + +--- + +## 工作原理 + + + +用户或 AI 智能体表达期望的结果(例如:将代币 A 换成代币 B),并将意图广播给做市商(也称为解决者)网络。 + + + + +由做市商(即解决者)组成的链下去中心化网络竞相以最优方式履行请求。当网络找到最佳解决方案时,它会以报价的形式呈现给原始用户/智能体进行审批。 + + + +如果接受做市商的报价,则通过调用 NEAR 协议上的"验证者"智能合约来执行意图。该合约安全地验证并结算最终交易。 + + + + +--- + +## 资源 + + + + 阅读官方文档,了解有关 NEAR 意图的更多信息。 + + + 加入 Telegram 开发者支持频道。 + + + 探索使用 1Click API 的简单集成示例。 + + + 试用展示代币兑换的实时演示应用程序。 + + diff --git a/zh/chain-abstraction/omnibridge/how-it-works.mdx b/zh/chain-abstraction/omnibridge/how-it-works.mdx new file mode 100644 index 00000000000..1c73eb44bc1 --- /dev/null +++ b/zh/chain-abstraction/omnibridge/how-it-works.mdx @@ -0,0 +1,79 @@ +--- +sidebarTitle: 工作原理 +title: Omni Bridge 工作原理 +description: "了解 Omni Bridge 如何使用链签名实现跨链转移。" +--- + +走向真正无需信任的跨链通信之路迈出了重要一步,当时 NEAR 团队[创建了与以太坊的首个无需信任桥](https://near.org/blog/the-rainbow-bridge-is-live)(Rainbow Bridge)。这一开创性成就证明了完全无需信任的跨链通信是可能的,标志着向链抽象愿景迈出了关键一步。然而,这种方法依赖于在以太坊上直接实现 NEAR 轻客户端——本质上需要以太坊理解并验证 NEAR 复杂的区块链规则。 + +Omni Bridge 使用链签名引入了一个更优雅的解决方案。它不在每个目标链上运行轻客户端,而是利用链签名的 MPC 服务在不产生轻客户端验证开销的情况下实现安全的跨链消息验证。这种新方法将验证时间从数小时缩短到数分钟,同时显著降低了所有支持链上的 Gas 成本。 + +### 轻客户端的问题 + +轻客户端是一种智能合约,允许一条区块链验证另一条区块链上发生的事件。就 Rainbow Bridge 而言,以太坊轻客户端需要跟踪 NEAR 的区块、验证其验证者的签名并确认交易。这带来了重大技术挑战:需要存储两周的以太坊区块数据,维护 NEAR 验证者及其质押的最新列表,最关键的是验证 NEAR 的 ED25519 签名——这是以太坊原本不支持的过程。这种验证计算成本高昂,使整个过程变得缓慢、昂贵,最终成为主要瓶颈。 + +例如,使用 Rainbow Bridge 时,由于 4 小时的挑战期和以太坊高 Gas 成本驱动的区块提交间隔,NEAR 到以太坊的交易需要 4 到 8 小时。更重要的是,当连接到多条链时,这种方法变得越来越不实际,因为每条链都需要其自己的轻客户端实现。某些链(如比特币)甚至不支持智能合约,使得实现 NEAR 轻客户端在技术上不可能。 + +虽然我们仍然需要在 NEAR 上支持不同网络的轻客户端(这更容易实现),但验证外部链上的 NEAR 状态需要不同的方法。 + +### 代币标准与跨链通信 + +在探索链签名如何解决这些问题之前,了解代币在 NEAR 上的工作方式非常重要。[NEP-141](https://github.com/near/NEPs/tree/master/neps/nep-0141.md),NEAR 的可替换代币标准,有一个将其与以太坊 ERC-20 区分开来的关键特性:通过转移并调用功能实现内置的可组合性。 + +当在 NEAR 上使用 `ft_transfer_call` 进行代币转移时,代币合约首先转移代币,然后自动调用接收方合约上指定的 `ft_on_transfer` 方法。虽然这些操作在同一交易中按顺序发生,但接收方合约有能力拒绝转移,导致代币被退还。这种原子行为通过防止部分执行状态,确保了桥接操作的完整性和安全性。 + +有关更多信息,请参阅[可替换代币](../../primitives/ft/ft)。 + +## 进入链签名时代 + +链签名不是在目标链上维护复杂的轻客户端,而是基于三个核心组件引入了根本不同的方法: + +1. **确定性地址衍生** - 每个 NEAR 账户都可以通过衍生路径在其他链上数学推导地址。这不仅仅是一种映射——而是一种密码学衍生,确保同一个 NEAR 账户始终控制所有支持链上的同一组地址。 + +2. **桥接智能合约** - NEAR 上的中心合约与 MPC 网络协调,为跨链交易生成安全签名。该合约处理代币锁定并为出站转移请求签名 + +3. **MPC 服务** - 一个去中心化节点网络,无需重建完整私钥即可联合签署交易。安全性来自阈值密码学——没有单个节点或小组节点能够单独创建有效签名。 + +## 综合运用 + +如我们所了解的,链签名从根本上改变了跨链消息的验证机制。在实践中,这意味着: + +轻客户端方法需要目标链验证来自 NEAR 验证者的 ED25519 签名。链签名用单个 MPC 签名验证替代了这一过程。目标链只需使用其原生签名验证方案(EVM 链通常为 ECDSA)验证一个签名。 + +NEP-141 的交易保证处理代币锁定的安全性。转移在**单个交易**中创建两个操作: +1. 锁定代币并记录转移状态 +2. 为目标链请求 MPC 签名 + +Locker 合约向 MPC 网络请求签名,MPC 网络随后为有效转移请求生成签名。这替代了挑战期的需求——安全性来自 MPC 阈值保证,而非乐观假设。 + +添加新链只需实现三个标准组件: +1. 链特定地址衍生 +2. MPC 签名验证(或比特币等链的交易签名) +3. 桥接合约部署 +4. 向 NEAR 转回的通信路径(目前对较新的链使用 Wormhole) + +虽然我们仍然需要 NEAR 上的轻客户端来接收来自其他链的转移,但这种方法使支持更广泛的链成为可能,而无需在每个目标链上实现复杂的验证逻辑。 + +```mermaid +sequenceDiagram + title: High-Level Overview of NEAR to External Chain Transfer + participant User as User Account + participant Bridge as Omni Bridge
Locker Contract + participant MPC as MPC Service
(off-chain) + participant Other as Destination Chain + + note over User, Bridge: NEAR Blockchain + + User->>Bridge:1. Submits transfer
token request + Bridge->>Bridge: 2. Locks tokens + Bridge->>MPC: 3. Request signature + MPC->>MPC: 3. Signs message + MPC-->>Bridge: 4. Return signed msg + Bridge->>Other: 5. Broadcast signed msg to destination chain + Other->>Other: 4. Mint/release tokens +``` + +要开始使用 Omni Bridge 进行构建,请参阅: + +- [Bridge SDK JS](https://github.com/near-one/bridge-sdk-js) JavaScript 版 Omni Bridge 实现 +- [Bridge SDK Rust](https://github.com/near-one/bridge-sdk-rs) Rust 版 Omni Bridge 实现 diff --git a/zh/chain-abstraction/omnibridge/implementation.mdx b/zh/chain-abstraction/omnibridge/implementation.mdx new file mode 100644 index 00000000000..3600fb69be4 --- /dev/null +++ b/zh/chain-abstraction/omnibridge/implementation.mdx @@ -0,0 +1,291 @@ +--- +sidebarTitle: 实现细节 +title: 实现细节 +description: "探索 Omni Bridge 的技术架构" +--- + +Omni Bridge 是一个复杂的跨链桥基础设施,能够在 NEAR 协议和各种其他区块链网络之间实现安全高效的代币转移。本文档对桥的架构提供详细的技术概述,涵盖其核心组件、安全模型和操作机制。通过利用多方计算(MPC)、链特定轻客户端和无许可中继器网络的组合,该桥在安全性、去中心化和用户体验之间实现了稳健的平衡。 + +有关参考代码实现,请参阅: + +- [Bridge SDK JS](https://github.com/near-one/bridge-sdk-js) JavaScript 版 Omni Bridge 实现 +- [Bridge SDK Rust](https://github.com/near-one/bridge-sdk-rs) Rust 版 Omni Bridge 实现 + +--- + +## 桥接代币工厂模式 + +Omni Bridge 的核心是 NEAR 上的桥接代币工厂合约,它既充当代币工厂又充当托管方。这个统一合约处理来自源链的原生代币和工厂本身创建的桥接代币。与拥有独立合约相比,这种设计简化了维护并降低了复杂性。 + +该合约有几项关键职责: + +### 对于桥接代币(原本来自其他链的代币): + +* 首次桥接代币时部署新的代币合约 +* 收到有效转移消息时铸造代币 +* 发起向源链的转回时销毁代币 + +### 对于原生 NEAR 代币: + +* 在转移期间作为托管方锁定代币 +* 收到有效转移消息时释放代币 +* 通过 NEP-141 标准管理代币操作 + +### 转移生命周期 + +转移的生命周期包括几个状态,以下显示的是原生 NEAR 代币从 NEAR 到以太坊的转移: + +```mermaid +stateDiagram-v2 + [*] --> Initiated: User calls transfer + Initiated --> Locked: Tokens locked in bridge + Locked --> Signed: MPC signature generated + Signed --> Completed: Tokens released on Ethereum + Completed --> [*] +``` + +--- + +## 消息签名与验证 + +对于大多数链,桥使用基于有效载荷的消息签名系统(比特币是一个值得注意的例外,需要完整的交易签名)。 + +### 消息类型 + +桥支持几种类型的签名消息: + +* **转移消息** + * 发起消息 + * 完成消息 +* **代币消息** + * 部署消息 + * 元数据更新消息 + +### 有效载荷结构 + +消息使用 Borsh 序列化编码,包含: + +| 组件 | 描述 | +|-----------|-------------| +| 消息类型 | 消息类别的标识符 | +| 链信息 | 链 ID 和相关地址 | +| 操作数据 | 金额、接收方、费用等 | + +### 签名流程 + +1. NEAR 合约创建并存储消息有效载荷 +2. MPC 网络观察者检测有效有效载荷 +3. 节点联合签署有效载荷 +4. 在目标链上验证签名 + + +**主要优势** +* 通过结构化有效载荷使消息意图更清晰 +* 在目标链上更高效地进行签名验证 +* 跨链的标准化消息格式 + + +## 交易流程:NEAR 到其他链 + +以下是从 NEAR 到不同目标链处理转移的概述: + +```mermaid +flowchart TD + Start[用户发起转移] --> TokenCheck{代币类型?} + + TokenCheck -->|NEAR 原生| Lock[锁定在桥合约中] + TokenCheck -->|桥接代币| Burn[销毁代币] + + Lock --> MPCSign[请求 MPC 签名] + Burn --> MPCSign + + MPCSign --> Chain{目标链} + + Chain -->|比特币| BTCBridge[比特币脚本] + Chain -->|其他| OtherBridge[验证 MPC 签名] + + BTCBridge --> Mint[铸造/释放代币] + OtherBridge --> Mint + + Mint --> End[转移完成] +``` + +### 转移流程 + +让我们跟踪当用户想要将代币从 NEAR 转移到另一条链时会发生什么: + +#### 1. 发起 + +用户开始时调用代币合约,包含: + +* 转移金额 +* 目标链和地址 +* 费用偏好(是否在被转移代币中支付费用还是用 NEAR 支付) +* 费用在 NEAR 侧为中继器铸造 + +#### 2. 代币锁定 + +代币合约将代币转移到 Locker 合约,该合约: + +* 验证转移消息 +* 分配唯一的随机数 +* 记录待处理的转移 +* 发出转移事件 + +#### 3. MPC 签名 + +桥合约: + +* 请求生成签名 +* MPC 节点联合生成和聚合签名 +* 在整个过程中保持阈值安全性 + +#### 4. 目标链 + +目标链上的桥接代币工厂: + +* 验证 MPC 签名 +* 铸造等量代币 + +--- + +## 交易流程:其他链到 NEAR + +反向流程根据源链有所不同: + +### 1. 以太坊 + +使用 NEAR 轻客户端以获得最高安全性: + +* 在源链上销毁代币 +* 向 NEAR 提交证明 +* 通过轻客户端验证证明 +* 向接收方释放代币 + +### 2. 支持的非 EVM 链(如 Solana) + +利用既有的消息传递协议(如 Wormhole)用于: + +* 链间消息传递 +* 交易验证 +* 与 NEAR 代币工厂系统集成 + +### 3. 其他 EVM 链 + +利用轻客户端(效率高时)和消息传递协议的组合,确保对入站转移的安全验证。 + +--- + +## 交易流程:链到链(通过 NEAR) + +对于两条非 NEAR 链之间的转移(如以太坊到 Solana),桥将两种流程结合起来,以 NEAR 作为中间路由层。桥不是在 NEAR 上铸造或解锁代币,而是创建一条转发消息,指示代币在最终目标链上铸造或解锁。 + +从用户的角度来看,这表现为单一操作——他们在源链上发起转移,链下中继器基础设施自动处理中间 NEAR 路由。 + +--- + +## 安全模型 + +### 信任假设 + +Omni Bridge 根据链连接方式需要不同的信任假设: + +#### 对于链签名: + +* NEAR 协议安全性(2/3 以上诚实验证者) +* MPC 网络安全性(2/3 以上诚实节点) +* 没有单一实体控制足够多的 MPC 节点来伪造签名 +* 签名协议的正确实现 + +#### 对于以太坊/比特币连接: + +* 轻客户端安全性 +* 最终性假设(如足够的区块确认) +* 链特定的共识假设 + +#### 对于消息传递连接: + +* 底层消息传递协议的安全性(如 Wormhole 守护者网络) +* 由 NEAR 网络参与者(如验证者和全节点)验证 + +--- + +## 中继器网络 + +中继器是无许可基础设施运营商,监控桥接事件并执行跨链交易。与许多桥设计不同,我们的中继器不能: + +* 伪造转移 +* 窃取资金 +* 审查交易(用户可以自行中继) +* 为了利润抢先交易 +* 不产生额外的安全假设 + + +中继器的角色纯粹是操作性的——执行有效的转移并收取预定费用。多个中继器可以同时运行,为更快的执行速度和更低的费用创造竞争。 + + +--- + +## 快速转移 + +标准跨链转移可能由于最终性和验证要求而耗时较长。**快速转移**允许中继器通过提前提供流动性来加快这一过程。 + +### 工作原理 + +1. **用户发起:** 用户发送一条 `FastFinTransferMsg`,指定目的地和费用。 +2. **中继器执行:** 中继器检测到请求,立即从自己的资金中向用户在目标链上转移等额金额(扣除费用)。 +3. **结算:** 一旦原始转移完全验证和最终确认,桥后续偿还中继器。 + + +快速转移非常适合优先考虑速度而非成本的用户,因为中继器可能会为流动性和便利性收取溢价。 + + +--- + +## 多代币支持(ERC1155) + +Omni Bridge 支持 **ERC1155** 标准,能够在单个合约内转移多种代币类型。 + +### 地址衍生 +为了保持跨链一致性,桥接的 ERC1155 代币使用确定性地址衍生方案: +* **确定性地址:** `keccak256(tokenAddress + tokenId)` +* 这确保 ERC1155 合约中的每个 `tokenId` 在目标链上映射到唯一的一致地址。 + +### 关键函数 +* **`initTransfer1155`**:为特定 ERC1155 代币 ID 发起转移。 +* **`logMetadata1155`**:注册特定代币 ID 的元数据,确保索引器和钱包能够识别它。 + +--- + +## 费用结构 + +桥接费用在 NEAR 上统一处理,包含以下组成部分: + +### 执行费用 + +* 目标链 Gas 成本 +* 源链存储成本 +* 中继器运营成本 +* MPC 签名成本 + +### 费用支付选项 + +* 源链原生代币 +* 被转移的代币 + + +费用根据不同链上的 Gas 价格动态调整,以确保可靠执行。 + + +### 设计目标 + +费用结构旨在: + +* 确保中继器的经济可行性 +* 防止经济攻击 +* 允许费用市场竞争 +* 覆盖最坏情况下的执行成本 + + +用户可以通过自行执行转移完全绕过中继器,只需支付每条链上必要的 Gas 费用。这为中继器费用创造了自然上限。 + diff --git a/zh/chain-abstraction/omnibridge/overview.mdx b/zh/chain-abstraction/omnibridge/overview.mdx new file mode 100644 index 00000000000..076acf1db84 --- /dev/null +++ b/zh/chain-abstraction/omnibridge/overview.mdx @@ -0,0 +1,34 @@ +--- +sidebarTitle: 概述 +title: Omni Bridge 概述 +description: "了解 Omni Bridge,这是一个多链资产桥,使用链签名和 MPC 技术实现区块链网络之间安全高效的转移。" +--- + +[Omni Bridge](https://github.com/Near-One/omni-bridge) 是一个多链资产桥,可促进不同区块链网络之间安全高效的资产转移。它通过利用[链签名](/chain-abstraction/chain-signatures)及其去中心化的[多方计算(MPC)服务](/chain-abstraction/chain-signatures#multi-party-computation-service)来解决跨链通信中的关键挑战,实现无需信任的跨链资产转移。 + + +要了解更多信息,请参阅 [Omni Bridge 工作原理](./how-it-works)。 + + +## 支持的链 + +Omni Bridge 采用混合架构启动,根据链特定要求和技术限制使用不同的验证方法。这种方式使我们从第一天起就能支持多条链,同时逐步过渡到完整的链签名集成。 + +目前支持的链包括: + +- **以太坊** - _(轻客户端 + 链签名)_ +- **比特币** - _(轻客户端 + 链签名)_ +- **Solana** - _(Wormhole + 链签名)_ +- **Base** - _(Wormhole + 链签名)_ +- **BNB** - _(Wormhole + 链签名)_ +- **Arbitrum** - _(Wormhole + 链签名)_ + + +有关更多详细信息,请参阅 [Omni Bridge 路线图](./roadmap)。 + + +## 资源 + +- [Near-One/omni-bridge](https://github.com/Near-One/omni-bridge) - Omni Bridge 仓库 +- [Near-One/bridge-sdk-js](https://github.com/Near-One/bridge-sdk-js) - JavaScript SDK +- [Near-One/bridge-sdk-rs](https://github.com/Near-One/bridge-sdk-rs) - Rust SDK diff --git a/zh/chain-abstraction/omnibridge/roadmap.mdx b/zh/chain-abstraction/omnibridge/roadmap.mdx new file mode 100644 index 00000000000..71f17b610d9 --- /dev/null +++ b/zh/chain-abstraction/omnibridge/roadmap.mdx @@ -0,0 +1,61 @@ +--- +sidebarTitle: 路线图 +title: Omni Bridge 路线图 +description: "探索 Omni Bridge 路线图,包括混合架构启动、链签名迁移路径以及跨链基础设施的未来开发计划。" +--- + +Omni Bridge 采用混合架构启动,根据链特定要求和技术限制使用不同的验证方法。这种方式使我们从第一天起就能支持多条链,同时逐步过渡到完整的链签名集成。 + +## 支持的链 +该桥目前支持以下网络: + +- **EVM 链:** + - 以太坊 + - Base + - Arbitrum + - BNB Chain + - Polygon(PoS) +- **非 EVM 链:** + - 比特币 + - Solana + - Zcash + +## 架构概述 + +Omni Bridge 将**链签名**作为从 NEAR 出站转移的主要验证机制。入站转移依赖于链特定的验证方法,包括在可用时使用轻客户端以实现最高安全性。 + +### 验证方法 +- **以太坊和比特币:** 轻客户端验证用于入站转移。 +- **其他链:** 由 NEAR 网络验证的消息传递协议。 +- **出站(所有链):** 用于安全交易签名的链签名(MPC)。 + +## 未来开发 +1. **协议改进** +- 增强的费用机制 +- 跨链合约调用 +- 新代币标准支持 + +除基本资产转移外,我们正在扩展桥的功能。增强的费用机制将更好地处理 Gas 价格波动,而跨链合约调用将实现更复杂的交互。 + +2. **基础设施** +- 扩展的中继器网络 +- 改进的监控工具 +- 增强的开发者工具 + +基础设施开发专注于可靠性和可用性。扩展的中继器网络提高了转移速度和可靠性,而更好的监控和开发者工具使集成和维护更加容易。 + +## 参与其中 + +### 贡献领域 +- 链集成 +- 性能优化 +- 安全分析 +- 开发者工具 + +- [Near-One/omni-bridge](https://github.com/Near-One/omni-bridge) - Omni Bridge 仓库 +- [Near-One/bridge-sdk-js](https://github.com/Near-One/bridge-sdk-js) - JavaScript SDK +- [Near-One/bridge-sdk-rs](https://github.com/Near-One/bridge-sdk-rs) - Rust SDK 和桥接 CLI + +代码是开源的,我们欢迎社区贡献。无论您对添加新链支持、优化性能还是构建开发者工具感兴趣,都有空间做出有意义的贡献。 + +桥接基础设施是多链未来的基本组成部分。通过链签名,我们正在创建一种更高效、更安全、更可扩展的跨链通信方法。加入我们共同构建它。 diff --git a/zh/chain-abstraction/what-is.mdx b/zh/chain-abstraction/what-is.mdx new file mode 100644 index 00000000000..c49f022f1be --- /dev/null +++ b/zh/chain-abstraction/what-is.mdx @@ -0,0 +1,50 @@ +--- +title: 什么是链抽象? +description: "了解 NEAR 如何让您无缝地跨所有链进行操作" +--- + +通过多种创新技术的结合,NEAR 使开发者能够构建跨多个区块链无缝运行的应用程序,同时为开发者和最终用户屏蔽底层的复杂性。 + + + + 链签名允许 NEAR 账户**以及智能合约**为所有其他链(包括比特币、以太坊和 Solana)签署交易 + + + 一个去中心化系统,用户只需**表达期望的结果**(例如"以最优价格将 BTC 换成 ETH"),解决者网络便会竞相以最优方式履行这些意图 + + + 一个多链桥,能够实现安全高效的跨链转移。该桥既是代币工厂又是托管方,通过统一接口管理原生代币和桥接代币 + + + +--- + +## 为什么链抽象至关重要 + +通过在 NEAR 上构建,开发者无需担心与多个区块链集成的复杂性。他们可以专注于构建能够跨所有链无缝运行的优质应用程序。 + +与此同时,用户可以享受流畅的体验,使用统一的账户和资产,甚至无需了解自己正在哪条区块链上操作。 + + + + - 通过单一的 NEAR API 与多个区块链集成 + - 专注于应用逻辑而非区块链复杂性 + - 覆盖用户,无论其偏好哪个区块链网络 + + + - 使用单一 NEAR 账户跨所有链操作 + - 无缝访问来自多个区块链的资产和服务 + - 享受统一且直观的用户体验 + + + + +想象构建一个数字艺术市场,用户可以在此购买来自不同区块链(以太坊、Solana 等)的 NFT。若没有链抽象,您需要: + +- 实现多个区块链连接 +- 处理不同的钱包类型 +- 管理跨链转移 +- 构建复杂的用户界面来解释区块链概念 + +有了链抽象,您和您的用户只需专注于核心体验:浏览和交易艺术品。所有区块链的复杂性都在后台自动处理。 + diff --git a/zh/data-infrastructure/big-query.mdx b/zh/data-infrastructure/big-query.mdx new file mode 100644 index 00000000000..5a66e5a138a --- /dev/null +++ b/zh/data-infrastructure/big-query.mdx @@ -0,0 +1,116 @@ +--- +title: BigQuery 公共数据集 +sidebarTitle: BigQuery +description: "了解如何使用 NEAR 协议的 BigQuery 公共数据集进行区块链数据分析,包括查询链上数据、了解费用以及访问历史交易数据。" +--- + +本文档介绍了 BigQuery 公共数据集的概览,该数据集允许用户查询 NEAR 协议的历史链上数据。内容包括设置说明、示例查询以及可用数据结构的相关信息。 + +# NEAR 公共数据湖 + +NEAR 公共数据湖中的区块链数据索引适合所有希望深入了解区块链数据的人。这包括: + +- **用户**:创建查询,大规模追踪 NEAR 资产、监控交易或分析链上事件。 +- **研究人员**:将索引数据用于数据科学任务,包括链上活动分析、趋势识别,或为 AI/ML 管道提供预测分析数据。 +- **初创企业**:可利用 NEAR 的索引数据深入洞察用户参与度、智能合约使用情况,或跨代币和 NFT 采用情况的洞察。 + +优势: + +- **NEAR 即时洞察**:大规模查询历史链上数据。 +- **经济高效**:无需存储和处理大量 NEAR 协议数据;可按需查询任意数量的数据。 +- **易于使用**:无需区块链技术背景;只需掌握基本 SQL 知识即可获取洞察。 + +## 快速开始 + +1. 登录您的 [Google Cloud 账户](https://console.cloud.google.com/)。 +2. 打开 [NEAR 协议 BigQuery 公共数据集](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=crypto_near_mainnet_us&page=dataset)。 +3. 点击 VIEW DATASET 按钮。 +4. 点击 + 创建新标签页,编写您的查询,点击 RUN 按钮,在查询下方查看 `Query results`。 +5. 完成 :) + + + +[NEAR 公共数据湖仓库](https://github.com/near/near-public-lakehouse) 包含用于摄取 NEAR 协议数据的源代码,这些数据由 [NEAR Lake Indexer](https://github.com/near/near-lake-indexer) 以 JSON 文件形式存储在 AWS S3 中。 + + + +### 示例查询 + +- _每天有多少唯一签名者和账户与我的智能合约交互?_ + +```sql +SELECT + ra.block_date collected_for_day, + COUNT(DISTINCT t.signer_account_id) as total_signers, + COUNT(DISTINCT ra.receipt_predecessor_account_id) as total_accounts +FROM `bigquery-public-data.crypto_near_mainnet_us.receipt_actions` ra + JOIN `bigquery-public-data.crypto_near_mainnet_us.receipt_origin_transaction` ro ON ro.receipt_id = ra.receipt_id + JOIN `bigquery-public-data.crypto_near_mainnet_us.transactions` t ON ro.originated_from_transaction_hash = t.transaction_hash +WHERE ra.action_kind = 'FUNCTION_CALL' + AND ra.receipt_receiver_account_id = 'social.near' -- 替换为您的合约 +GROUP BY 1 +ORDER BY 1 DESC; +``` + +## 费用是多少? + +- NEAR 支付存储费用,不向您收取使用公共数据集的费用。 + > 如需了解更多关于 BigQuery 公共数据集的信息,请[查看此页面](https://cloud.google.com/bigquery/public-data)。 +- Google GCP 会对您对数据执行的查询收费。例如,以今天("2023年9月1日")的价格为例,按需(每 TB)查询定价为每 TB $6.25,每月前 1 TB 免费。 + > 请查看 [Google 定价页面](https://cloud.google.com/bigquery/pricing#analysis_pricing_models) 了解详细定价信息、选项和最佳实践。 + + +您可以在 BigQuery 控制台 UI 中运行查询前查看将查询多少数据。同样,由于 BigQuery 使用列式数据结构和分区,建议仅选择所需的列和分区(`block_date`),以避免不必要的查询费用。 + + +![查询费用](/assets/docs/data-infrastructure/BQ_Query_Cost.png "BQ 查询费用") + +## 架构 + +数据使用 [Databricks Autoloader](https://docs.gcp.databricks.com/ingestion/auto-loader/index.html) 以流式方式加载到原始/铜层表中,并通过 [Databricks Delta Live Tables](https://www.databricks.com/product/delta-live-tables) 流式作业转换为清洗/丰富/银层表。 + +银层表也会复制到 [GCP BigQuery 公共数据集](https://cloud.google.com/bigquery/public-data)。 + +![架构](/assets/docs/data-infrastructure/Architecture.png "架构") + + + +[Databricks 奖章架构](https://www.databricks.com/glossary/medallion-architecture)。 + + + +## 可用数据 + +NEAR 目前提供的数据受 [NEAR Indexer for Explorer](https://github.com/near/near-indexer-for-explorer/) 的启发。 + + +NEAR 计划通过对部分表进行非规范化处理来改善 NEAR 公共数据湖中的可用数据,使其更易于使用。 + + +NEAR 公共数据湖中可用的表包括: + +- **blocks(区块)**:表示 NEAR 区块链中完整区块的结构。`Block`(区块)是 NEAR 协议区块链中的主要实体。NEAR 协议每秒产生一个区块。 +- **chunks(分块)**:表示 NEAR 区块链中分块的结构。`Block`(区块)的 `Chunk`(分块)是来自 `Shard`(分片)的 `Block` 的一部分。`Block` 的 `Chunks` 集合构成 NEAR 协议区块。`Chunk` 包含构成 `Block` 的所有结构:`Transactions`(交易)、[`Receipts`](https://nomicon.io/RuntimeSpec/Receipts)(收据)和 `Chunk Header`(分块头)。 +- **transactions(交易)**:`Transaction`(交易)是用户与区块链交互的主要方式。交易包含:签名者账户 ID、接收者账户 ID 和操作。 +- **execution_outcomes(执行结果)**:执行结果是执行 `Transaction`(交易)或 `Receipt`(收据)的结果。交易执行的结果始终会产生一个收据。 +- **receipt_details(收据详情)**:Near 中所有跨合约(假设每个账户存在于其自己的分片中)通信都通过收据进行。收据是有状态的,它们不仅作为账户之间的消息,还可以存储在账户存储中以等待 `DataReceipts`。每个收据都有一个 `predecessor_id`(发送者)和 `receiver_id`(当前账户)。 +- **receipt_origin(收据来源)**:追踪产生该收据的交易。 +- **receipt_actions(收据操作)**:操作收据表示在 `receiver_id` 端应用操作的请求。它可以作为 `Transaction`(交易)执行或另一个 `ACTION` 收据处理的结果产生。操作类型可以是:`ADD_KEY`、`CREATE_ACCOUNT`、`DELEGATE_ACTION`、`DELETE_ACCOUNT`、`DELETE_KEY`、`DEPLOY_CONTRACT`、`FUNCTION_CALL`、`STAKE`、`TRANSFER`。 +- **receipts(收据视图)**:建议仅选择所需的列和分区(`block_date`),以避免不必要的查询费用。此视图连接了收据详情、产生该收据的交易以及收据执行结果。 +- **account_changes(账户变更)**:每个账户都有一个关联状态,存储其元数据和所有与合约相关的数据(合约代码 + 存储)。 + + +**关于数据的补充说明** + +- 跳过的区块:NEAR 区块链可能包含跳过的区块,例如区块 `57730443`。对于这些情况,我们可以使用 `prev_block_hash` 列查找分块数据对应的区块,例如 `SELECT * FROM chunks c JOIN blocks b ON c.chunk.header.prev_block_hash = b.header.prev_hash`。 + + + + +**参考资料** + +- [协议文档](/getting-started/what-is-near) +- [Near 数据流](/protocol/data-flow/near-data-flow) +- [协议规范](https://nomicon.io/) + + diff --git a/zh/data-infrastructure/data-api.mdx b/zh/data-infrastructure/data-api.mdx new file mode 100644 index 00000000000..37fb08158b9 --- /dev/null +++ b/zh/data-infrastructure/data-api.mdx @@ -0,0 +1,99 @@ +--- +title: 数据 API +description: "探索社区构建的链上数据访问 API" +--- + +如果您正在构建去中心化应用,很可能需要查询链上数据。由于构建完整的索引器并非总是可行,社区已创建了一套 API,供您查询 NEAR 区块链上的数据。 + +这些 API 提供了一种访问链上数据的简便方式,无需运行自己的索引器或节点。它们设计简洁,功能丰富,涵盖从查询账户余额到探索交易和区块的各种操作。 + +--- + +## FastNEAR API + +[FastNEAR API](https://github.com/fastnear/fastnear-api-server-rs?tab=readme-ov-file#api-v1) 可以轻松查询 NEAR 区块链,以获取账户资产、将密钥映射到账户 ID、探索区块交易等。 + +可能的使用场景包括: +- 查询账户的所有资产(包括同质化和非同质化代币) +- 查询最新产生的区块 +- 将公钥映射到账户 ID +- 将完全访问公钥映射到账户 ID +- 查询用户的质押池(验证者) +- 查询代币的最大持有者 + +
+ +#### 示例 + +```bash +# 查询用户的 FT +curl https://api.fastnear.com/v1/account/root.near/ft + +# 查询用户的 NFT +curl https://api.fastnear.com/v1/account/root.near/nft + +# 查询用户的所有资产 +curl https://api.fastnear.com/v1/account/root.near/full +``` + +--- + +## NearBlocks API + +[NearBlocks API](https://api.nearblocks.io/api-docs/) 提供了查询 NEAR 账户上发生的操作的端点,可能的使用场景包括: + +- 查询账户余额 +- 查询对特定合约的所有函数调用 +- 获取 NEAR 总供应量和流通供应量 +- 查询 NEAR 上的总交易数量 + +
+ +#### 示例 + +```bash +# 所有在 Keypom 上调用 `create_drop` 的交易 +curl -X GET "https://api.nearblocks.io/v1/account/v2.keypom.near/txns?method=create_drop" + +# `gagdiez.near` 在 Keypom 上调用 `create_drop` 的所有记录 +curl -X GET "https://api.nearblocks.io/v1/account/v2.keypom.near/txns?method=create_drop&from=gagdiez.near" +``` + +--- + +## Pikespeak API + +[Pikespeak API](https://doc.pikespeak.ai/) 允许您获取区块链事件以及钱包、验证者、委托者、资金转移、dApp 活动等方面的聚合分析数据。 + +使用场景包括: +- 查询账户余额 +- 查询最活跃的钱包 +- 查询历史账户事件 + +_要访问 Pikespeak API,您需要[注册并创建账户](https://pikespeak.ai/plans)。注册后,在 [`My Account`](https://pikespeak.ai/myaccount) 页面下可以获取您的 API 密钥。_ + +
+ +#### 示例 + +```sh +# 查询 `root.near` 的账户余额: +curl -X GET https://api.pikespeak.ai/account/balance/root.near -H "accept: application/json" -H "x-api-key: YOUR-PIKESPEAK-API-KEY" + +# 最活跃的 NEAR 发送者钱包 +curl -X GET https://api.pikespeak.ai/hot-wallets/near -H "accept: application/json" -H "x-api-key: YOUR-PIKESPEAK-API-KEY" + +# 获取 `keypom.near` 的历史账户事件 +curl -X GET https://api.pikespeak.ai/event-historic/keypom.near -H "accept: application/json" -H "x-api-key: YOUR-PIKESPEAK-API-KEY" +``` + +--- + +## The Graph + +[The Graph](https://thegraph.com/docs/en/cookbook/near/) 为开发者提供处理区块链事件的工具,并通过 GraphQL API(称为子图)使结果数据易于访问。[Graph Node](https://github.com/graphprotocol/graph-node) 现在能够处理 NEAR 事件,这意味着 NEAR 开发者可以构建子图来索引其智能合约。 + +--- +## SubQuery + +[SubQuery](https://academy.subquery.network/quickstart/quickstart_chains/near.html):一个快速、灵活、可靠的开源数据索引器,为您的 NEAR 及其他多链 Web3 项目提供自定义 API。 diff --git a/zh/data-infrastructure/data-services.mdx b/zh/data-infrastructure/data-services.mdx new file mode 100644 index 00000000000..4309262b4bc --- /dev/null +++ b/zh/data-infrastructure/data-services.mdx @@ -0,0 +1,24 @@ +--- +title: 现有服务 +description: "索引器持续监听交易并将其存储,以便轻松查询。" +--- + +数据服务持续监听区块链,处理交易并将其存储到可方便查询的数据库中。您可以使用它们高效地访问区块链数据: + +- [BigQuery](./big-query):NEAR 公共数据湖中的区块链数据索引,适合所有希望深入了解区块链数据的人。 + +- [NEAR Lake Framework](./near-lake-framework):NEAR Lake 的配套库。它允许您构建自己的索引器,**从 NEAR Lake 数据源**监听区块流,并允许您**创建自己的逻辑来处理该数据**。请记住,对于未来的项目,**这是您应该优先使用的**,而不是 Indexer Framework。了解[为什么它更好](/data-infrastructure/near-lake-framework#comparison-with-near-indexer-framework)。 + +- [Indexer.xyz 多链索引器](https://indexer.xyz/):Indexer.xyz 是一个应用层,您可以完全在其上构建 NFT 或 DeFi 应用。除了原始交易索引外,Indexer.xyz 还为您提供标准化的 GraphQL API 层,轻松访问跨合约和跨链的交易。 + +- [The Graph](https://thegraph.com/docs/en/cookbook/near/):用于处理区块链事件的开发工具,通过 GraphQL API(称为子图)使结果数据易于访问。[Graph Node](https://github.com/graphprotocol/graph-node) 能够处理 NEAR 事件,这意味着 NEAR 开发者可以构建子图来索引其智能合约。 + +- [GetBlock](https://getblock.io/explorers/near/blocks/):为 NEAR 提供简单可靠的 API 访问历史数据流及其他服务的开发者工具。 + +- [社区 API](./data-api):使用我们社区的 API 构建精确、可靠的 dApp。 + +- [Covalent](https://www.covalenthq.com/docs/networks/aurora/):用于 [Aurora EVM](https://aurora.dev/) 索引,Covalent 提供统一 API,为数十亿个 Web3 数据点带来可见性。 + +- [NEAR Indexer Framework](https://github.com/near/nearcore/tree/master/chain/indexer):一个微框架,为您提供区块的"实时"流。适用于处理链上实时 `events`(事件)。 + +- [SubQuery](https://academy.subquery.network/quickstart/quickstart_chains/near.html):一个端到端的多区块链索引解决方案,为 NEAR 开发者提供快速、灵活、通用、开源且去中心化的 Web3 项目 API。[NEAR 入门项目](https://github.com/subquery/near-subql-starter/tree/main/Near/near-starter)为开发者提供模板,可在数分钟内快速上手。 diff --git a/zh/data-infrastructure/indexers.mdx b/zh/data-infrastructure/indexers.mdx new file mode 100644 index 00000000000..1855fa09056 --- /dev/null +++ b/zh/data-infrastructure/indexers.mdx @@ -0,0 +1,115 @@ +--- +title: 索引器简介 +sidebarTitle: 简介 +description: "了解区块链索引器、它们如何与 NEAR 协议配合使用、拉取模型与推送模型的区别,以及何时使用索引器进行数据查询。" +--- + +在这里,您将找到熟悉索引器概念所需的一切知识。 + + + +我们建议查阅 [NEAR 数据流](../protocol/data-flow/near-data-flow),以熟悉数据在 NEAR 生态系统中的流动方式。 + + + +--- + +## 区块链及其特性 + +区块链数据针对序列化**写入**进行了优化,每次一个区块,随着链的创建而进行。查询特定区块或账户的区块链数据相对简单,因为只需检索特定区块的数据。 + +然而,跨多个区块查询数据(例如 `X 日期到 Y 日期之间的所有转账`)可能较为繁琐,因为我们必须聚合来自多个单区块查询的结果。 + +鉴于区块链本身是一个分布式数据库,而智能合约(去中心化应用,dApp)是在区块链内部虚拟机上运行的应用,我们需要理解智能合约*不应*被视为"后端"。虽然某些应用可能仅由智能合约组成,但在大多数情况下,仅凭智能合约来构建 dApp 是不可能的。 + +智能合约在交互方面受到限制。所谓"交互",是指现实世界中非常常见的事情,如用户通知、与第三方应用的集成等。 + +然而,区块链的本质是它*必须*是确定性的。区块链的一个关键特性是它知道给定时刻的状态,而对于区块链来说,那个时间单位就是区块。可以将它们视为快照。区块链在每个区块上对其状态进行快照。作为用户,我们可以针对特定区块调用智能合约,区块链保证无论何时调用,同一区块的执行始终产生相同的结果。 + +区块链的确定性特性使其与外部(链外)变量隔绝。从智能合约内部调用 API 是完全不可能的。区块链和智能合约与外部(链外)世界是隔离的。 + +![区块链与外部世界隔绝](/assets/docs/data-infrastructure/blockchain.png) + +区块链擅长以去中心化方式应用请求的状态变更。然而,要观察这些变更,您需要主动从网络中拉取信息。 + +让我们通过一个例子来说明,而不是抽象的解释。 + + +**示例 dApp** + +假设我们有一个销售电子书的智能合约。一旦用户购买了一本书,我们希望通过电子邮件向他们发送副本。 + + + +该 dApp 在链外某处部署了一个辅助程序,该辅助程序包含可以通过电子邮件发送电子书副本的代码。但我们如何触发这个辅助程序呢? + +--- + +## 从外部世界获取区块链数据 + +NEAR 区块链为所有人实现了一个 [JSON-RPC 端点](/api/rpc/introduction),用于与区块链交互。通过 JSON-RPC API,用户可以调用智能合约,使其以给定参数执行。此外,用户还可以查看区块链上的数据。 + +继续我们的例子,我们可以让辅助程序每秒拉取一个[区块](/api/rpc/block-chunk#block-details),然后拉取所有[分块](/api/rpc/block-chunk#chunk-details),并分析区块中包含的交易,检查是否存在对我们智能合约的"购买电子书"函数调用的交易。如果我们观察到这样的交易,我们需要确认它是成功的,以免向"购买电子书"交易失败的用户发送电子书。 + +完成上述过程后,我们可以触发辅助程序的代码,向用户发送包含他们购买的电子书的电子邮件。 + +这种方式被称为获取数据的*拉取模型*。这种方式没有任何问题,但有时您可能会发现它并不是最方便或最可靠的方式。 + +此外,并非所有数据都可以通过 JSON-RPC 获取。例如,*本地收据*就无法通过 JSON-RPC 获取,因为它们不存储在 NEAR 节点的内部数据库中。 + +--- + +## 索引器 + +区块链索引器是获取数据的*推送模型*的一种实现。辅助程序不是主动从来源拉取数据,而是等待数据被推送给它。数据是完整的,因此辅助程序可以立即开始分析;理想情况下,数据足够完整,无需额外拉取以获取更多详情。 + +回到我们的例子,辅助程序变成了**一个索引器**,接收每个*区块*,以及**分块**、带有状态的**交易**等。辅助程序以同样的方式分析数据,并触发代码向用户发送包含他们购买的电子书的电子邮件。 + +![索引器从区块链流式传输数据](/assets/docs/data-infrastructure/indexer.png) + + +**索引器的概念** + +索引器监听*链上写入时的数据流*,并可立即对其进行过滤和处理,以检测感兴趣的事件或模式。 + + + +--- + +## 索引器与"宽查询" + +本文开头提到了*"宽查询"*这个术语。以下是简要回顾: + + +**"宽查询"的定义** + +跨多个区块查询数据需要聚合来自多个单区块查询的结果。我们可以将这些聚合视为来自*"宽查询"*的结果。 + + + +由于索引器监听来自区块链的*数据流*,并且数据可以根据定义的需求立即进行过滤和处理,因此它们可以用于简化"宽查询"的执行。例如,数据流可以写入永久数据库,以便稍后使用 SQL 等便捷查询语言进行数据分析。 + +另一个凸显"宽查询"需求的例子是:当您使用助记词恢复一个或多个账户时。由于助记词本质上代表一个签名密钥对,恢复的是共享关联公钥的所有账户。因此,当通过 [NEAR Wallet](https://wallet.near.org) 使用助记词恢复账户时,查询需要找到并恢复所有具有匹配公钥的账户。利用 [Near Lake Framework](https://github.com/near/near-lake-framework-rs) 可以将此数据存储在永久数据库中,从而允许 [NEAR Wallet](https://wallet.near.org) 执行此类"宽查询"。这是仅使用 JSON-RPC 无法实现的。 + +--- + +## NEAR 生态系统中的索引器 + +如果您准备好托管自己的索引器,我们推荐使用 [Near Lake Framework](./lake-framework/near-lake),因为它简单、可靠,并支持多种语言(JavaScript、Rust、Python)。 + +如果速度对您的索引需求至关重要,请考虑使用 [Near Indexer](./near-indexer)。但请注意,维护它可能更为复杂和昂贵,因为它本质上作为网络中的独立节点运行。 + +如果您不希望托管自己的解决方案,可以使用[第三方服务](./data-services)。 + +--- + +## 总结 + +我们希望本文能帮助您理解索引器的概念。同时,我们希望您现在能够轻松判断您的应用是否需要索引器。 + +--- + +## 下一步是什么? +我们鼓励您进一步了解 [Lake Indexer 项目](./lake-framework/near-lake)。请前往[教程](/data-infrastructure/tutorials/near-lake-state-changes-indexer)部分,了解如何在实践中构建索引器。 + +此外,还有一些与 NEAR 生态系统紧密集成的第三方索引器。您可以在[索引工具](./data-services)下查看所有数据来源选项(包括 The Graph、Pagoda、Pikespeak 和 SubQuery)。 diff --git a/zh/data-infrastructure/lake-framework/near-lake.mdx b/zh/data-infrastructure/lake-framework/near-lake.mdx new file mode 100644 index 00000000000..3228eb04721 --- /dev/null +++ b/zh/data-infrastructure/lake-framework/near-lake.mdx @@ -0,0 +1,71 @@ +--- +sidebarTitle: Lake 概述 +title: NEAR Lake 索引器 +description: "了解 NEAR Lake 如何索引网络" +--- + +NEAR Lake 是一个构建在 [NEAR Indexer Framework](https://github.com/near/nearcore/tree/master/chain/indexer) 之上的索引器,用于监控网络并将所有事件日志(例如 [FT 事件](https://github.com/near/NEPs/tree/master/neps/nep-0300.md) 和 [NFT 事件](https://github.com/near/NEPs/tree/master/neps/nep-0256.md))以 JSON 文件形式存储在 AWS S3 上。 + + +**GitHub 仓库** + +您可以在[此 GitHub 仓库](https://github.com/near/near-lake-indexer/)中找到 Lake Indexer 源代码。 + + + +### 工作原理 + + + +[Pagoda Inc.](https://www.pagoda.co) 运行 NEAR Lake 节点,以 JSON 格式将数据存储在 AWS S3 上。 +除非您有特定原因,否则无需运行自己的 NEAR Lake。 + + + +已创建的 AWS S3 存储桶: + +- `near-lake-data-testnet`(`eu-central-1` 区域) +- `near-lake-data-mainnet`(`eu-central-1` 区域) + +所有存储桶均设置为由请求者支付访问费用。任何人都可以使用自己的 AWS 凭证连接到这些存储桶来读取数据,并由 Amazon 向其收费。 + +### 数据结构 + +Lake Indexer 使用的数据结构如下: + +```text + / + block.json + shard_0.json + shard_1.json + ... + shard_N.json +``` + +`` 是一个 12 位长的 [`u64`](https://doc.rust-lang.org/std/primitive.u64.html) 字符串,带有前导零(例如 "000042839521")。请参阅[此 issue 了解原因](https://github.com/near/near-lake/issues/23)。 + +`block_json` 包含 JSON 序列化的 `BlockView` 结构体。**注意!** 此结构体将来可能会更改,我们会提前公告。 + +`shard_N.json` 其中 N 是从 `0` 开始的 [`u64`](https://doc.rust-lang.org/std/primitive.u64.html)。表示分片的索引编号。要了解区块中预期的分片数量,可以在 `block.json` 的 `.header.chunks_included` 中查看。 + +### 如何使用 + +我们创建了 [NEAR Lake Framework](/data-infrastructure/near-lake-framework),提供了一种简单直接的方式,基于 NEAR Lake 本身存储的数据创建索引器。 + + +**NEAR Lake Framework** + +您可以在 [NEAR 治理论坛](https://gov.near.org/t/announcement-near-lake-framework-brand-new-word-in-indexer-building-approach/17668)上查看 NEAR Lake Framework 的发布公告。 + + + +我们准备了这个视频教程,通过一个简单的例子为您提供概述和一些实践思路。 + + diff --git a/zh/data-infrastructure/near-indexer.mdx b/zh/data-infrastructure/near-indexer.mdx new file mode 100644 index 00000000000..d14e3ff4f9f --- /dev/null +++ b/zh/data-infrastructure/near-indexer.mdx @@ -0,0 +1,48 @@ +--- +title: 什么是 NEAR Indexer? +description: "用于处理区块链实时事件的框架" +--- + +随着扩展 dApp 进入 NEAR 主网,可能会出现一个问题:它们如何快速、高效地访问已部署智能合约的状态,并过滤掉无关信息?合约可能会发展出复杂的数据结构,通过网络 RPC 查询状态数据可能并非最优方式。 + +[NEAR Indexer](https://github.com/near/nearcore/tree/master/chain/indexer) 是一个专为处理区块链实时事件而设计的微框架,允许以自定义方式捕获和索引区块流。 + +借助 NEAR Indexer,开发者可以对区块链事件进行高层数据聚合和底层内省。 + + + +对于那些不想构建自己的索引器的人,[NEAR Lake Framework](./near-lake-framework) 提供了一种更简单的方式来实时访问区块链数据。 + + + +--- + +## 工作原理 + +NEAR Indexer 通过**运行一个节点**,在区块添加到区块链时处理它们。该框架提供区块流,允许开发者订阅并实时处理这些区块。 + + + +按照[教程](./tutorials/near-indexer)学习如何运行它。 + + + +--- + +## 与 [NEAR Lake Framework](./near-lake-framework) 的比较 + +与 NEAR Lake Framework 相比,在延迟方面,NEAR Indexer 明显更快,因为它以与 RPC 节点相同的方式直接从区块链读取数据。 + +功能 | Indexer Framework | Lake Framework +------- | ----------------- | -------------- +允许跟踪 NEAR 协议中的区块和交易 | **是** | **是**
(但仅限于主网和测试网) +去中心化 | **是** | 否
(Pagoda Inc 将区块转储到 AWS S3) +响应时间(端到端) | 最短 3.8 秒(估计平均 5-7 秒) | [最短 3.9 秒(估计平均 6-8 秒)](./near-lake-framework#latency) +响应时间(仅框架开销) | 0.1 秒 | 0.2-2.2 秒 +基础设施估算成本 | [$500+/月](https://near-nodes.io/rpc/hardware-rpc) | [**$20/月**](./near-lake-framework#cost) +维护难度 | 高级
(需要跟进每次 nearcore 升级并同步状态) | **简单**
(部署一次,无需维护) +启动需要多长时间? | 数天(在主网/测试网上) | **秒级** +本地开发难度 | 高级
(localnet 是个好选择,但在 testnet/mainnet 上测试太重) | **简单**
(参见[教程](./tutorials/near-lake-state-changes-indexer)) +自定义索引器可使用的编程语言 | 仅限 Rust | **任意语言**
(目前,辅助包已发布 [Python](http://pypi.org/project/near-lake-framework)、[JavaScript](https://www.npmjs.com/package/near-lake-framework) 和 [Rust](https://crates.io/crates/near-lake-framework) 版本) + +--- diff --git a/zh/data-infrastructure/near-lake-framework.mdx b/zh/data-infrastructure/near-lake-framework.mdx new file mode 100644 index 00000000000..f2f883dbc9f --- /dev/null +++ b/zh/data-infrastructure/near-lake-framework.mdx @@ -0,0 +1,118 @@ +--- +title: 什么是 Lake Framework? +description: "使用现有数据湖构建自己的索引器的库" +--- + +NEAR Lake Framework 是一个由伴随库组成的生态系统,这些库从 [`NEAR Data Lake`](#data-lake) 读取数据。它们允许您通过订阅持续推送到 [NEAR Lake](#data-lake) 的区块流来构建自己的索引器。 + + +*基于 NEAR Lake Framework 构建的简单索引器示例* + +您可以使用自己选择的编程语言创建自己的逻辑来处理 NEAR 协议数据: +- [Python](http://pypi.org/project/near-lake-framework) +- [JavaScript](https://github.com/near/near-lake-framework-js) +- [Rust](https://crates.io/crates/near-lake-framework) + + +**GitHub 仓库** + +https://github.com/near/near-lake-framework/ + + + +--- + +## 数据湖 + +NEAR Lake 是一组 AWS S3 存储桶,持续从由 [Aurora](https://aurora.dev) 维护的[运行中的索引器](https://github.com/aurora-is-near/near-lake-indexer)接收新区块。 + +[FT 事件](https://github.com/near/NEPs/tree/master/neps/nep-0300.md) 和 [NFT 事件](https://github.com/near/NEPs/tree/master/neps/nep-0256.md) 等事件持续以 JSON 文件形式写入两个 AWS S3 存储桶: + +- `near-lake-data-testnet`(`eu-central-1` 区域) +- `near-lake-data-mainnet`(`eu-central-1` 区域) + +两个存储桶均设置为由请求者支付访问费用。任何人都可以使用自己的 AWS 凭证连接到这些存储桶来读取数据,并由 Amazon 向其收费。 + +--- + +## 延迟 + +基于 Lake Framework 的索引器继承了 [NEAR Indexer 的延迟特性](https://github.com/near/nearcore/tree/master/chain/indexer),**加上转储到 AWS S3 和从 AWS S3 读取的额外 0.1-2.1 秒延迟**。 + + + +大部分延迟来自最终确定性延迟,Indexer Framework 和 Lake Framework 都只增加了极少的开销。 + + + +--- + +## 费用 + +基于 NEAR Lake 的索引器消耗 100-500MB 内存(取决于预加载队列的大小),不需要任何存储,甚至可以在树莓派上运行。 + +由于 NEAR Lake 将 S3 存储桶配置为**由读者支付费用**,从 S3 获取区块链数据每月大约需要 $30.16。 + + + +假设 NEAR 协议每 600ms 产生 1 个区块,一整天网络最多可以创建 144000 个区块(86400s / 每块 600ms)。 + +根据 [Amazon S3 价格](https://aws.amazon.com/s3/pricing/?nc1=h_ls),`list` 请求收费为每 1000 个请求 $0.005,`get` 收费为每 1000 个请求 $0.0004。 + +计算(假设我们始终跟踪网络最新状态): + +```text +144000 区块/天 * 每区块 10 个请求 / 1000 个请求 * 每千请求 $0.0004 = $0.576 * 30 天 = $17.20 +``` + +**注意:** 每区块 10 个请求意味着我们有 9 个分片(1 个区块公共数据文件和每个分片各 1 个单独文件) + +30 天内需要执行的 `list` 请求数量: + +```text +144000 区块/天 / 1000 个请求 * 每千 list 请求 $0.005 = $0.72 * 30 天 = $21.60 + +$17.20 + $21.60 = $30.16(约 $30.16) +``` + + + +--- + +## 与 [NEAR Indexer Framework](/data-infrastructure/near-indexer) 的比较 + +NEAR Lake Framework 从 AWS S3 读取数据,而 NEAR Indexer 运行完整节点并直接从区块链实时读取数据。 + +功能 | Indexer Framework | Lake Framework +------- | ----------------- | -------------- +允许跟踪 NEAR 协议中的区块和交易 | **是** | **是**
(但仅限于主网和测试网) +去中心化 | **是** | 否
(Pagoda Inc 将区块转储到 AWS S3) +响应时间(端到端) | 最短 3.8 秒(估计平均 5-7 秒) | [最短 3.9 秒(估计平均 6-8 秒)](#latency) +响应时间(仅框架开销) | 0.1 秒 | 0.2-2.2 秒 +基础设施估算成本 | [$500+/月](https://near-nodes.io/rpc/hardware-rpc) | [**$20/月**](#cost) +维护难度 | 高级
(需要跟进每次 nearcore 升级并同步状态) | **简单**
(部署一次,无需维护) +启动需要多长时间? | 数天(在主网/测试网上) | **秒级** +本地开发难度 | 高级
(localnet 是个好选择,但在 testnet/mainnet 上测试太重) | **简单**
(参见[教程](./tutorials/near-lake-state-changes-indexer)) +自定义索引器可使用的编程语言 | 仅限 Rust | **任意语言**
(目前,辅助包已发布 [Python](http://pypi.org/project/near-lake-framework)、[JavaScript](https://www.npmjs.com/package/near-lake-framework) 和 [Rust](https://crates.io/crates/near-lake-framework) 版本) + +--- + +## 示例与教程 + +- [`near-examples/near-lake-raw-printer`](https://github.com/near-examples/near-lake-raw-printer):基于 NEAR Lake Framework 构建的简单数据打印机示例 +- [`near-examples/near-lake-accounts-watcher`](https://github.com/near-examples/near-lake-accounts-watcher):关于如何使用 NEAR Lake Framework 的视频教程源代码 +- [`near-examples/indexer-tx-watcher-example-lake`](https://github.com/near-examples/indexer-tx-watcher-example-lake):基于 NEAR Lake Framework 构建的索引器示例,用于监控指定账户/合约的交易 + + +**教程** + +参见[教程页面](./tutorials/near-lake-state-changes-indexer) + + diff --git a/zh/data-infrastructure/tutorials/near-indexer.mdx b/zh/data-infrastructure/tutorials/near-indexer.mdx new file mode 100644 index 00000000000..2f0cecbb0da --- /dev/null +++ b/zh/data-infrastructure/tutorials/near-indexer.mdx @@ -0,0 +1,288 @@ +--- +title: "教程:创建索引器" +description: "本教程将引导您使用 NEAR Indexer Framework 构建索引器。该索引器将监听特定合约上的 FunctionCall 并记录每次调用的详细信息。" +--- + +import {Github} from '/snippets/github.jsx'; + +在本教程中,我们将使用 NEAR Indexer Framework 构建一个索引器。该索引器将实时监听来自 NEAR 区块链的区块数据。 + +要让我们的索引器运行起来,需要完成两个步骤: + +1. [初始化](#initialization)索引器 +2. [启动它](#starting-the-indexer) + +索引器示例的完整源代码可在 [GitHub 仓库](https://github.com/near/nearcore/tree/master/tools/indexer/example)中找到。 + + +源代码链接指向 `nearcore` 仓库,因为 Indexer Framework 是 `nearcore` 代码库的一部分。我们提供的链接指向 `master` 分支。如果您想使用**最新稳定发布版本**,应查看[发布页面](https://github.com/near/nearcore/releases)并检出相应的标签。 + + + +NEAR Indexer Framework 仅支持 **`Linux x86`**,**不**支持 Windows 或 MacOS + + +--- + +## 前提条件 + +### 安装 Rust + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +### 安装开发者工具 + +```bash +apt update +apt install -y git binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev cmake gcc g++ python docker.io protobuf-compiler libssl-dev pkg-config clang llvm cargo awscli +``` + + +NEAR Indexer Framework 仅支持 **`Linux x86`**,**不**支持 Windows 或 MacOS + + +### 克隆 nearcore 仓库 + +```bash +git clone git@github.com:near/nearcore.git +``` + +--- + +## 初始化 + +为了让我们的索引器处理区块,它需要作为节点加入 NEAR 网络。为此,我们首先需要初始化它,这将下载区块链的 `genesis` 配置,并为我们的节点创建一个与其他节点通信的 `key`(密钥)。 + +进入 `nearcore/tools/indexer/example` 文件夹并构建索引器: + +```bash +cd nearcore/tools/indexer/example && cargo build --release +``` + +然后,运行以下命令来初始化网络配置: + + + + ```bash + cargo run --release -- --home-dir ~/.near/localnet init + ``` + + + ```bash + cargo run --release -- --home-dir ~/.near/testnet init --chain-id testnet --download-config rpc --download-genesis + ``` + + + ```bash + cargo run --release -- --home-dir ~/.near/mainnet init --chain-id mainnet --download-config rpc --download-genesis + ``` + + + +根据我们要连接的网络,密钥将创建在不同的文件夹中(`~/.near/`)。 + +#### 配置文件 + +配置文件(`~/.near//config.json`)会自动创建,但建议将其替换为以下专为 RPC 节点设计的配置文件之一: + +- [testnet config.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/testnet/rpc/config.json) +- [mainnet config.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/mainnet/rpc/config.json) + + +**配置选项** + +请参阅下方的[自定义配置](#custom-configuration)部分,了解更多配置选项。 + + + +--- + +## 启动索引器 + +完成索引器的初始化和配置后,我们可以通过运行以下命令来启动它: + + + + ```bash + cargo run --release -- --home-dir ~/.near/localnet + ``` + + + ```bash + cargo run --release -- --home-dir ~/.near/testnet run + ``` + + + ```bash + cargo run --release -- --home-dir ~/.near/mainnet run + ``` + + + + + +- 该命令初始化索引器的配置:主目录、同步模式、流式模式、最终性等。 +- 在专用线程上创建 Tokio 运行时。 +- 使用提供的配置创建 Indexer 实例,启动它,并将区块流传输到我们的处理程序。在处理程序(`listen_blocks` 方法)中,有一个无限循环,用于解析每个接收到的新区块的区块数据。 + + + + + +#### 遇到错误? +- 如果您的索引器无法找到 `boot nodes`(启动节点),请查看[启动节点](#boot-nodes)部分 +--- + +## 解析区块数据 + +在 `listen_blocks` 方法中,我们可以在数据从流中流出时解析区块数据。从区块数据中,我们可以访问交易、其收据和操作。请参阅下面的代码,了解如何解析区块数据的示例: + + + +--- + +## 自定义配置 + +默认情况下,nearcore 配置为在保持最新状态的同时尽量减少工作量。索引器可能有不同的需求,因此您可能需要根据需求调整配置。 + +### 要跟踪的分片/账户 + +我们需要确保 NEAR Indexer 跟踪所有必要的分片,因此默认情况下 `"tracked_shards_config"` 设置为 `"AllShards"`。您可能需要应用的最常见调整是监听特定分片;为此,在 `"tracked_shards_config"` 部分(`~/.near//config.json` 文件)中列出您要跟踪的所有分片 UID: + +```json +... +"tracked_shards_config": { + "Shards": [ + "s3.v3", + "s4.v3" + ] +}, +... +``` +或者,如果您想跟踪特定账户: + +```json +... +"tracked_shards_config": { + "Accounts": [ + "account_a", + "account_b" + ] +}, +... +``` + +### 同步模式 +您可以通过设置要流式传输的内容来选择 Indexer Framework 同步模式: + +- LatestSynced - 实时同步,始终获取最新的已最终确认区块进行流式传输 +- FromInterruption - 从 NEAR Indexer 上次中断时的区块开始同步 +- BlockHeight(u64) - 从指定的区块高度开始同步 + + + +### 流式模式 +您可以通过设置要流式传输的内容来选择 Indexer Framework 流式模式: + +- StreamWhileSyncing - 节点同步时进行流式传输 +- WaitForFullSync - 等待节点完全同步后再进行流式传输 + + + +### 最终性 +您可以选择流式传输区块的最终性级别: + +- None - `optimistic`,可能(虽然不太可能)被跳过的区块 +- DoomSlug - `near-final`,不可逆的区块,除非至少一个区块生产者被惩罚 +- Final - `final`,区块已最终确认且不可逆 + + + +### 启动节点 +如果您的节点找不到任何对等节点连接,可以在 `config.json` 文件中手动指定一些启动节点。您可以通过运行以下命令获取网络的活跃对等节点列表: + + + + ```bash + curl -X POST https://rpc.testnet.near.org \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "network_info", + "params": [], + "id": "dontcare" + }' | \ + jq '.result.active_peers as $list1 | .result.known_producers as $list2 | + $list1[] as $active_peer | $list2[] | + select(.peer_id == $active_peer.id) | + "\(.peer_id)@\($active_peer.addr)"' |\ + awk 'NR>2 {print ","} length($0) {print p} {p=$0}' ORS="" | sed 's/"//g' + ``` + + + ```bash + curl -X POST https://rpc.mainnet.near.org \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "network_info", + "params": [], + "id": "dontcare" + }' | \ + jq '.result.active_peers as $list1 | .result.known_producers as $list2 | + $list1[] as $active_peer | $list2[] | + select(.peer_id == $active_peer.id) | + "\(.peer_id)@\($active_peer.addr)"' |\ + awk 'NR>2 {print ","} length($0) {print p} {p=$0}' ORS="" | sed 's/"//g' + ``` + + + +然后将输出作为字符串添加到 `config.json` 文件的 `boot_nodes` 部分: + +```json +... +"network": { + "addr": "0.0.0.0:24567", + "boot_nodes": "ed25519:8oVENgBp6zJfnwXFe...", + ... +}, +... +``` + +### 历史数据 + +Indexer Framework 还提供对内部 API 的访问(请参阅 Indexer::client_actors 方法),因此您可以获取任何区块、交易等的数据,但默认情况下,nearcore 配置为删除旧数据(垃圾回收),因此查询几个纪元之前观察到的数据可能会返回数据未找到的错误。如果您只需要区块流,则不需要此调整,但如果您需要直接从 Indexer 访问历史数据,请考虑将 config.json 中的 "archive" 设置更新为 true: + +```json +... +"archive": true, +... +``` + +--- + +## 在您的项目中使用 NEAR Indexer + +您也可以在自己的 Rust 项目中将 NEAR Indexer Framework 作为依赖项使用。为此,请将以下内容添加到您的 `Cargo.toml` 文件中(将 `2.8.0` 替换为最新稳定发布版本): + +```toml +[dependencies] +near-indexer = { git = "https://github.com/near/nearcore", tag = "2.9.1" } +near-indexer-primitives = { git = "https://github.com/near/nearcore", tag = "2.9.1" } +near-config-utils = { git = "https://github.com/near/nearcore", tag = "2.9.1" } +near-o11y = { git = "https://github.com/near/nearcore", tag = "2.9.1" } +near-primitives = { git = "https://github.com/near/nearcore", tag = "2.9.1" } +``` diff --git a/zh/data-infrastructure/tutorials/near-lake-framework.mdx b/zh/data-infrastructure/tutorials/near-lake-framework.mdx new file mode 100644 index 00000000000..59d32ce30cf --- /dev/null +++ b/zh/data-infrastructure/tutorials/near-lake-framework.mdx @@ -0,0 +1,144 @@ +--- +title: "教程:简单索引器" +description: "本教程将引导您使用 NEAR Lake Framework 构建一个简单的索引器。该索引器将监听特定合约上的 FunctionCall 并记录每次调用的详细信息。" +--- + +import {Github} from '/snippets/github.jsx'; + +在本教程中,我们将使用 NEAR Lake Framework 构建一个简单的索引器。该索引器将监听特定合约上的 FunctionCall 并记录每次调用的详细信息。 + +索引器的完整源代码可在 [GitHub 仓库](https://github.com/near-examples/indexer-near-lake-framework?tab=readme-ov-file)中找到。 + + +使用 NEAR Lake Framework,我们可以订阅来自 NEAR Lake 数据源的区块流。数据来源是由 [NEAR Lake Indexer](https://github.com/aurora-is-near/near-lake-indexer) 存储在 AWS S3 存储桶中的 JSON 文件。NEAR Lake Framework 负责为用户下载和解析数据,但**读者需要支付费用**。关于技术限制和**费用估算**的更多详情,请参见[此处](../near-lake-framework#comparison-with-near-indexer-framework)。 + + +--- + +## 初始化 + +让我们从初始化 NEAR Lake Framework 开始。 + +### AWS 凭证 + +要访问 [NEAR Lake](../lake-framework/near-lake) 提供的数据,您需要提供有效的 AWS 凭证,以便 AWS 对 S3 使用收费。 + + +**AWS 凭证** + +请注意,使用您自己的 AWS 凭证是访问 [NEAR Lake](../lake-framework/near-lake) 生态系统所提供数据的唯一方式。 + + + +使用 aws configure 进行 AWS 默认配置文件配置,类似如下: + +```text +~/.aws/credentials +``` + +```text +[default] +aws_access_key_id= +aws_secret_access_key= +``` + +[AWS 文档:配置和凭证文件设置](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) + +### Lake 配置 + +要初始化 NEAR Lake Framework,我们需要提供以下基本设置: + +- S3 存储桶名称:存储 NEAR Lake 数据的存储桶。测试网的值为 `near-lake-data-testnet`,主网为 `near-lake-data-mainnet`。 +- S3 区域:S3 存储桶所在的 AWS 区域。默认值为 `eu-central-1`。 +- 起始区块高度:索引器将从该区块高度开始处理区块。 + + + + Rust 包提供了一种使用测试网/主网默认配置的方式,只需选择网络并设置起始区块高度(在示例中我们通过命令行参数传递)。 + + + + + 在 JavaScript/TypeScript 中,我们只需手动创建配置对象。区块高度通过命令行参数传递。 + + + + + 在 Python 中,我们将手动创建配置对象,然后设置 `s3_bucket_name` 和 `s3_region_name` 属性。区块高度通过命令行参数传递。 + + + + + +## 运行索引器 + +要运行索引器,我们需要创建一个函数来处理来自流的每条消息。在此函数中,我们可以访问区块数据并按需处理。 + + + + + + + + + + + + + +## 解析区块数据 + +从区块数据中,我们可以访问交易、其收据和操作。在本示例中,我们将查找特定合约上的 FunctionCall 操作,并记录每次调用的详细信息。 + + + + + + + + + + + + + +日志数据示例: +```bash +Block height: 214692896 + +Transaction hash HQsRK16ABEQWtKpHKWMbPgUreXCD95ZpKw47YkHxGsEc related to 6QpDUkd5n2xJ6mTjkdzXDbvMFo5mEzANS1t4Hfr76SAY executed with status "SuccessValue" +aha_6.testnet +{"contract_id":"3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"} +``` + + +**本文的版本信息** + +撰写本文时,本示例适用于以下版本: + +- near-lake-framework (Rust): `0.7.13` +- @near-lake/framework (JS): `0.1.5` +- near-lake-framework (Python): `0.1.3` +- rustc: `1.86.0` +- node: `22.18.0` +- python: `3.13.5` + + diff --git a/zh/data-infrastructure/tutorials/near-lake-state-changes-indexer.mdx b/zh/data-infrastructure/tutorials/near-lake-state-changes-indexer.mdx new file mode 100644 index 00000000000..df1f29bf642 --- /dev/null +++ b/zh/data-infrastructure/tutorials/near-lake-state-changes-indexer.mdx @@ -0,0 +1,36 @@ +--- +title: "教程:状态变更" +description: "本教程将引导您使用 NEAR Lake Framework 构建一个简单的索引器。该索引器将监听 StateChange 事件并打印有关账户变更的相关数据。" +--- + +本教程将引导您使用 NEAR Lake Framework 构建一个简单的索引器。该索引器将监听 `StateChange` 事件并打印有关账户变更的相关数据。 + + +**教程源代码** + +[`near-examples/near-lake-accounts-watcher`](https://github.com/near-examples/near-lake-accounts-watcher/tree/0.2.0):关于如何使用 NEAR Lake Framework 的视频教程源代码 + + + + +**版本 0.2.0** + +该视频基于 [`near-lake-framework`](/data-infrastructure/near-lake-framework) 版本 0.2.0 + +同时,我们持续更新源代码以与已发布 crate 的最新版本保持同步。 + + + +我们创建了一个视频教程,以庆祝 [NEAR Lake Framework](/data-infrastructure/near-lake-framework) 的发布公告。 + +在本教程中,您将构建一个索引器应用,用于监控影响所提供账户或账户列表的任何 `StateChange`。 + + diff --git a/zh/data-infrastructure/tutorials/nft-indexer.mdx b/zh/data-infrastructure/tutorials/nft-indexer.mdx new file mode 100644 index 00000000000..f9830ace339 --- /dev/null +++ b/zh/data-infrastructure/tutorials/nft-indexer.mdx @@ -0,0 +1,607 @@ +--- +sidebarTitle: NFT 索引器 +description: "学习使用 NEAR Lake Framework 构建简单的 NFT 索引器。" +--- + +本教程将引导您使用 [NEAR Lake Framework JS](/data-infrastructure/near-lake-framework) 的 JavaScript 版本构建一个简单的 NFT 索引器。该索引器将监听 `nft_mint` 事件并打印新铸造 NFT 的相关数据。 + +该索引器监听 `nft_mint` [事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) 并打印一些相关数据: +- 铸造发生所在 `Receipt` 的 `receiptId` +- 市场 +- NFT 所有者账户名 +- NFT 在市场上的链接 + +最终源代码可在 GitHub [`near-examples/near-lake-nft-indexer`](https://github.com/near-examples/near-lake-nft-indexer) 上获取。 + + +**教程源代码** + +[`near-examples/near-lake-nft-indexer`](https://github.com/near-examples/near-lake-nft-indexer):本教程的源代码 + + + +## 动机 + +NEAR 协议引入了一个很好的功能——[事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md)。事件允许合约开发者向 `ExecutionOutcomes` 添加标准化日志,从而允许自己或其他开发者通过 API 或索引器以更方便的方式读取这些日志。 + +事件有一个 `standard` 字段,与 NEP 对齐。在本教程中,我们将讨论 [NEP-171 非同质化代币标准](https://github.com/near/NEPs/discussions/171)。 + +本教程的目标是向您展示如何"监听"合约发出的事件以及如何从中受益。 + +作为示例,我们将构建一个索引器,监控所有按照 [NEP-171 事件](https://github.com/near/NEPs/tree/master/neps/nep-0256.md) 标准铸造的 NFT,假设我们是不想错过任何东西的收藏家。我们的索引器应该注意到每一个铸造的 NFT,并为我们提供一组基本数据,例如:在哪个收据中铸造,并向我们显示市场链接(在我们的示例中将介绍 Paras 和 Bitte/Mintbase)。 + +在本教程中,我们将使用 [NEAR Lake Framework](/data-infrastructure/near-lake-framework) 的 JS 版本。虽然 Rust 的概念相同,但我们希望向更多人展示构建自己的索引器并不那么复杂。 + +## 准备工作 + + +**凭证** + +请确保您已按照[凭证](./running-near-lake/credentials)页面的描述设置凭证。否则您将无法使代码正常工作。 + + + +您需要: + +- 已[安装并配置](https://nodejs.org/en/download/) `node` + +让我们创建项目文件夹 + +```bash +mkdir lake-nft-indexer && cd lake-nft-indexer +``` + +让我们添加 `package.json` + +```json title=package.json +{ + "name": "lake-nft-indexer", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "tsc && node index.js" + }, + "dependencies": { + "near-lake-framework": "^1.0.2" + }, + "devDependencies": { + "typescript": "^4.6.4" + } +} +``` + +您可能注意到我们将 `typescript` 添加为开发依赖项。让我们配置 TypeScript。我们需要为此创建 `tsconfig.json` 文件。 + +```json title=tsconfig.json +{ + "compilerOptions": { + "lib": [ + "ES2019", + "dom" + ] + } +} +``` + + +**ES2019 版本** + +请注意使用的 `ES2019` 版本。我们需要它是因为我们将在代码中使用 `.flatMap()` 和 `.flat()`。这些方法是在 `ES2019` 中引入的。当然您也可以使用更新的版本。 + + + +让我们在项目根目录中创建空的 `index.ts`,从而完成准备工作。 + +```bash +npm install +``` + +现在我们可以开始真正的工作了。 + +## 设置 NEAR Lake Framework + +在 `index.ts` 中,让我们从 `near-lake-framework` 导入 `startStream` 函数和 `types`: + +```ts title=index.ts +import { startStream, types } from 'near-lake-framework'; +``` + +在下面添加 `LakeConfig` 的实例化: + +```ts title=index.js +const lakeConfig: types.LakeConfig = { + s3BucketName: "near-lake-data-mainnet", + s3RegionName: "eu-central-1", + startBlockHeight: 66264389, +}; +``` + +关于配置的几点说明:我们为主网设置了 `s3BucketName`,默认的 `s3RegionName` 以及一个较新的区块高度作为 `startBlockHeight`。您可以访问 [NEAR Explorer](https://nearblocks.io) 获取**最新**的区块高度用于您的设置,也可以使用我们使用的相同值。 + +现在我们需要创建一个回调函数,用于处理我们的索引器接收到的 `StreamerMessage`。 + +```ts title=index.ts +async function handleStreamerMessage( + streamerMessage: types.StreamerMessage +): Promise { + +} +``` + + +**回调函数要求** + +在 `near-lake-framework` JS 库中,处理程序必须以回调函数的形式呈现。此函数必须: +- 是异步的 +- 接受类型为 `StreamerMessage` 的参数 +- 不返回任何内容(`void`) + + + +以及在 `index.ts` 末尾实际启动我们的索引器 + +```ts title=index.ts +(async () => { + await startStream(lakeConfig, handleStreamerMessage); +})(); +``` + +此时最终的 `index.ts` 应如下所示: + +```ts title=index.ts +import { startStream, types } from 'near-lake-framework'; + +const lakeConfig: types.LakeConfig = { + s3BucketName: "near-lake-data-mainnet", + s3RegionName: "eu-central-1", + startBlockHeight: 66264389, +}; + +async function handleStreamerMessage( + streamerMessage: types.StreamerMessage +): Promise { + +} + +(async () => { + await startStream(lakeConfig, handleStreamerMessage); +})(); +``` + +## 事件及其捕获位置 + +首先让我们找出可以在哪里捕获事件。我们希望您熟悉 [NEAR 区块链中的数据流](/protocol/data-flow/near-data-flow),但让我们回顾一下: +- 铸造 NFT 是 NFT 合约中的一个操作(不管是哪个合约) +- 操作位于 `Receipt`(收据)中 +- 收据执行的结果是 `ExecutionOutcome`(执行结果) +- `ExecutionOutcome` 反过来捕获合约"打印"的日志 +- [事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) 构建在日志之上 + +这让我们意识到,我们只能监控 ExecutionOutcomes,忽略 `StreamerMessage` 带来的其他所有内容。 + +另外,我们需要定义一个接口来捕获事件。让我们从 [Events Nomicon 页面](https://github.com/near/NEPs/tree/master/neps/nep-0297.md#events) 复制接口定义并粘贴到 `handleStreamerMessage` 函数之前。 + +```ts title=index.ts +interface EventLogData { + standard: string, + version: string, + event: string, + data?: unknown, +}; +``` + +## 只捕获我们需要的数据 + +在我们在[准备工作](#preparation)部分创建的回调函数 `handleStreamerMessage` 中,让我们开始过滤我们需要的数据: + +```ts title=index.ts +async function handleStreamerMessage( + streamerMessage: types.StreamerMessage +): Promise { + const relevantOutcomes = streamerMessage + .shards + .flatMap(shard => shard.receiptExecutionOutcomes) + +} +``` + +我们遍历了所有 `Shards`,并将所有 `ExecutionOutcomes` 的列表收集到一个单一列表中(在我们的案例中,我们不关心铸造发生在哪个分片上)。 + +现在我们只想处理那些包含事件格式日志的 `ExecutionOutcomes`。根据[事件文档](https://github.com/near/NEPs/tree/master/neps/nep-0297.md#events),此类日志以 `EVENT_JSON:` 开头。 + +另外,我们不需要 ExecutionOutcome 中的所有数据,让我们处理一下: + +```ts title=index.ts +async function handleStreamerMessage( + streamerMessage: types.StreamerMessage +): Promise { + const relevantOutcomes = streamerMessage + .shards + .flatMap(shard => shard.receiptExecutionOutcomes) + .map(outcome => ({ + receipt: { + id: outcome.receipt.receiptId, + receiverId: outcome.receipt.receiverId, + }, + events: outcome.executionOutcome.outcome.logs.map( + (log: string): EventLogData => { + const [_, probablyEvent] = log.match(/^EVENT_JSON:(.*)$/) ?? [] + try { + return JSON.parse(probablyEvent) + } catch (e) { + return + } + } + ) + .filter(event => event !== undefined) + })) + +} +``` + +让我们解释一下我们在做什么: + +1. 我们遍历 ExecutionOutcomes +2. 我们构建一个对象列表,包含 `receipt`(其 id 和接收者)以及包含事件的 `events` +3. 为了收集事件,我们遍历 ExecutionOutcome 的日志,尝试使用正则表达式解析事件。如果无法解析 `EventLogData`,我们返回 `undefined` +4. 最后,一旦收集了 `events` 列表,我们过滤掉 `undefined` + +好的,现在我们只有一个对象列表,其中包含一些收据数据和成功解析的 `EventLogData` 列表。 + +我们的索引器目标是返回有关遵循 NEP-171 标准的铸造 NFT 的有用数据。我们需要删除不相关的标准事件: + +```ts title=index.ts + .filter(relevantOutcome => + relevantOutcome.events.some( + event => event.standard === "nep171" && event.event === "nft_mint" + ) + ) +``` + +## 即将完成 + +到目前为止,我们已经收集了符合我们要求的所有内容。 + +我们可以在 `handleStreamerMessage` 的末尾打印所有内容: + +```ts title=index.ts + relevantOutcomes.length && console.dir(relevantOutcomes, { depth: 10 }) +``` + +`handleStreamerMessage` 函数的最终形态: + +```ts title=index.ts +async function handleStreamerMessage( + streamerMessage: types.StreamerMessage +): Promise { + const relevantOutcomes = streamerMessage + .shards + .flatMap(shard => shard.receiptExecutionOutcomes) + .map(outcome => ({ + receipt: { + id: outcome.receipt.receiptId, + receiverId: outcome.receipt.receiverId, + }, + events: outcome.executionOutcome.outcome.logs.map( + (log: string): EventLogData => { + const [_, probablyEvent] = log.match(/^EVENT_JSON:(.*)$/) ?? [] + try { + return JSON.parse(probablyEvent) + } catch (e) { + return + } + } + ) + .filter(event => event !== undefined) + })) + .filter(relevantOutcome => + relevantOutcome.events.some( + event => event.standard === "nep171" && event.event === "nft_mint" + ) + ) + + relevantOutcomes.length && console.dir(relevantOutcomes, { depth: 10 }) +} + +``` + +如果我们运行索引器,我们将捕获 `nft_mint` 事件并在终端打印数据。 + +```bash +npm run start +``` + + + +运行索引器时遇到问题?请检查您是否跳过了[凭证](./running-near-lake/credentials)部分 :) + + + +先别急!还记得我们谈到要提供市场链接以查看铸造的代币吗?我们将尽可能地用链接扩展我们的数据。至少我们会向您展示如何处理在 Paras 和 Bitte/Mintbase 上铸造的 NFT。 + +## 为在 Paras 和 Mintbase 上铸造的 NFT 生成链接 + +此时我们有一个对象数组,我们实时构建了这些对象,它们暴露了收据、执行状态和事件日志。我们确实知道我们现在拥有的所有数据都与我们相关,并且我们希望至少为我们知道的那些市场上的铸造 NFT 扩展链接。 + +我们了解并喜爱 Paras 和 Mintbase。 + +### Paras 代币 URL + +我们已经为您做了研究,以下是 Paras 上代币 URL 的构建方式: + +```text +https://paras.id/token/[1]::[2]/[3] +``` + +其中: + +- [1] - Paras 合约地址(`x.paras.near`) +- [2] - `token_id` 的第一部分(Paras 的 `EventLogData.data` 是一个包含 `token_ids` 键的对象数组。这些 ID 由数字和列 `:` 表示) +- [3] - `token_id` 本身 + +示例: + +```text +https://paras.id/token/x.paras.near::387427/387427:373 +``` + +让我们在 `interface EventLogData` 之后添加接口以备后用: + +```ts +interface ParasEventLogData { + owner_id: string, + token_ids: string[], +}; +``` + +### Mintbase 代币 URL + +我们再次为您做了研究: + +```text +https://bitte.ai/thing/[1]:[2] +``` + +其中: + +- [1] - `meta_id`(Mintbase 的 `EventLogData.data` 是一个包含 `meta_id` 的字符串化 JSON 数组) +- [2] - 商店合约账户地址(基本上是收据的接收者 ID) + +示例: + +```text +https://bitte.ai/thing/70eES-icwSw9iPIkUluMHOV055pKTTgQgTiXtwy3Xus:vnartistsdao.mintbase1.near +``` + +让我们在 `interface EventLogData` 之后添加接口以备后用: + +```ts +interface MintbaseEventLogData { + owner_id: string, + memo: string, +} +``` + +现在是添加另一个 `.map()` 的好时机,但可能太多了。所以让我们使用 for 循环来生成我们想要打印的输出数据。 + +```ts title=index.ts +let output = [] +for (let relevantOutcome of relevantOutcomes) { + let marketplace = "Unknown" + let nfts = [] +} +``` + +我们将打印市场名称、收据 id(以便您可以在 [NEAR Explorer](https://nearblocks.io) 上搜索)以及 NFT 链接列表和所有者账户名。 + +让我们开始生成链接。是时候向 [Riqi](https://twitter.com/hdriqi) 打个招呼了(因为我们可以): + +```ts title=index.ts +let output = [] + for (let relevantOutcome of relevantOutcomes) { + let marketplace = "Unknown" + let nfts = [] + if (relevantOutcome.receipt.receiverId.endsWith(".paras.near")) { + marketplace = "Paras" + nfts = relevantOutcome.events.flatMap(event => { + return (event.data as ParasEventLogData[]) + .map(eventData => ({ + owner: eventData.owner_id, + links: eventData.token_ids.map( + tokenId => `https://paras.id/token/${relevantOutcome.receipt.receiverId}::${tokenId.split(":")[0]}/${tokenId}` + ) + }) + ) + }) + } +``` + +关于这里发生的事情说几句。如果收据的接收者账户名以 `.paras.near` 结尾(例如 `x.paras.near`),我们假设它来自 Paras 市场,因此我们更改相应的变量。 + +之后,我们使用之前定义的 `ParasEventLogData` 遍历事件及其 `data`,收集包含 NFT 所有者和 NFT 链接的对象列表。 + +Mintbase 的处理,我们希望 [Nate](https://twitter.com/nategeier) 和他的团队已经迁移到 [NEAR Lake Framework](./near-lake-framework),向他们打个招呼并生成链接: + +```ts title=index.ts + } else if (relevantOutcome.receipt.receiverId.match(/\.mintbase\d+\.near$/)) { + marketplace = "Mintbase" + nfts = relevantOutcome.events.flatMap(event => { + return (event.data as MintbaseEventLogData[]) + .map(eventData => { + const memo = JSON.parse(eventData.memo) + return { + owner: eventData.owner_id, + links: [`https://bitte.ai/thing/${memo["meta_id"]}:${relevantOutcome.receipt.receiverId}`] + } + }) + }) + } +``` + +与 Paras 几乎相同,但稍微复杂一些。Mintbase 市场的本质是它不是单一的市场!每个 Mintbase 用户都有自己的商店和独立的合约。这些合约地址似乎遵循相同的原则,它们以 `.mintbaseN.near` 结尾,其中 `N` 是数字(例如 `nate.mintbase1.near`)。 + +确定 ExecutionOutcome 接收者来自 Mintbase 后,我们做与 Paras 相同的事情: + +1. 更改 `marketplace` 变量 +2. 收集包含所有者和生成链接的 NFT 列表 + +如果我们无法确定市场,我们仍然想返回一些内容,所以让我们按原样返回事件数据: + +```ts title=index.ts + } else { + nfts = relevantOutcome.events.flatMap(event => event.data) + } +``` + +是时候将收集的数据推送到 `output` 中了 + +```ts title=index.ts + output.push({ + receiptId: relevantOutcome.receipt.id, + marketplace, + nfts, + }) +``` + +并让它将输出打印到终端: + +```ts title=index.ts +if (output.length) { + console.log(`We caught freshly minted NFTs!`) + console.dir(output, { depth: 5 }) +} +``` + +全部放在一起: + +```ts title=index.ts + let output = [] + for (let relevantOutcome of relevantOutcomes) { + let marketplace = "Unknown" + let nfts = [] + if (relevantOutcome.receipt.receiverId.endsWith(".paras.near")) { + marketplace = "Paras" + nfts = relevantOutcome.events.flatMap(event => { + return (event.data as ParasEventLogData[]) + .map(eventData => ({ + owner: eventData.owner_id, + links: eventData.token_ids.map( + tokenId => `https://paras.id/token/${relevantOutcome.receipt.receiverId}::${tokenId.split(":")[0]}/${tokenId}` + ) + }) + ) + }) + } else if (relevantOutcome.receipt.receiverId.match(/\.mintbase\d+\.near$/)) { + marketplace = "Mintbase" + nfts = relevantOutcome.events.flatMap(event => { + return (event.data as MintbaseEventLogData[]) + .map(eventData => { + const memo = JSON.parse(eventData.memo) + return { + owner: eventData.owner_id, + links: [`https://bitte.ai/thing/${memo["meta_id"]}:${relevantOutcome.receipt.receiverId}`] + } + }) + }) + } else { + nfts = relevantOutcome.events.flatMap(event => event.data) + } + output.push({ + receiptId: relevantOutcome.receipt.id, + marketplace, + createdOn, + nfts, + }) + } + if (output.length) { + console.log(`We caught freshly minted NFTs!`) + console.dir(output, { depth: 5 }) + } +``` + +好了,NFT 铸造的日期和时间怎么办?让我们在 `handleStreamerMessage` 函数的开头添加 + +```ts title=index.ts +const createdOn = new Date(streamerMessage.block.header.timestamp / 1000000) +``` + +更新 `output.push()` 表达式: + +```ts title=index.ts +output.push({ + receiptId: relevantOutcome.receipt.id, + marketplace, + createdOn, + nfts, +}) +``` + +现在就这样了。运行索引器以监控 NFT 铸造,不要错过任何东西。 + +```bash +npm run start +``` + + + +运行索引器时遇到问题?请检查您是否跳过了[凭证](./running-near-lake/credentials)部分 :) + + + +输出示例: + +```text +We caught freshly minted NFTs! +[ + { + receiptId: '2y5XzzL1EEAxgq8EW3es2r1dLLkcecC6pDFHR12osCGk', + marketplace: 'Paras', + createdOn: 2022-05-24T09:35:57.831Z, + nfts: [ + { + owner: 'dccc.near', + links: [ 'https://paras.id/token/x.paras.near::398089/398089:17' ] + } + ] + } +] +We caught freshly minted NFTs! +[ + { + receiptId: 'BAVZ92XdbkAPX4DkqW5gjCvrhLX6kGq8nD8HkhQFVt5q', + marketplace: 'Mintbase', + createdOn: 2022-05-24T09:36:00.411Z, + nfts: [ + { + owner: 'chiming.near', + links: [ + 'https://bitte.ai/thing/HOTcn6LTo3qTq8bUbB7VwA1GfSDYx2fYOqvP0L_N5Es:vnartistsdao.mintbase1.near' + ] + } + ] + } +] +``` + +## 结论 + +真是一段旅程,对吧?让我们总结一下我们所做的事情: + +- 您已经了解了[事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) +- 现在您了解如何跟踪事件 +- 知道作为合约开发者可以使用事件并发出自己的事件,现在您知道如何创建跟踪这些事件的索引器 +- 我们更深入地了解了 NFT 铸造过程,您可以进一步实验,了解如何跟踪 `nft_transfer` 事件 + +本教程的内容可以推广到任何遵循[事件格式](https://github.com/near/NEPs/tree/master/neps/nep-0297.md)的事件。 + +更不用说您现在拥有了一个专用索引器,可以发现最新铸造的 NFT,成为最早收集它们的人。 + +冲冲冲 🦈 + + +**教程源代码** + +[`near-examples/near-lake-nft-indexer`](https://github.com/near-examples/near-lake-nft-indexer):本教程的源代码 + + diff --git a/zh/data-infrastructure/tutorials/python-nft-indexer.mdx b/zh/data-infrastructure/tutorials/python-nft-indexer.mdx new file mode 100644 index 00000000000..15d4d3ea76e --- /dev/null +++ b/zh/data-infrastructure/tutorials/python-nft-indexer.mdx @@ -0,0 +1,485 @@ +--- +sidebarTitle: Python NFT 索引器 +description: "学习使用 NEAR Lake Framework 构建 Python NFT 索引器" +--- + +本教程展示如何使用 [NEAR Lake Framework for Python](/data-infrastructure/near-lake-framework) 构建一个功能完整的 NFT 索引器,该索引器监控 `nft_mint` [事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) 并输出相关详情,例如铸造发生所在 `Receipt` 的 `receipt_id`、市场、NFT 所有者的账户名以及 NFT 在市场上的链接。 + +最终源代码可在 GitHub [`frolvanya/near-lake-nft-indexer`](https://github.com/frolvanya/near-lake-nft-indexer) 上获取。 + + +**教程源代码** + +[`frolvanya/near-lake-nft-indexer`](https://github.com/frolvanya/near-lake-nft-indexer):本教程的源代码 + + + +## 动机 + +NEAR 协议引入了一个很好的功能——[事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md)。事件允许合约开发者向 `ExecutionOutcomes` 添加标准化日志,从而允许自己或其他开发者通过 API 或索引器以更方便的方式读取这些日志。 + +事件有一个 `standard` 字段,与 NEP 对齐。在本教程中,我们将讨论 [NEP-171 非同质化代币标准](https://github.com/near/NEPs/discussions/171)。 + +本教程的目标是向您展示如何"监听"合约发出的事件以及如何从中受益。 + +作为示例,我们将构建一个索引器,监控所有按照 [NEP-171 事件](https://github.com/near/NEPs/tree/master/neps/nep-0256.md) 标准铸造的 NFT,假设我们是不想错过任何东西的收藏家。我们的索引器应该注意到每一个铸造的 NFT,并为我们提供一组基本数据,例如:在哪个收据中铸造,并向我们显示市场链接(在我们的示例中将介绍 [Paras](https://paras.id) 和 [Mintbase/Bitte](https://bitte.ai/))。 + +在本教程中,我们将使用 [NEAR Lake Framework](/data-infrastructure/near-lake-framework) 的 Python 版本。虽然 Rust 的概念相同,但我们希望向更多人展示构建自己的索引器并不那么复杂。 + +## 准备工作 + + +**凭证** + +请确保您已按照[凭证](./running-near-lake/credentials)页面的描述设置凭证。否则您将无法使代码正常工作。 + + + +让我们创建项目文件夹 + +```bash +mkdir lake-nft-indexer && cd lake-nft-indexer +touch main.py +``` + +## 设置 NEAR Lake Framework + +在 `main.py` 中,让我们从 `near-lake-framework` 导入 `stream` 函数和 `near_primitives`: + +```python title=main.py +from near_lake_framework import near_primitives, LakeConfig, streamer +``` + +添加主函数 + +```python title=main.py +async def main(): + print("Starting NFT indexer") +``` + +在下面添加 `LakeConfig` 的实例化: + +```python title=main.py +config = LakeConfig.mainnet() +config.start_block_height = 69030747 +config.aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID") +config.aws_secret_key = os.getenv("AWS_SECRET_ACCESS_KEY") +``` + +关于配置的几点说明:函数 `mainnet()` 已为主网设置了 `s3_bucket_name`、`s3_region_name`。 +您可以访问 [NEAR Explorer](https://nearblocks.io) 获取**最新**的区块高度来设置 `config.start_block_height`。 + +让我们用 `config` 调用 `streamer` 函数 + +```python title=main.py +stream_handle, streamer_messages_queue = streamer(config) +while True: + streamer_message = await streamer_messages_queue.get() +``` + +以及在 `main.py` 末尾实际启动我们的索引器 + +```python title=main.py +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) +``` + +此时最终的 `main.py` 应如下所示: + +```python title=main.py +from near_lake_framework import LakeConfig, streamer, near_primitives +async def main(): + print("Starting NFT indexer") + + config = LakeConfig.mainnet() + config.start_block_height = 69030747 + config.aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID") + config.aws_secret_key = os.getenv("AWS_SECRET_ACCESS_KEY") + + stream_handle, streamer_messages_queue = streamer(config) + while True: + streamer_message = await streamer_messages_queue.get() + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) +``` + +现在我们需要创建一个回调函数,用于处理我们的索引器接收到的 `StreamerMessage`。 + +```python title=main.py +async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + pass +``` + +## 事件及其捕获位置 + +首先让我们找出可以在哪里捕获事件。我们希望您熟悉 [NEAR 区块链中的数据流](/protocol/data-flow/near-data-flow),但让我们回顾一下: +- 铸造 NFT 是 NFT 合约中的一个操作(不管是哪个合约) +- 操作位于 `Receipt`(收据)中 +- 收据执行的结果是 `ExecutionOutcome`(执行结果) +- `ExecutionOutcome` 反过来捕获合约"打印"的日志 +- [事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) 构建在日志之上 + +这让我们意识到,我们只能监控 ExecutionOutcomes,忽略 `StreamerMessage` 带来的其他所有内容。 + +## 只捕获我们需要的数据 + +在我们在[准备工作](#preparation)部分创建的回调函数 `handle_streamer_message` 中,让我们开始过滤我们需要的数据: + +```python title=main.py +async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + for shard in streamer_message.shards: + for receipt_execution_outcome in shard.receipt_execution_outcomes: + for log in receipt_execution_outcome.execution_outcome.outcome.logs: + pass +``` + +我们遍历了所有 `Shards` 的 ExecutionOutcomes 的日志(在我们的案例中,我们不关心铸造发生在哪个分片上)。 + +现在我们只想处理那些包含事件格式日志的 ExecutionOutcomes。根据[事件文档](https://github.com/near/NEPs/tree/master/neps/nep-0297.md#events),此类日志以 `EVENT_JSON:` 开头。 + +```python title=main.py +async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + for shard in streamer_message.shards: + for receipt_execution_outcome in shard.receipt_execution_outcomes: + for log in receipt_execution_outcome.execution_outcome.outcome.logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed_log = json.loads(log[len("EVENT_JSON:") :]) + except json.JSONDecodeError: + print( + f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" + ) + continue +``` + +让我们解释一下我们在做什么: + +1. 我们遍历 ExecutionOutcomes 中的日志 +2. 我们过滤包含事件格式日志的 ExecutionOutcomes +3. 为了收集事件,我们遍历 ExecutionOutcome 的日志,尝试使用 `json.loads` 解析事件 + +我们的索引器目标是返回有关遵循 NEP-171 标准的铸造 NFT 的有用数据。我们需要删除不相关的标准事件: + +```python title=main.py + if ( + parsed_log.get("standard") != "nep171" + or parsed_log.get("event") != "nft_mint" + ): + continue +``` + +## 即将完成 + +到目前为止,我们已经收集了符合我们要求的所有内容。 + +`handle_streamer_message` 函数的最终形态: + +```python title=main.py +async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + for shard in streamer_message.shards: + for receipt_execution_outcome in shard.receipt_execution_outcomes: + for log in receipt_execution_outcome.execution_outcome.outcome.logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed_log = json.loads(log[len("EVENT_JSON:") :]) + except json.JSONDecodeError: + print( + f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" + ) + continue + + if ( + parsed_log.get("standard") != "nep171" + or parsed_log.get("event") != "nft_mint" + ): + continue + + print(parsed_log) +``` + +现在让我们在 `main` 函数的循环中调用 `handle_streamer_message` + +```python title=main.py +await handle_streamer_message(streamer_message) +``` + +如果我们运行索引器,我们将捕获 `nft_mint` 事件并在终端打印日志。 + +```bash +python3 main.py +``` + + + +运行索引器时遇到问题?请检查您是否跳过了[凭证](./running-near-lake/credentials)部分 :) + + + +先别急!还记得我们谈到要提供市场链接以查看铸造的代币吗?我们将尽可能地用链接扩展我们的数据。至少我们会向您展示如何处理在 [Paras](https://paras.id) 和 [Mintbase/Bitte](https://bitte.ai/) 上铸造的 NFT。 + +## 为在 Paras 和 Mintbase 上铸造的 NFT 生成链接 + +此时我们可以访问遵循 NEP-171 标准的日志。我们确实知道我们现在拥有的所有数据都与我们相关,并且我们希望至少为我们知道的那些市场上的铸造 NFT 扩展链接。 + +我们了解并喜爱 Paras 和 Mintbase。 + +### Paras 代币 URL + +我们已经为您做了研究,以下是 Paras 上代币 URL 的构建方式: + +```text +https://paras.id/token/[1]::[2]/[3] +``` + +其中: + +- [1] - Paras 合约地址(`x.paras.near`) +- [2] - `token_id` 的第一部分(Paras 的 `parsed_log["data"]` 是一个包含 `token_ids` 键的对象数组。这些 ID 由数字和列 `:` 表示) +- [3] - `token_id` 本身 + +示例: + +```text +https://paras.id/token/x.paras.near::387427/387427:373 +``` + +### Mintbase 代币 URL + +我们再次为您做了研究: + +```text +https://bitte.ai//thing/[1]:[2] +``` + +其中: + +- [1] - `meta_id`(Mintbase 的 `parsed_log["data"]` 是一个包含 `meta_id` 的字符串化 JSON 数组) +- [2] - 商店合约账户地址(基本上是收据的接收者 ID) + +示例: + +```text +https://bitte.ai/thing/70eES-icwSw9iPIkUluMHOV055pKTTgQgTiXtwy3Xus:vnartistsdao.mintbase1.near +``` + +让我们开始生成链接: + +```python title=main.py +def format_paras_nfts(data, receipt_execution_outcome): + links = [] + + for data_element in data: + for token_id in data_element.get("token_ids", []): + first_part_of_token_id = token_id.split(":")[0] + links.append( + f"https://paras.id/token/{receipt_execution_outcome.receipt.receiver_id}::{first_part_of_token_id}/{token_id}" + ) + + return {"owner": data[0].get("owner_id"), "links": links} + +def format_mintbase_nfts(data, receipt_execution_outcome): + links = [] + for data_block in data: + try: + memo = json.loads(data_block.get("memo")) + except json.JSONDecodeError: + print( + f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nMemo: `{memo}`\nError during parsing Mintbase memo from JSON string to dict" + ) + return + + meta_id = memo.get("meta_id") + links.append( + f"https://bitte.ai/thing/{meta_id}:{receipt_execution_outcome.receipt.receiver_id}" + ) + + return {"owner": data[0].get("owner_id"), "links": links} +``` + +我们将打印 receipt_id(以便您可以在 [NEAR Explorer](https://nearblocks.io) 上搜索),市场名称以及 NFT 链接列表和所有者账户名。 + +```python title=main.py +if receipt_execution_outcome.receipt.receiver_id.endswith( + ".paras.near" +): + output = { + "receipt_id": receipt_execution_outcome.receipt.receipt_id, + "marketplace": "Paras", + "nfts": format_paras_nfts( + parsed_log["data"], receipt_execution_outcome + ), + } +``` + +关于这里发生的事情说几句。如果收据的接收者账户名以 `.paras.near` 结尾(例如 `x.paras.near`),我们假设它来自 Paras 市场,因此我们更改相应的变量。 + +Mintbase 的处理,我们希望 [Nate](https://twitter.com/nategeier) 和他的团队已经迁移到 [NEAR Lake Framework](./near-lake-framework),向他们打个招呼并生成链接: + +```python title=main.py + elif re.search( + ".mintbase\d+.near", receipt_execution_outcome.receipt.receiver_id + ): + output = { + "receipt_id": receipt_execution_outcome.receipt.receipt_id, + "marketplace": "Mintbase", + "nfts": format_mintbase_nfts( + parsed_log["data"], receipt_execution_outcome + ), + } + else: + continue +``` + +与 Paras 几乎相同,但稍微复杂一些。Mintbase 市场的本质是它不是单一的市场!每个 Mintbase 用户都有自己的商店和独立的合约。这些合约地址似乎遵循相同的原则,它们以 `.mintbaseN.near` 结尾,其中 `N` 是数字(例如 `nate.mintbase1.near`)。 + +确定 ExecutionOutcome 接收者来自 Mintbase 后,我们做与 Paras 相同的事情: + +1. 将 `marketplace` 变量设置为 Mintbase +2. 收集包含所有者和生成链接的 NFT 列表 + +并让它将输出打印到终端: + +```python title=main.py +print(json.dumps(output, indent=4)) +``` + +全部放在一起: + +```python title=main.py +def format_paras_nfts(data, receipt_execution_outcome): + links = [] + + for data_element in data: + for token_id in data_element.get("token_ids", []): + first_part_of_token_id = token_id.split(":")[0] + links.append( + f"https://paras.id/token/{receipt_execution_outcome.receipt.receiver_id}::{first_part_of_token_id}/{token_id}" + ) + + return {"owner": data[0].get("owner_id"), "links": links} + +def format_mintbase_nfts(data, receipt_execution_outcome): + links = [] + for data_block in data: + try: + memo = json.loads(data_block.get("memo")) + except json.JSONDecodeError: + print( + f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nMemo: `{memo}`\nError during parsing Mintbase memo from JSON string to dict" + ) + return + + meta_id = memo.get("meta_id") + links.append( + f"https://bitte.ai/thing/{meta_id}:{receipt_execution_outcome.receipt.receiver_id}" + ) + + return {"owner": data[0].get("owner_id"), "links": links} + +async def handle_streamer_message(streamer_message: near_primitives.StreamerMessage): + for shard in streamer_message.shards: + for receipt_execution_outcome in shard.receipt_execution_outcomes: + for log in receipt_execution_outcome.execution_outcome.outcome.logs: + if not log.startswith("EVENT_JSON:"): + continue + try: + parsed_log = json.loads(log[len("EVENT_JSON:") :]) + except json.JSONDecodeError: + print( + f"Receipt ID: `{receipt_execution_outcome.receipt.receipt_id}`\nError during parsing logs from JSON string to dict" + ) + continue + + if ( + parsed_log.get("standard") != "nep171" + or parsed_log.get("event") != "nft_mint" + ): + continue + + if receipt_execution_outcome.receipt.receiver_id.endswith( + ".paras.near" + ): + output = { + "receipt_id": receipt_execution_outcome.receipt.receipt_id, + "marketplace": "Paras", + "nfts": format_paras_nfts( + parsed_log["data"], receipt_execution_outcome + ), + } + elif re.search( + ".mintbase\d+.near", receipt_execution_outcome.receipt.receiver_id + ): + output = { + "receipt_id": receipt_execution_outcome.receipt.receipt_id, + "marketplace": "Mintbase", + "nfts": format_mintbase_nfts( + parsed_log["data"], receipt_execution_outcome + ), + } + else: + continue + + print(json.dumps(output, indent=4)) +``` + +现在就这样了。运行索引器以监控 NFT 铸造,不要错过任何东西。 + +```bash +python3 main.py +``` + + + +运行索引器时遇到问题?请检查您是否跳过了[凭证](./running-near-lake/credentials)部分 :) + + + +输出示例: + +```text +{ + "receipt_id": "8rMK8rxb3WmFcSfM3ahFoeeoBF92pad3tpsqKoSWurr2", + "marketplace": "Mintbase", + "nfts": { + "owner": "vn-artists-dao.near", + "links": [ + "https://bitte.ai/thing/aqdCBHB9_2XZY7pwXRRu5rGDeLQl7Q8KgNud1wKgnGo:vnartistsdao.mintbase1.near" + ] + } +} +{ + "receipt_id": "ArRh94Fe1LKF9yPrAdzrMozWoxMVQbEW2Z2Zf4fsSsce", + "marketplace": "Paras", + "nfts": { + "owner": "eeaeb516e0945893ac01eaf547f499abdbd344831c5fcbefa1a5c0a9f303cc5c", + "links": [ + "https://paras.id/token/x.paras.near::432714/432714:1" + ] + } +} +``` + +## 结论 + +真是一段旅程,对吧?让我们总结一下我们所做的事情: + +- 您已经了解了[事件](https://github.com/near/NEPs/tree/master/neps/nep-0297.md) +- 现在您了解如何跟踪事件 +- 知道作为合约开发者可以使用事件并发出自己的事件,现在您知道如何创建跟踪这些事件的索引器 +- 我们更深入地了解了 NFT 铸造过程,您可以进一步实验,了解如何跟踪 `nft_transfer` 事件 + +本教程的内容可以推广到任何遵循[事件格式](https://github.com/near/NEPs/tree/master/neps/nep-0297.md)的事件。 + +更不用说您现在拥有了一个专用索引器,可以发现最新铸造的 NFT,成为最早收集它们的人。 + +冲冲冲 🦈 + + +**教程源代码** + +[`near-examples/near-lake-nft-indexer`](https://github.com/near-examples/near-lake-nft-indexer):本教程的源代码 + + diff --git a/zh/data-infrastructure/tutorials/running-near-lake/credentials.mdx b/zh/data-infrastructure/tutorials/running-near-lake/credentials.mdx new file mode 100644 index 00000000000..44fe047424c --- /dev/null +++ b/zh/data-infrastructure/tutorials/running-near-lake/credentials.mdx @@ -0,0 +1,41 @@ +--- +title: 凭证 +description: "了解如何提供 AWS 凭证以访问 NEAR Lake 数据" +--- + +要访问 [NEAR Lake](../../near-lake-framework) 提供的数据,您需要提供有效的 AWS 凭证,以便 AWS 对 S3 使用收费。 + + +**AWS 凭证** + +请注意,使用您自己的 AWS 凭证是访问 [NEAR Lake](../../near-lake-framework) 生态系统所提供数据的唯一方式。 + + + +### AWS S3 凭证 + +要能够从 AWS S3 存储桶获取对象,您需要提供 AWS 凭证。 + +使用 aws configure 进行 AWS 默认配置文件配置,类似如下: + +```text +~/.aws/credentials +``` + +```text +[default] +aws_access_key_id= +aws_secret_access_key= +``` + +[AWS 文档:配置和凭证文件设置](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) + +#### 环境变量 + +或者,您可以通过具有固定名称的环境变量提供 AWS 凭证: + +```text +$ export AWS_ACCESS_KEY_ID= +$ AWS_SECRET_ACCESS_KEY= +$ AWS_DEFAULT_REGION=eu-central-1 +``` diff --git a/zh/data-infrastructure/tutorials/running-near-lake/lake-start-options.mdx b/zh/data-infrastructure/tutorials/running-near-lake/lake-start-options.mdx new file mode 100644 index 00000000000..677847ce416 --- /dev/null +++ b/zh/data-infrastructure/tutorials/running-near-lake/lake-start-options.mdx @@ -0,0 +1,587 @@ +--- +title: 启动选项 +description: "了解如何使用 NEAR Lake Framework 创建索引器。" +--- + +本教程将引导您创建一个使用 NEAR Lake Framework 的简单索引器,该索引器可以从指定的区块高度、最新的最终区块或最后索引的区块开始。 + +### 启动选项 + +使用 NEAR Lake Framework 启动索引器有三种选项: + +- 从指定的区块高度开始(开箱即用) + ```bash + ./target/release/indexer mainnet from-block 65359506 + ``` +- 从网络的最新最终区块开始 + ```bash + ./target/release/indexer mainnet from-latest + ``` +- 从索引器上次中断前最后索引的区块开始 + ```bash + ./target/release/indexer mainnet from-interruption + ``` + +--- + +## 动机 + +确定您的项目是否需要索引器并创建一个,意味着您只涵盖了一方面——开发。 + +还有另一个重要方面——维护。这包括: +- 需要使用更新版本的依赖项升级索引器 +- 需要使用您开发的新功能更新索引器 +- 您的服务器需要一些维护 +- 发生了事故 +- 等等 + +在几乎所有上述情况中,您可能希望不仅从需要提供的特定区块启动或重启索引器,还可以从它停止的区块,或从网络中最新的最终区块开始。 + +[NEAR Lake Framework](/data-infrastructure/near-lake-framework) 不提供这些选项。实际上,我们有意不为库添加这些启动索引器的选项。 + + +**设计意图** + +我们希望将 [NEAR Lake Framework](/data-infrastructure/near-lake-framework) crate 保持在尽可能小的范围内。该库的目标是做一项工作,并允许在 crate 外部添加任何功能。 + + + +不过,从最新区块或从上次索引的区块之后的区块启动索引器的功能可能非常有用。 + +此外,在[四月数据平台社区会议](https://github.com/near/indexers-docs/blob/main/blog/2022-05-11-community-meeting-recordx.mdx)期间,有人询问我们是否计划将此功能添加到库中。我们承诺创建一个教程,展示如何自行实现。现在它就在这里。 + +--- + +## 准备工作 + +在本教程中,我们不会将注意力集中在索引器本身,而是集中在启动选项上。 + + + +为了简化教程中的代码示例,我们将整个应用程序写在单个文件 `src/main.rs` 中。 + +**请不要将其视为设计建议。我们这样做只是为了简单起见** + + + +让我们准备一个具有基本依赖项的项目,以便我们可以专注于本教程的主要目标。 + +创建一个新的 Rust 项目 + +```bash +cargo new --bin indexer && cd indexer +``` + +将 `Cargo.toml` 文件的内容替换为: + +```toml title=Cargo.toml +[package] +name = "indexer" +version = "0.1.0" +edition = "2021" +rust-version = "1.60.0" + +[dependencies] +clap = { version = "3.1.6", features = ["derive"] } +futures = "0.3.5" +itertools = "0.9.0" +tokio = { version = "1.1", features = ["sync", "time", "macros", "rt-multi-thread"] } +tokio-stream = { version = "0.1" } +tracing = "0.1.13" +tracing-subscriber = "0.2.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0.55" + +near-lake-framework = "0.3.0" + +``` + +将 `src/main.rs` 的内容替换为: + +```rust +use clap::{Parser, Subcommand}; +use futures::StreamExt; +use tracing_subscriber::EnvFilter; + +// TODO: StartOptions + +#[tokio::main] +async fn main() -> Result<(), tokio::io::Error> { + init_tracing(); + + let opts = Opts::parse(); + + // TODO: Config + + let stream = near_lake_framework::streamer(config); + + let mut handlers = tokio_stream::wrappers::ReceiverStream::new(stream) + .map(handle_streamer_message) + .buffer_unordered(1usize); + + while let Some(_handle_message) = handlers.next().await {} + + Ok(()) +} + +async fn handle_streamer_message( + streamer_message: near_lake_framework::near_indexer_primitives::StreamerMessage, +) { + eprintln!( + "{} / shards {}", + streamer_message.block.header.height, + streamer_message.shards.len() + ); + std::fs::write("last_indexed_block", streamer_message.block.header.height.to_string().as_bytes()).unwrap(); +} + +fn init_tracing() { + let mut env_filter = EnvFilter::new("near_lake_framework=info"); + + if let Ok(rust_log) = std::env::var("RUST_LOG") { + if !rust_log.is_empty() { + for directive in rust_log.split(',').filter_map(|s| match s.parse() { + Ok(directive) => Some(directive), + Err(err) => { + eprintln!("Ignoring directive `{}`: {}", s, err); + None + } + }) { + env_filter = env_filter.add_directive(directive); + } + } + } + + tracing_subscriber::fmt::Subscriber::builder() + .with_env_filter(env_filter) + .with_writer(std::io::stderr) + .init(); +} +``` + +这段代码目前还无法构建。同时让我们快速了解一下我们刚才复制/粘贴的内容: + +- 我们导入了 [`clap`](https://docs.rs/clap/latest/clap/) 来设置我们将接受的命令行参数 +- 此外,我们导入了必要的内容,如 `futures` 和 `tracing_subscriber` +- 文件末尾的 `init_tracing` 是一个辅助函数,用于将我们的应用程序订阅到来自 `near-lake-framework` 的日志 +- 一个带有索引器样板代码的异步 `main` 函数,但缺少我们将在教程中介绍的 `LakeConfig` 创建部分 +- 您可以找到一些我们为您标记的 `// TODO: ...` 部分,以帮助找到编写本教程代码的位置 + +好了,所有准备工作都已完成。让我们继续。 + +--- + +## 设计 `StartOptions` + +因此,我们希望能够传递一个命令来定义索引器应该如何启动。在本教程中,我们将使用 `clap`。 + +我们需要一个接收链 id 的结构。这将允许我们使用命令: + +```bash +./target/release/indexer mainnet ... +``` + +或者 + +```bash +./target/release/indexer testnet ... +``` + +让我们将 `src/main.rs` 中的 `// TODO: StartOptions` 替换为: + +```rust title=src/main.rs +#[derive(Parser, Debug, Clone)] +#[clap(version = "0.1", author = "Near Inc. ")] +struct Opts { + #[clap(subcommand)] + pub chain_id: ChainId, +} + +#[derive(Subcommand, Debug, Clone)] +enum ChainId { + #[clap(subcommand)] + Mainnet(StartOptions), + #[clap(subcommand)] + Testnet(StartOptions), +} + +``` + +现在我们想创建一个 `StartOptions` 结构,允许我们告诉索引器从哪里开始索引。命令应该如下所示: + +```bash +./target/release mainnet from-latest +``` + +我们的变体是: + +- `from-block N`,其中 `N` 是要开始的区块高度 +- `from-latest`,从网络中最新的最终区块开始 +- `from-interruption`,从索引器之前中断的区块开始 + +让我们将注释 `// TODO: StartOptions` 替换为枚举: + +```rust title=src/main.rs +#[derive(Subcommand, Debug, Clone)] +pub(crate) enum StartOptions { + FromBlock { height: u64 }, + FromLatest, + FromInterruption, +} +``` + +很简单直接,对吧? + +--- + +## 创建 `LakeConfig` + +为了创建 `LakeConfig`,我们将使用配置构建器 [`LakeConfigBuilder`](https://docs.rs/near-lake-framework/0.3.0/near_lake_framework/struct.LakeConfigBuilder.html)。幸运的是,我们已经导入了它。 + +让我们在 `// TODO: Config` 注释的位置实例化一个构建器: + +```rust title=src/main.src + let mut lake_config_builder = near_lake_framework::LakeConfigBuilder::default(); +``` + +注意 `lake_config_builder` 被定义为可变的。 + +现在我们需要通过匹配提供的 `ChainId` 来设置我们要索引的链: + +```rust title=src/main.src + let mut lake_config_builder = near_lake_framework::LakeConfigBuilder::default(); + + match &opts.chain_id { + ChainId::Mainnet(start_options) => { + lake_config_builder = lake_config_builder + .mainnet(); + } + ChainId::Testnet(start_options) => { + lake_config_builder = lake_config_builder + .testnet(); + } + } +``` + +如您所见,根据 `ChainId` 枚举的变体,我们用快捷方式 `mainnet()` 或 `testnet()` 之一修改 `lake_config_builder`。 + +本教程中最重要的剩余参数是 `start_block_height`。 + +通常,我们只传递区块高度数字 `u64`,但我们在这里实现启动选项。 + +--- + +## 启动选项逻辑 + +让我们创建一个单独的函数来保存识别 `start_block_height` 的逻辑,并将其命名为 `get_start_block_height`。 + +**只是阅读代码,不要复制,这还不是最终方法** + +### `FromBlock { height: u64 }` + +让我们从最简单的 `from-block N` 实现开始: + +```rust title=src/main.rs +async fn get_start_block_height(start_options: &StartOptions) -> u64 { + match start_options { + StartOptions::FromBlock { height } => height, + + } +} +``` + +好的,这很简单,其他 `StartOptions` 的匹配臂呢: + +```rust title=src/main.rs +async fn get_start_block_height(start_options: &StartOptions) -> u64 { + match start_options { + StartOptions::FromBlock { height } => height, + StartOptions::FromLatest => + } +} +``` + +嗯,我们如何从网络获取最新区块?我们应该查询 JSON RPC 获取最终区块,提取其高度,然后完成。 + +### `FromLatest` + +为了在 Rust 代码中查询 JSON RPC,我们需要使用 [`near-jsonrpc-client-rs` crate](https://github.com/near/near-jsonrpc-client-rs)。 + +您可以在项目 GitHub 仓库的相应文件夹中找到[一些有用的示例](https://github.com/near/near-jsonrpc-client-rs/tree/master/examples)。 + +在 `Cargo.toml` 末尾添加它: + +```toml title=Cargo.toml +near-jsonrpc-client = "0.3.0" +``` + +获取最终区块高度的代码如下所示: + +```rust +use near_jsonrpc_client::{methods, JsonRpcClient}; +use near_lake_framework::near_indexer_primitives::types::{BlockReference, Finality}; + +async fn final_block_height() -> u64 { + let client = JsonRpcClient::connect("https://rpc.mainnet.near.org"); + let request = methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(Finality::Final), + }; + + let latest_block = client.call(request).await.unwrap(); + + latest_block.header.height +} +``` + +简洁明了。但是,硬编码的 `"https://rpc.mainnet.near.org"` 看起来不太好,尤其是当我们想同时支持两个网络时。 + +但我们可以通过将 JSON RPC URL 传递给 `get_start_block_function` 来处理: + +```rust title=src/main.rs + +async fn get_start_block_height( + start_options: &StartOptions, + rpc_url: &str, +) -> u64 { + ... +} + + ... + match &opts.chain_id { + ChainId::Mainnet(start_options) => { + lake_config_builder = lake_config_builder + .mainnet() + .start_block_height( + get_start_block_height( + start_options, + "https://rpc.mainnet.near.org", + ).await + ); + } + ChainId::Testnet(start_options) => { + lake_config_builder = lake_config_builder + .testnet() + .start_block_height( + get_start_block_height( + start_options, + "https://rpc.testnet.near.org", + ).await + ) + } + } + +``` + +嗯。这很丑陋,为什么我们每次都要传递它,如果它只在三种可能情况中的一种情况下需要? + +相反,我们可以将整个 `Opts` 传递给 `get_start_block_height` 函数。 + +```rust title=src/main.rs +async fn get_start_block_height(opts: &Opts) -> u64 { + match opts.chain_id { + ChainId::Mainnet(start_options) => { + match start_options { + StartOptions::FromBlock { height } => height, + StartOptions::FromLatest => + } + } + } +} +``` + +至少我们有了所需的一切。不过,它看起来仍然很丑陋,而且肯定会涉及代码重复。 + +我们建议改为创建带有几个有用方法的 `impl Opts`,以获取 JSON RPC URL 和获取 `StartOptions` 实例。 + +**现在您可以安全地复制代码了** + +在 `StartOptions` 定义下面的某个地方添加以下内容: + +```rust title=src/main.rs +impl Opts { + pub fn rpc_url(&self) -> &str { + match self.chain_id { + ChainId::Mainnet(_) => "https://rpc.mainnet.near.org", + ChainId::Testnet(_) => "https://rpc.testnet.near.org", + } + } + + pub fn start_options(&self) -> &StartOptions { + match &self.chain_id { + ChainId::Mainnet(args) | ChainId::Testnet(args) => args + } + } +} +``` + +现在我们可以创建带有查询最终区块的辅助函数 `final_block_height` 的 `get_start_block_height` 函数(我们将重用它,注意手法): + +```rust title=src/main.rs +async fn get_start_block_height(opts: &Opts) -> u64 { + match opts.start_options() { + StartOptions::FromBlock { height } => *height, + StartOptions::FromLatest => final_block_height(opts.rpc_url()).await, + // 占位符 + StartOptions::FromInterruption => 0, + } +} + +async fn final_block_height(rpc_url: &str) -> u64 { + let client = JsonRpcClient::connect(rpc_url); + let request = methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(Finality::Final), + }; + + let latest_block = client.call(request).await.unwrap(); + + latest_block.header.height +} +``` + +您可能注意到了 `FromInterruption` 和关于占位符的注释。我们这样做的原因是为了现在能够构建应用程序,测试 `FromLatest` 是否按预期工作。 + +### 测试 `FromLatest` + + +**凭证** + +请确保您已按照[凭证](/data-infrastructure/tutorials/running-near-lake/credentials)页面的描述设置凭证。否则您将无法使代码正常工作。 + + + +让我们尝试构建并运行我们的代码 + +```bash +cargo build --release + +./target/release/indexer mainnet from-latest +``` + +代码构建完成后,您应该在终端看到类似以下内容: + +```text +65364116 / shards 4 +65364117 / shards 4 +65364118 / shards 4 +65364119 / shards 4 +65364120 / shards 4 +``` + +您可以按 `CTRL+C` 停止它 + +现在我们可以继续处理 `FromInterruption` + +### `FromInterruption` + +为了让索引器知道它在哪个区块被中断,索引器需要将区块高度存储在某处。它应该在 `handle_message` 函数末尾执行此操作。 + +在您在本教程开头复制/粘贴的样板代码中,您可以注意到一行代码: + +```rust + std::fs::write("last_indexed_block", streamer_message.block.header.height.to_string().as_bytes()).unwrap(); +``` + +它将最后索引的区块高度保存到索引器二进制文件旁边的 `last_indexed_block` 文件中。 + +在现实世界的索引器中,您可能会使用其他存储,具体取决于您使用的工具集。 + +但为了向您展示这个概念,我们决定采用最简单的方法,将其保存到文件中。 + +现在我们需要实现从文件读取值的功能。 + + + +如果这是您索引器的第一次启动,并且您要求它从中断处开始,它将无法找到 `last_indexed_block` 并直接失败。 + +这不是我们期望的行为。这就是为什么我们假设您希望它从中断处开始(如果可能),否则从最新处开始。 + + + +让我们完成我们的 `get_start_block_height` + +```rust title=src/main.rs +async fn get_start_block_height(opts: &Opts) -> u64 { + match opts.start_options() { + StartOptions::FromBlock { height } => *height, + StartOptions::FromLatest => final_block_height(opts.rpc_url()).await, + // 占位符 + StartOptions::FromInterruption => { + match &std::fs::read("last_indexed_block") { + Ok(contents) => { + String::from_utf8_lossy(contents).parse().unwrap() + } + Err(e) => { + eprintln!("Cannot read last_indexed_block.\n{}\nStart indexer from latest final", e); + latest_block_height(opts.rpc_url()).await + } + } + }, + } +} +``` + +我们在这里做的是: + +- 尝试读取 `last_indexed_block` 文件 +- 如果 `Result` 是 `Ok`,我们读取 `contents` 并解析它 +- 如果 `Result` 是 `Err`,我们打印关于错误的消息并调用 `last_block_height` 从网络获取最终区块(我们之前提到的回退) + +### 测试 `FromInterruption` + +为了确保一切按预期工作,我们将从创世块开始索引以存储最后索引的区块。然后我们将从中断处开始,以确保我们不是从最新处开始。 + +让我们构建并从创世块运行。 + + +**创世块技巧** + +要从创世块开始基于 NEAR Lake Framework 的索引器,只需将 `start_block_height` 指定为 `0`。 + + + +```bash +cargo build --release +./target/release/indexer mainnet from-block 0 +``` + +您将看到类似以下内容: + +```text +9820210 / shards 1 +9820214 / shards 1 +9820216 / shards 1 +9820219 / shards 1 +9820221 / shards 1 +9820226 / shards 1 +9820228 / shards 1 +9820230 / shards 1 +9820231 / shards 1 +9820232 / shards 1 +9820233 / shards 1 +9820235 / shards 1 +9820236 / shards 1 +9820237 / shards 1 +9820238 / shards 1 +``` + +按 `CTRL+C` 停止它 + +记住您看到的最后一个区块高度。在我们的例子中是 `9820238` + +从中断处重启索引器 + +```bash +./target/release/indexer mainnet from-interruption +``` + +您应该看到索引器日志从您记住的区块开始。 + +太好了!一切都完成了。现在您可以根据需要调整您得到的代码并在您的索引器中使用它。 + +--- + +## 总结 + +您已经了解了如何为索引器添加启动选项的方式。如您所见,这里没有任何复杂的内容。 + +您可以在 [`near-examples/lake-indexer-start-options`](https://github.com/near-examples/lake-indexer-start-options) 中找到源代码。 diff --git a/zh/data-infrastructure/tutorials/running-near-lake/run-near-lake.mdx b/zh/data-infrastructure/tutorials/running-near-lake/run-near-lake.mdx new file mode 100644 index 00000000000..fccd3b61783 --- /dev/null +++ b/zh/data-infrastructure/tutorials/running-near-lake/run-near-lake.mdx @@ -0,0 +1,185 @@ +--- +title: 运行 Lake 索引器 +description: "了解如何设置和运行 NEAR Lake 索引器,包括前提条件、网络配置以及从最新区块或特定区块同步的命令。" +--- + +在 NEAR,我们已经有一个可用的解决方案,用于索引区块链数据并将其存储在 AWS S3 存储桶中,称为 **NEAR Lake**。在本指南中,您将了解如何设置和运行 NEAR Lake 索引器实例。 + +Lake 索引器设置包含以下组件: + +- AWS S3 存储桶作为存储 +- NEAR Lake 二进制文件,作为常规的 NEAR 协议点对点节点运行,因此您将像操作 + [NEAR 中的任何常规/RPC 节点](https://near-nodes.io/rpc/hardware-rpc)一样操作它 + + + +NEAR Lake 是一个专门用于将事件以 JSON 文件形式存储在 AWS S3 上的索引器,构建在 [NEAR Indexer 微框架](https://github.com/nearprotocol/nearcore/tree/master/chain/indexer)之上 + + + +### 准备开发环境 + +在继续之前,请确保已安装以下软件: + +- [Rust 编译器](https://rustup.rs/),版本需与 [nearcore](https://github.com/nearprotocol/nearcore) 项目根目录中 `rust-toolchain` 文件中提到的版本一致。 +- 确保您已[配置 AWS 凭证](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) + 来自 AWS 文档: + + > 例如,使用 aws configure 配置默认配置文件生成的文件类似于以下内容。 + > + > ~/.aws/credentials + > + > ``` + > [default] + > aws_access_key_id=AKIAIOSFODNN7EXAMPLE + > aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + > ``` + +### 编译 NEAR Lake + +```bash +$ cargo build --release +``` + +### 配置 NEAR Lake + +要将 NEAR Lake 连接到特定链,您需要必要的配置,可以按如下方式生成: + +```bash +$ ./target/release/near-lake --home ~/.near/testnet init --chain-id testnet --download-config --download-genesis +``` + +上述代码将下载官方创世配置并生成必要的配置。您可以将上述命令中的 `testnet` 替换为不同的网络 ID(`betanet`、`mainnet`)。 + +指定网络的配置在 `--home` 提供的文件夹中。我们需要确保 NEAR Lake 跟踪所有必要的分片,因此 `~/.near/testnet/config.json` 中的 `"tracked_shards"` 参数需要正确配置。 +目前,`nearcore` 将 `"tracked_shards"` 的空值视为"不跟踪任何分片",**任何值**视为"跟踪所有分片"。 +例如,为了跟踪所有分片,只需将分片 #0 添加到列表中: + +```text +... +"tracked_shards": [0], +... +``` + +### 运行 NEAR Lake + +在 `./target/release/near-lake` 之后运行 NEAR Lake 的命令 + +| 命令 | 键/子命令 | 必需/默认值 | 负责的功能 | +|---------|-------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| | `--home` | 默认值
`~/.near` | 告知节点在哪里查找必要文件:
`config.json`

`genesis.json`

`node_key.json`

`data`
文件夹 | +| `init` | | | 告知节点在 `--home-dir` 中生成配置文件 | +| | `--chain-id` | 必需

_ `localnet`
_ `testnet`
\* `mainnet` | 定义要为其生成配置文件的链 | +| | `--download-config` | 可选 | 如果提供,告知节点从公共 URL 下载 `config.json`。您可以手动下载

- [testnet config.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/testnet/rpc/config.json)
- [mainnet config.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/mainnet/rpc/config.json) | +| | `--download-genesis` | 可选 | 如果提供,告知节点从公共 URL 下载 `genesis.json`。您可以手动下载

- [testnet genesis.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/testnet/genesis.json)
- [mainnet genesis.json](https://s3-us-west-1.amazonaws.com/build.nearprotocol.com/nearcore-deploy/mainnet/genesis.json) | +| | TODO:
其他 `neard` 键 | | | +| `run` | | | 运行节点 | +| | `--bucket` | 必需 | AWS S3 存储桶名称 | +| | `--region` | 必需 | AWS S3 存储桶区域 | +| | `--fallback-region` | 默认值 eu-central-1 | AWS S3 备用区域 | +| | `--endpoint` | 可选 | 与 AWS S3 兼容的 API 端点 | +| | `--stream-while-syncing` | 可选 | 如果提供,索引器在节点上出现区块时立即流式传输,而不是等待节点完全同步 | +| | `--concurrency` | 默认值 1 | 定义将区块数据保存到 AWS S3 的进程并发度 | +| | `sync-from-latest` | 需要 `sync-` 子命令之一 | 告知节点从网络中的最新区块开始索引 | +| | `sync-from-interruption` | 需要 `sync-` 子命令之一 | 告知节点从节点中断的区块开始索引(如果是第一次启动,将回退到 `sync-from-latest`) | +| | `sync-from-block --height N` | 需要
`sync-`
子命令之一 | 告知节点从指定区块高度 `N` 开始索引(**确保**您的节点数据包含您想要开始的区块) | + +```bash +$ ./target/release/near-lake --home ~/.near/testnet run --stream-while-syncing --concurrency 50 sync-from-latest +``` + +网络同步后,您应该看到 NEAR Lake 当前接收到的每个区块高度的日志。 + +--- + +## 同步 + +每当您在 localnet 以外的任何网络上运行 NEAR Lake,您都需要与网络同步。 +这是必要的,因为这是 `nearcore` 节点的自然行为,而 NEAR Lake 是常规 `nearcore` 节点的包装器。为了工作并索引数据,您的节点必须与网络同步。 + +虽然基于快照的同步以前是推荐的默认方法,但我们现在推荐[纪元同步](https://near-nodes.io/intro/node-epoch-sync)——这是一种更快、更轻量级的方法,允许节点在不下载大型状态快照的情况下从创世块同步。 + +--- + +## 将 NEAR Lake 作为归档节点运行 + +这不是必要的,但为了索引网络中的所有内容,最好从创世块开始。 +`nearcore` 节点默认以非归档模式运行。这意味着节点仅保留[最近 5 个纪元](/protocol/network/epoch)的数据。为了从创世块索引数据,您需要将节点切换到归档模式。 + +为此,您需要更新位于 `--home-dir`(默认为 `~/.near`)中的 `config.json`。 + +在配置中找到以下键并按如下方式更新: + +```json +{ + ... + "archive": true, + "tracked_shards": [0], + ... +} +``` + +归档模式下的同步过程可能需要大量时间,因此最好下载 NEAR 提供的备份并将其放入您的 `data` 文件夹。之后,您的节点只需下载备份截止后的数据,这需要合理的时间。 + +所有备份可以从 FastNEAR 提供的公共 [S3 存储桶](https://docs.fastnear.com/docs/snapshots/mainnet#archival-mainnet-snapshot)下载。 + +参见[此链接](https://near-nodes.io/archival/run-archival-node-with-nearup)以供参考。 + +--- + +## 使用数据 + +我们将所有数据写入 AWS S3 存储桶: + +- `near-lake-data-testnet`(`eu-central-1` 区域)用于测试网 +- `near-lake-data-mainnet`(`eu-central-1` 区域)用于主网 + +--- + +## 自定义 S3 存储 + +如果您想运行自己的 `near-lake` 实例并将数据存储在某些兼容 S3 的存储中([Minio](https://min.io/) 或 [Localstack](https://localstack.cloud/) 等), +您可以使用 `--endpoint` 选项覆盖默认的 S3 API 端点 + +- 运行 `minio` + +```bash +$ mkdir -p /data/near-lake-custom && minio server /data +``` + +- 运行 `near-lake` + +```bash +$ ./target/release/near-lake --home ~/.near/testnet run --endpoint http://127.0.0.1:9000 --bucket near-lake-custom sync-from-latest +``` + +### 数据结构 + +我们使用的数据结构如下: + +```text +/ + block.json + shard_0.json + shard_1.json + ... + shard_N.json +``` + +- `` 是一个 12 位长的 `u64` 字符串,带有前导零(例如 `000042839521`)。[请参阅此 issue 了解原因](https://github.com/near/near-lake/issues/23) +- `block_json` 包含 JSON 序列化的 [`BlockView`](https://github.com/near/nearcore/blob/e9a28c46c2bea505b817abf484e6015a61ea7d01/core/primitives/src/views.rs#L711-L716) 结构体。**注意:** 此结构体将来可能会更改,我们会提前公告 +- `shard_N.json` 其中 `N` 是从 `0` 开始的 `u64`。表示分片的索引编号。要了解区块中预期的分片数量,可以在 `block.json` 的 `.header.chunks_included` 中查看 + +### 访问数据 + +所有 NEAR Lake AWS S3 存储桶都启用了[请求者付费](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html)。这意味着任何拥有自己 AWS 凭证的人都可以列出和读取存储桶的内容,并**由 AWS 向其收费**。连接到存储桶时必须提供 AWS 凭证。参见 [NEAR Lake Framework](https://github.com/near/near-lake-framework) 以供参考。 + +### NEAR Lake Framework + +一旦我们[设置了对存储桶的公共访问](https://github.com/near/near-lake/issues/22),任何人都可以构建自己的代码来读取它。 + +为了我们自己的需求,我们正在开发 [NEAR Lake Framework](https://github.com/near/near-lake-framework),以便有一种简单的方式基于 NEAR Lake 本身存储的数据创建索引器。 + + +请参阅官方 NEAR Lake Framework [在 NEAR Gov Forum 上的公告](https://gov.near.org/t/announcement-near-lake-framework-brand-new-word-in-indexer-building-approach/17668)。 + diff --git a/zh/data-infrastructure/what-is.mdx b/zh/data-infrastructure/what-is.mdx new file mode 100644 index 00000000000..3296a44c10f --- /dev/null +++ b/zh/data-infrastructure/what-is.mdx @@ -0,0 +1,45 @@ +--- +title: 什么是数据基础设施? +description: "探索 NEAR 的数据基础设施,轻松访问链上数据" +--- + +NEAR 提供即用型解决方案,帮助用户轻松访问和监控链上数据。这对于根据特定**事件**自动执行操作、缓存数据以**降低延迟**、收集区块链的**使用数据**,乃至**研究用户偏好**都非常有用。 + +![img](/assets/docs/welcome-pages/6.data-infrastructure.webp) + +在 NEAR 中,您将找到三种主要的链上数据访问和监控解决方案:[**数据 API**](#data-apis)、[**BigQuery 公共数据集**](#bigquery-public-dataset) 和 [**NEAR Lake**](#near-lake)。每种解决方案都针对不同的需求和使用场景,也可以组合使用,为您的应用构建完整的数据基础设施。 + +--- + +## [数据 API](./data-api) + +NEAR 社区成员构建了一系列 API,用于访问和监控链上数据。这些 API 设计简洁,可通过简单的 API 调用从任何应用访问。 + +- 用户资产:轻松追踪用户或合约持有的所有资产 +- 监控交易:获取用户、合约或特定代币的所有交易记录 +- 追踪链上事件:获取合约发出的所有事件,或特定类型的事件 + +
+ +## [BigQuery:公共数据集](./big-query) +Google Cloud Platform 上公开提供的大型链上数据集。使用简单的 SQL 查询获取近实时区块链数据。**所有数据,零配置**。 + +- 即时洞察:大规模查询历史链上数据,无需运行自己的基础设施。 +- 经济高效:无需存储和处理大量 NEAR 协议数据,按需查询所需数量的数据。 +- 像 SQL 一样简单:无需区块链技术背景,只需具备基本的 SQL 知识即可获取洞察。 + +
+ +## [NEAR Lake](./near-lake-framework) +一个监控 NEAR 网络并存储所有事件的解决方案,方便您随时访问。 + +- 经济高效的解决方案:使用 Rust、JavaScript、Python、Go 等语言构建自托管索引器的经济高效方案 +- 简化数据管理:使用 NEAR Lake Framework 直接从 NEAR Lake 向您的服务器流式传输区块 + +--- + +## 结论 + +数据基础设施是任何区块链应用的关键组成部分。它使开发者能够轻松访问和监控链上数据,这对于构建与区块链交互的应用至关重要。 + +NEAR 提供了一系列解决方案,帮助开发者为其应用构建强大的数据基础设施,包括数据 API、BigQuery 公共数据集和 NEAR Lake。通过组合使用这些解决方案,开发者可以创建完整的数据基础设施,以满足其特定的需求和使用场景。 diff --git a/zh/getting-started/create-account.mdx b/zh/getting-started/create-account.mdx new file mode 100644 index 00000000000..ae5284f2b57 --- /dev/null +++ b/zh/getting-started/create-account.mdx @@ -0,0 +1,132 @@ +--- +title: 创建账户 +icon: user-plus +description: "了解如何使用钱包和 NEAR CLI 创建 NEAR 账户" +--- + +要开始在 NEAR 上开发应用程序,您需要一个 NEAR `testnet` 账户。该账户允许您在不花费真实资金的情况下部署和测试应用程序。 + +您可以通过以下方式之一创建 `testnet` NEAR 账户: + +- [使用 wallet.near.org 中列出的钱包之一](#using-a-wallet) +- [使用命令行界面(CLI)](#through-the-cli) + + + +如果您已有 NEAR 账户,可以将其导入另一个钱包或 CLI。请参阅下方的[导入现有账户](#importing-existing-accounts)。 + + + +--- + +## 使用钱包 + +访问 [wallet.near.org](https://wallet.near.org/),从中选择一个钱包。无需担心,该列表经过精心筛选,所有列出的钱包均可放心使用。 + +总体而言,所有钱包提供的功能大致相同,理论上您可以选择其中任何一个。但请注意,部分钱包可以方便地创建[具名账户](/protocol/accounts-contracts/account-id#named-address)(例如 `alice.testnet`),这类账户更易于记忆。 + +请务必记录下您的助记词,因为这是访问账户的唯一方式! + + +**测试网** +请确保创建的是 `testnet` 账户(以 `.testnet` 结尾,例如 `alice.testnet`),而非 `mainnet` 账户(以 `.near` 结尾)。 + +NEAR `testnet` 是一个独立的网络,允许您在不花费真实资金的情况下测试应用程序。 + + + +**为钱包充值** + +需要 `testnet` 代币?请尝试使用我们的[水龙头](/getting-started/faucet)之一 + + + +--- + +## 通过 CLI + +在开发智能合约时,您将花费大量时间通过命令行界面(CLI)与 NEAR 区块链进行交互。 + +首先,您需要安装 [NEAR CLI](/tools/cli#installation): + + + + + ```bash + npm install -g near-cli-rs@latest + ``` + + + + ``` + $ cargo install near-cli-rs + ``` + + + + ```bash + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + ``` + + + + ```bash + irm https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.ps1 | iex + ``` + + + +安装 CLI 后,您可以使用以下命令创建新账户: + +```bash +near create-account --useFaucet +``` + +该命令将创建一个名为 `` 的新账户,并通过 `testnet` 水龙头为其注入资金。请将 `` 替换为您希望使用的账户名称。 + +--- + +## 导入现有账户 + +如果您想在另一个钱包或 CLI 中使用已有的 NEAR 账户,可以通过助记词或私钥进行导入。 + + +从当前钱包中,使用其账户备份/导出流程。 + +如果账户是通过 CLI 创建的,请运行: + +```bash +near account export-account +``` + + + +在目标钱包中,选择账户导入/恢复选项,并提供您的助记词。 + + + +运行: + +```bash +near account import-account +``` + +然后按照提示输入您的助记词或私钥。 + + + +请妥善保管您的助记词和私钥。任何能够获取这些信息的人都可以控制您的账户。 + + +--- + +## 后续步骤 + +创建 NEAR 账户后,您就可以开始在 NEAR 协议上开发应用程序了。 + +以下是一些帮助您入门的资源: + +- 通过我们的[水龙头](/getting-started/faucet)为钱包充值 +- 创建您的[第一个智能合约](/smart-contracts/quickstart) +- 构建 [Web3 应用程序](/web3-apps/quickstart) +- 学习如何[端到端构建竞拍应用](/web3-apps/tutorials/mastering-near/0-intro) diff --git a/zh/getting-started/faucet.mdx b/zh/getting-started/faucet.mdx new file mode 100644 index 00000000000..77ef6ebc50a --- /dev/null +++ b/zh/getting-started/faucet.mdx @@ -0,0 +1,47 @@ +--- +title: 代币水龙头 +icon: droplet +sidebarTitle: 获取测试网代币 +description: "了解如何在 NEAR 区块链上获取用于开发和测试的免费测试网代币" +--- +import { Faucet } from '/snippets/faucet.jsx'; + +**水龙头**提供免费的 `testnet` 代币,用于测试和开发目的。 + +目前有两个测试网水龙头:本页内置的水龙头(仅提供原生 `testnet` NEAR)以及[测试网水龙头](#external-faucet)(支持多种同质化代币,包括 NEAR、USDT 等)。 + + + 如果任何水龙头无法正常使用,请在我们的 [NEAR Telegram 频道](https://t.me/neardev) 中反馈 + + +--- + +## 水龙头 + +只需输入您希望接收代币的账户地址,然后点击"Request"按钮。水龙头将向该账户发送少量 `testnet` NEAR 代币。 + + + +--- + +## 外部水龙头 + +访问[测试网水龙头](https://near-faucet.io/)页面,输入您的地址并选择所需的代币类型!该水龙头支持多种同质化代币,包括 NEAR、Tether USD、Ether 等。 + +![显示代币选择界面的水龙头页面](/assets/docs/getting-started/faucet.png) + + + 请注意,水龙头有时可能会暂时缺少代币,请继续阅读下方内容,了解获取其他同质化代币的替代方式 + + +--- + +## 获取同质化代币 + +在 `testnet` 上获取同质化代币的另一种方式是通过 [`testnet` 版本的 ref finance](https://testnet.ref.finance/#near|ref.fakes.testnet) + +在那里,您可以将 `testnet` NEAR 兑换成其他同质化代币,例如 `testnet USDC`、`testnet USDT`、`testnet DAI` 等 + + + 请注意,`testnet` 上的代币汇率可能与 `mainnet` 不同(例如 `1 USDC` 兑换的 `NEAR` 数量可能与 `mainnet` 上不同),此外,`testnet` 上的流动性可能非常有限 + diff --git a/zh/getting-started/hackathons.mdx b/zh/getting-started/hackathons.mdx new file mode 100644 index 00000000000..3fc528e665e --- /dev/null +++ b/zh/getting-started/hackathons.mdx @@ -0,0 +1,171 @@ +--- +title: 在 NEAR 上构建 +sidebarTitle: 黑客松指南 +icon: laptop +description: 精选资源,帮助您快速开始构建,以及 Awesome NEAR 目录。 +--- + +本页汇集了您在 NEAR 上构建时可以立即使用的实用资源。 + +## 入门 + + + + 了解 NEAR 基础知识、架构,以及开发者选择它的原因。 + + + 加入 Telegram 开发者群组,提问并获得帮助。 + + + 为您的项目申请 NEAR AI Cloud 积分。 + + + +## 核心文档 + +- [NEAR 协议基础](/protocol/accounts-contracts/account-model) +- [智能合约快速入门](/smart-contracts/quickstart) +- [Web3 应用快速入门](/web3-apps/quickstart) +- [链抽象概述](/chain-abstraction/what-is) +- [AI 代理工具](/getting-started/tools-for-ai) +- [NEAR AI 文档](https://docs.near.ai/) +- [NEAR intents 文档](https://docs.near-intents.org/near-intents) + +## Awesome NEAR + +`awesome-near` 是一个由社区维护的资源目录,包含工具、示例、库和集成。 + +- [技能](#skills) +- [测试网水龙头](#testnet-faucets) +- [NEAR 协议 SDK](#near-protocol-sdks) +- [钱包与身份验证](#wallet-and-authentication) +- [CLI 工具](#cli-tools) +- [入门模板](#starter-templates) +- [数据基础设施](#data-infrastructure) +- [数据 API](#data-apis) +- [AI 与云服务](#ai-and-cloud-services) +- [Near intents](#near-intents) +- [链签名](#chain-signatures) +- [区块链浏览器](#explorers) +- [其他资源](#additional-resources) + +## 技能 + +| 软件包 | 描述 | +|---------|-------------| +| [agent-skills](https://github.com/near/agent-skills) | 用于 NEAR 协议区块链开发的 AI 代理技能 | + +## 测试网水龙头 + +| 水龙头 | 描述 | +|--------|-------------| +| [Circle Faucet](https://faucet.circle.com/) | NEAR 测试网上的 20 USDC(每 2 小时可领取一次) | +| [NEAR Faucet](https://near-faucet.io/) | 测试网上的 2 NEAR(有频率限制) | + +## NEAR 协议 SDK + +### JavaScript / TypeScript + +| 软件包 | 描述 | +|---------|-------------| +| [near-api-js](https://github.com/near/near-api-js) | 用于账户、区块、交易、代币和函数调用的高级 JavaScript 客户端 | +| [near-api-ts](https://github.com/near/near-api-ts) | 适用于 NEAR 协议的高级类型安全 TypeScript 客户端 | +| [near-kit](https://github.com/r-near/near-kit) | 具有类 fetch API 的现代 TypeScript 客户端 | +| [near-jsonrpc-client-ts](https://github.com/near/near-jsonrpc-client-ts) | 自动生成的 TypeScript JSON-RPC 客户端 | + +### Rust + +| 软件包 | 描述 | +|---------|-------------| +| [near-api-rs](https://github.com/near/near-api-rs) | 适用于 NEAR 协议的高级 Rust 链下客户端 | +| [near-sdk-rs](https://github.com/near/near-sdk-rs) | 用于编写 NEAR 智能合约的 Rust SDK | +| [near-abi-rs](https://github.com/near/near-abi-rs) | Rust 合约的 ABI 原语和模型 | + +### Python + +| 软件包 | 描述 | +|---------|-------------| +| [py-near](https://github.com/pvolnov/py-near) | 支持 HOT Protocol 和 NEAR Intents 的异步 Python 客户端 | +| [near-jsonrpc-client-py](https://github.com/near/near-jsonrpc-client-py) | 基于 Pydantic 模型的类型安全 Python JSON-RPC 客户端 | +| [near-sdk-py](https://github.com/r-near/near-sdk-py) | 用于编写 NEAR 智能合约的 Python SDK | + +## 钱包与身份验证 + +| 软件包 | 描述 | +|---------|-------------| +| [@hot-labs/near-connect](https://github.com/azbang/near-connect) | 适用于 JavaScript/TypeScript 的安全轻量级钱包连接器 | +| [near-connect-hooks](https://github.com/matiasbenary/near-connect-hooks) | 用于 NEAR 钱包集成的 React hooks | +| [near-sign-verify](https://github.com/elliotBraem/near-sign-verify) | 创建并验证 NEP-413 签名消息 | +| [better-near-auth](https://github.com/elliotBraem/better-near-auth) | 适用于 Better Auth 的 NEAR 登录插件 | + +## CLI 工具 + +| 软件包 | 描述 | +|---------|-------------| +| [near-cli-rs](https://github.com/near/near-cli-rs) | 适用于 NEAR 协议的人性化交互式 CLI | +| [create-near-app](https://github.com/near/create-near-app) | 使用前端和合约模板快速搭建 NEAR 应用 | +| [cargo-near](https://github.com/near/cargo-near) | 支持 ABI 生成的构建和部署 Cargo 扩展 | +| [near-validator-cli-rs](https://github.com/near/near-validator-cli-rs) | 用于质押操作和验证者工作流的 CLI 工具 | + +## 入门模板 + +| 软件包 | 描述 | +|---------|-------------| +| [near-ai-chat](https://github.com/jlwaugh/near-ai-chat) | 适用于 NEAR AI Cloud 的 AI 对话代理入门项目 | +| [NEAR Playground](https://nearplay.app/) | 用于创建和部署 NEAR 合约的在线 IDE | +| [near-sdk-rs-template](https://github.com/near/cargo-near-new-project-template) | Rust 智能合约模板 | +| [NEAR-fast-ft-transfer](https://github.com/NEAR-DevHub/NEAR-fast-ft-transfer) | 高性能同质化代币转账服务 | + +## 数据基础设施 + +| 软件包 | 描述 | +|---------|-------------| +| [Goldsky](https://goldsky.com/chains/near) | 数据基础设施与索引服务 | +| [Stream NEAR](https://stream.near.tools) | 用于实时区块数据的服务器发送事件流 | +| [Explorer API](https://github.com/fastnear/explorer-api) | 基于交易的浏览器 API | +| [Near Lake Indexer](https://github.com/aurora-is-near/near-lake-indexer) | 基于 Lake 的索引服务 | + +## 数据 API + +| 软件包 | 描述 | +|---------|-------------| +| [FastNear API](https://github.com/fastnear/fastnear-api-server-rs) | 面向钱包和浏览器的低延迟 API | +| [NearBlocks API](https://api.nearblocks.io/api-docs/) | 用于 NEAR 区块链数据的 REST API | +| [Pikespeak API](https://doc.pikespeak.ai/) | 聚合分析 API | +| [Allium API](https://docs.allium.so/api/developer/wallets/overview) | 钱包与历史交易 API | + +## AI 与云服务 + +| 软件包 | 描述 | +|---------|-------------| +| [NEAR AI Cloud](https://cloud.near.ai) | 用于可验证 AI 推理的私有推理平台 | + +## Near intents + +| 软件包 | 描述 | +|---------|-------------| +| [One Click SDK TypeScript](https://github.com/defuse-protocol/one-click-sdk-typescript) | 通过 1Click API 实现跨链兑换的 TypeScript SDK | +| [One Click SDK Rust](https://github.com/defuse-protocol/one-click-sdk-rs) | 用于一键兑换的 Rust API 客户端 | +| [One Click SDK Go](https://github.com/defuse-protocol/one-click-sdk-go) | 用于一键兑换的 Go API 客户端 | +| [intents-sdk](https://github.com/defuse-protocol/sdk-monorepo/tree/main/packages/intents-sdk) | 用于基于意图应用的 SDK | + +## 链签名 + +| 软件包 | 描述 | +|---------|-------------| +| [chainsig.js](https://github.com/NearDeFi/chainsig.js) | 用于多链交易和 MPC 签名的 TypeScript 库 | +| [omni-transaction-rs](https://github.com/near/omni-transaction-rs) | 用于构建多链交易的 Rust 库 | + +## 区块链浏览器 + +- [NEAR Validate](https://nearvalidate.org/) +- [NearBlocks](https://nearblocks.io/) +- [Pikespeak](https://pikespeak.ai/) +- [Near Intents Explorer](https://explorer.near-intents.org/) + +## 其他资源 + +- [NEAR Intents 文档](https://docs.near-intents.org) +- [NEP 规范](https://github.com/near/NEPs) +- [NEAR 博客](https://near.org/blog) +- [NEAR 目录](https://nearcatalog.xyz/) diff --git a/zh/getting-started/tools-for-ai.mdx b/zh/getting-started/tools-for-ai.mdx new file mode 100644 index 00000000000..6222a48605f --- /dev/null +++ b/zh/getting-started/tools-for-ai.mdx @@ -0,0 +1,105 @@ +--- +title: AI 代理工具 +icon: cpu +description: "指导您的 AI 代理构建 NEAR 应用程序" +--- + +NEAR 提供多种工具和资源,帮助您的 AI 代理构建和使用 NEAR 应用程序。本页概述了可用的主要工具及其有效使用方式。 + + + + 当您需要为代理提供所有 NEAR 文档的快速参考时使用。 + + + 当您希望代理实时搜索文档以获取最新详细信息时使用。 + + + 当您需要代理在特定任务上成为专家时使用(例如使用我们的 API)。 + + + 当您的代理需要执行链上操作时使用(例如转账和函数调用)。 + + + +--- + +## llms.txt + +**是什么:** 为编程助手精选的 NEAR 文档上下文。 + +**使用场景:** 当您需要为代理提供所有 NEAR 文档的快速参考时。 + +**链接:** [https://docs.near.org/llms.txt](https://docs.near.org/llms.txt) + + + +在提示词中使用 `#fetch`: + +```text +How can I upgrade a contract state? +#fetch https://docs.near.org/llms.txt +``` + + + + + +在 Cursor Chat 中添加一次文档来源: + +1. `@` → `Docs` → `+ Add new doc` +2. 添加:`https://docs.near.org/llms.txt` +3. 在提示时选择该来源 + + + +--- + +## Docs MCP 端点 + +**是什么:** 通过 MCP 搜索 NEAR 文档的端点。 + +**使用场景:** 当您希望代理实时搜索文档以获取最新详细信息和更精准的 API 查询时。 + +**链接:** [https://docs.near.org/mcp](https://docs.near.org/mcp) + +结合使用两个互补层: + +1. **静态上下文(`llms.txt`)** 用于快速、高质量的文档基础。 +2. **检索(Docs MCP)** 当代理需要跨文档进行实时查询时。 + +--- + +## NEAR Agent Skills + +NEAR Agent Skills 是封装了可重复工作流的复用能力集合。 + +**使用场景:** 当您需要代理在特定任务上成为专家时(例如使用 NEAR API)。 + +**链接:** [https://github.com/near/agent-skills](https://github.com/near/agent-skills) + +| 技能 | 专注领域 | +|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| +| [near-ai-cloud](https://github.com/near/agent-skills/tree/main/skills/near-ai-cloud) | 可验证的私有 AI 推理与证明 | +| [near-api-js](https://github.com/near/agent-skills/tree/main/skills/near-api-js) | JS/TS 区块链交互、交易、代币和钱包集成 | +| [near-dapp](https://github.com/near/agent-skills/tree/main/skills/near-dapp) | dApp 项目搭建、钱包集成、React/Next.js 模式 | +| [near-intents](https://github.com/near/agent-skills/tree/main/skills/near-intents) | 通过 1Click API 实现跨链兑换 | +| [near-kit](https://github.com/near/agent-skills/tree/main/skills/near-kit) | 具有类型安全合约和沙箱测试的 TypeScript SDK | +| [near-smart-contracts](https://github.com/near/agent-skills/tree/main/skills/near-smart-contracts) | Rust 智能合约开发、安全性和状态管理 | + +--- + +## NEAR MCP 服务器 + +NEAR MCP 服务器是一个工具服务器(目前包含 [23 个工具](https://github.com/nearai/near-mcp/blob/main/TOOLS.md)),可让代理执行区块链操作。 + +**使用场景:** 当您的代理需要持有资金、转移资金、与智能合约交互,或在 NEAR 协议上执行任何链上操作时 + +**代码仓库:** [https://github.com/nearai/near-mcp](https://github.com/nearai/near-mcp) + +**远程部署指南:** [https://github.com/nearai/near-mcp/blob/main/tee.md](https://github.com/nearai/near-mcp/blob/main/tee.md) + + + +NEAR MCP 设计为在**本地**或您的**可信基础设施**上运行,因为它需要处理私钥。目前没有公开托管的版本。 + + diff --git a/zh/getting-started/what-is-near.mdx b/zh/getting-started/what-is-near.mdx new file mode 100644 index 00000000000..87579195a6d --- /dev/null +++ b/zh/getting-started/what-is-near.mdx @@ -0,0 +1,64 @@ +--- +title: 什么是 NEAR? +icon: book-open +description: "一条可扩展且安全、具有卓越开发者体验的区块链" +--- + +import { Github } from '/snippets/github.jsx' + +NEAR 是一条**用户友好**且[**碳中和**](https://near.org/blog/near-climate-neutral-product/)的区块链,旨在实现[快速、安全和无限可扩展](https://www.leewayhertz.com/comparison-of-blockchain-protocols#Parallel-comparison-of-various-blockchain-networks)。它提供简洁的用户体验,包括具名账户、低手续费以及强大的开发者生态系统。 + +![img](/assets/docs/welcome-pages/1.near-protocol.webp) + +从技术角度而言,NEAR 是一条以可用性为设计核心的[第一层](https://coinmarketcap.com/academy/glossary/layer-1-blockchain)、[分片](https://near.org/blog/near-launches-nightshade-sharding-paving-the-way-for-mass-adoption)、[权益证明](https://en.wikipedia.org/wiki/Proof_of_stake)区块链。 + +简而言之,NEAR 是**面向所有人的区块链**。 + + + +从技术角度而言,NEAR 是一条以可用性为设计核心的[第一层](https://coinmarketcap.com/academy/glossary/layer-1-blockchain)、[分片](https://near.org/blog/near-launches-nightshade-sharding-paving-the-way-for-mass-adoption)、[权益证明](https://en.wikipedia.org/wiki/Proof_of_stake)区块链。 + +[第一层](https://coinmarketcap.com/academy/glossary/layer-1-blockchain)意味着 NEAR 是支撑其上一切构建的基础。它将所有交易记录保持安全且不可篡改,从而保证网络的安全性和可信度。 + +[分片](https://near.org/blog/near-launches-nightshade-sharding-paving-the-way-for-mass-adoption)意味着网络被拆分成并行运行的多个片段。这有助于 NEAR 快速高效地处理交易。 + +[权益证明](https://en.wikipedia.org/wiki/Proof_of_stake)相比使用工作量证明的其他区块链消耗更少的电力。用户通过持有 NEAR 代币来参与网络运行,这使得成本更低,并允许更多人参与其中。 + + + +--- + +## 为何选择 NEAR? + +NEAR 是一项技术杰作,提供具名账户和账户抽象等内置功能。对于开发者而言,NEAR 提供了应用程序所需的一切,从智能合约到索引器,同时还能与其他链互操作。 + +### 简单易用 + +1. 使用 `alice.near` 等[**具名账户**](/protocol/accounts-contracts/account-model) +2. 简单注册:[免费创建账户](/getting-started/create-account),通过[社交账号](../web3-apps/tutorials/wallet-login)或 [Telegram](https://web.telegram.org/k/#@herewalletbot) 登录 +3. 交易**快速** _(约 1.3 秒最终确认)_ 且**低廉** _(手续费低于 1 美分)_ +4. 得益于**内置账户抽象**,您无需购买加密货币 +5. [访问密钥](/protocol/accounts-contracts/access-keys)让使用更安全便捷 +6. 借助[链签名](../chain-abstraction/chain-signatures),可控制**其他链**上的账户 + +### 久经考验 + +1. 5 年**100% 正常运行时间**,处理了 [**40 亿**笔交易](https://pikespeak.ai/near-world/overview) +2. NEAR 曾在单日内维持 [超过 1300 万笔交易](https://pikespeak.ai/near-world/overview) 的峰值 +3. NEAR 上托管着拥有[数百万用户](https://dappradar.com/rankings/protocol/near?sort=uawCount&order=desc&range=30d)的去中心化应用: + - [Kai-ching](https://cosmose.ai/) + - [Sweat](https://sweateconomy.com/) + - [Hot Wallet](https://t.me/herewalletbot/) + +### 卓越的开发者体验 + +1. 使用 **Javascript** 或 **Rust** 构建智能合约 +2. **简单的入门流程**,得益于完整的文档和示例 +3. 在 NEAR DevRel **办公时间**获得解答和学习,任何人均可参与 +4. 从合约的 gas 费用中获得收益 +5. 通过 [Project Aurora](http://www.aurora.dev) 实现 **EVM 兼容性** _(轻松部署您的 Solidity 合约)_ + +### 环保友好 + +1. NEAR 已获得**[碳中和认证](https://near.org/blog/the-near-blockchain-is-climate-neutral/)** +2. NEAR **一年的能耗**相当于[**比特币 3 分钟的能耗**](https://medium.com/nearprotocol/how-near-went-carbon-neutral-e656db96da47#:~:text=The%20firm%20found%20that%20NEAR,PoS%20technology%20instead%20of%20PoW) diff --git a/zh/index.mdx b/zh/index.mdx new file mode 100644 index 00000000000..875ccda8da9 --- /dev/null +++ b/zh/index.mdx @@ -0,0 +1,235 @@ +--- +mode: wide +--- + + + +

+ NEAR 开发者文档 +

+

+ 构建智能合约、去中心化应用及原生跨链应用,从未如此简单。 +

+ + + + +
+
+ ~5 分钟 + Rust + JavaScript + Python +
+

几分钟内部署您的第一个合约

+

+ 使用 Rust、JavaScript、TypeScript 或 Python 编写智能合约。通过单条命令快速生成可运行的项目,然后构建、测试并部署。 +

+ + 开始快速入门 → + + + 概念、存储模型以及在 NEAR 上的执行方式。 + + + 动作、回调、跨合约调用及最佳实践。 + +
+ + +```bash bash +$> npx create-near-app@latest + +====================================================== +👋 Welcome to Near! Learn more: https://docs.near.org/ +🔧 Let's get your project ready. +====================================================== + +✅ What do you want to build? › "Smart Contract" +✅ Name your project to create a contract: "hello-near" +✅ Success! Created 'hello-near', a smart contract in Rust + + +Build, test, and deploy your contract using cargo: + * cargo near build + * cargo test + * cargo near deploy +``` + + +
+
+ + +
+
+ ~10 分钟 + React + TypeScript +
+

将您的前端连接到 NEAR

+

+ 使用 near-api-js 和 Wallet Selector 对用户进行身份验证,并从任意前端框架调用智能合约。 +

+ + 开始快速入门 → + + + 连接钱包、管理会话及用户账户。 + + + NEAR 类型如何在 near-api-js 中映射到 JavaScript。 + +
+ + +```bash bash +$> npx create-near-app@latest + +====================================================== +👋 Welcome to Near! Learn more: https://docs.near.org/ +🔧 Let's get your project ready. +====================================================== + +✅ What do you want to build? › "A Web App" +✅ Select a framework for your frontend › "Vite (React)" +✅ Name your project: "hello-near" +✅ Success! Created 'hello-near', a web-app using Vite React. + +Start using your new NEAR app: + * cd hello-near + * npm run dev +``` + + +
+
+ + +
+
+ 多链 + Ethereum + Bitcoin + Solana +
+

在任意链上签署交易

+

+ 使用链签名派生门限密钥,并在任意区块链上签署交易——全部通过单个 NEAR 账户完成,无需桥接或封装资产。 +

+ + 立即开始 → + + + NEAR 如何让单个账户控制任意链上的钱包。 + + + 密钥派生、MPC 签名及实现细节。 + +
+ + +```javascript multichain.js +import { Account, KeyPair, JsonRpcProvider, KeyPairSigner } from "near-api-js" +import { getAdapter, chains } from "multichain.js" + +async function main() { + const nearProvider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }) + const nearAccount = new Account("account.testnet", nearProvider, "ed25519:...") + + const arbitrum = getAdapter({ chain: chains.ARBITRUM, mpcNetwork: "testnet" }) + const arbAddress = await arbitrum.getAddressControlledBy({ nearAddress: nearAccount.accountId }) + const arbBalance = await arbitrum.getBalance({ address: arbAddress }) + console.log("Controlled Account", { arbAddress, arbBalance }) + + await arbitrum.transfer({ + to: "0x2f318C334780961FB129D2a6c30D0763d9a5C970", + amount: "10000000000000000", // 0.01 ETH + nearAccount, + }) +} +``` + + +
+
+
+ +--- + +## 平台能力 + + + + 交易在主网约 1–2 秒内确认,具有确定性最终性。无分叉,无需等待。 + + + 交易费用低于 $0.001。存储成本约为每 100 KB 1 NEAR——删除后可全额退款。 + + + 支持 Rust、JavaScript/TypeScript、Python 或 Go 编写合约。完整的 WASM 运行时,无需学习新语法。 + + + 从单个 NEAR 账户为任意链——Bitcoin、Ethereum、Solana——派生门限密钥。 + + + 在可信执行环境中运行 AI 推理,获得可验证、防篡改的输出结果。 + + + Nightshade 分片技术随验证者数量增长实现线性吞吐量扩展。 + + + +--- + +## 两步快速上手 + + + + 获取一个可读的账户名,如 `yourname.testnet`,并通过水龙头立即获取资金——无需安装钱包。 + + + 免费测试网账户,即时获得 NEAR 余额。60 秒内即可准备就绪。 + + + + 运行 `create-near-app`,以您偏好的语言生成可运行的智能合约或全栈 Web 应用。 + + ```bash + npx create-near-app@latest + ``` + + + +--- + +## 按主题浏览 + + + + 账户、访问密钥、Gas、交易及 NEAR 运行时。 + + + 使用 Rust、TypeScript 或 Python 编写和部署合约。 + + + 使用 near-api-js 和 Wallet Selector 将前端连接到 NEAR。 + + + 从单个 NEAR 账户在任意链上签署交易。 + + + FT、NFT、DAO、DEX、流动性质押等——即用即取。 + + + 使用 NEAR Lake、BigQuery 及索引器对链上数据进行索引和查询。 + + + diff --git a/zh/primitives/dao.mdx b/zh/primitives/dao.mdx new file mode 100644 index 00000000000..96eae3f15b3 --- /dev/null +++ b/zh/primitives/dao.mdx @@ -0,0 +1,792 @@ +--- +title: 去中心化自治组织 +description: "了解 NEAR 上的去中心化自治组织(DAO)——通过智能合约投票来协调成员关系、决策制定和资金分配的自组织群体。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; + +去中心化自治组织(DAO)是围绕共同目标形成的自组织群体。成员资格、决策制定和资金分配均通过智能合约上的公开提案投票来协调。 + +![dao](/assets/docs/primitives/dao.png) + +与 [FT](./ft/ft) 和 [NFT](./nft/nft) 不同,DAO 合约没有统一的标准。因此,本页将以 [sputnik dao 合约](https://github.com/near-daos/sputnik-dao-contract)作为参考。此处介绍的核心概念应该可以轻松推广到其他 DAO 实现。 + + + 创建并与 DAO 交互的最简便方式是使用 + [AstraDAO + 界面](https://near.social/astraplusplus.ndctools.near/widget/home?page=daos)。 + + +--- + +## 创建 DAO + +您可以通过与 `sputnik-dao` 合约交互来创建 DAO: + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const DAO_FACTORY_CONTRACT_ADDRESS = 'sputnik-dao.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: DAO_FACTORY_CONTRACT_ADDRESS, + method: 'create', + args: { + name: 'primitives', + args: btoa({ + config: { + name: 'Primitives', + purpose: 'Building primitives on NEAR', + metadata: '', + }, + policy: ['bob.near'], + }), + }, + gas: 300000000000000, + deposit: 6000000000000000000000000, + }); + ``` + + 完整的角色和权限列表请参见[此处](https://github.com/near-daos/sputnik-dao-contract#roles-and-permissions)。 + + +了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + export COUNCIL='["bob.near"]' + export ARGS=`echo '{"config": {"name": "Primitives", "purpose": "Building primitives on NEAR", "metadata":""}, "policy": '$COUNCIL'}' | base64` + + near call sputnikv2.testnet create "{\"name\": \"primitives\", \"args\": \"$ARGS\"}" --useAccount bob.near --amount 6 --gas 150000000000000 + ``` + + + 完整的角色和权限列表请参见[此处](https://github.com/near-daos/sputnik-dao-contract#roles-and-permissions)。 + + + + + + + + 完整的[角色和权限列表请参见此处](https://github.com/near-daos/sputnik-dao-contract#roles-and-permissions)。 + + + + + + ```rust + // Validator interface, for cross-contract calls + #[ext_contract(ext_dao_factory_contract)] + trait ExternalDaoFactoryContract { + fn create(&mut self, name: AccountId, args: Base64VecU8) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + #[payable] + pub fn create_dao(&mut self, name: AccountId, args: Base64VecU8) -> Promise { + let promise = ext_dao_factory_contract::ext(self.dao_factory_contract.clone()) + .with_attached_deposit(env::attached_deposit()) + .with_static_gas(Gas(30*TGAS)) + .create(name, args); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(50*TGAS)) + .external_common_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn external_common_callback(&self, #[callback_result] call_result: Result<(), PromiseError>) { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting external contract") + } + } + } + ``` + + + + + + 创建并与 DAO 交互的最简便方式是使用 + [AstraDAO + 界面](https://near.social/astraplusplus.ndctools.near/widget/home?page=daos)。 + + +
+ +### 使用全局合约 + +您可以使用我们的全局合约部署新的 DAO——这是一个预先部署的 [Sputnik DAO 合约](https://github.com/near-daos/sputnik-dao-contract/tree/main/sputnikdao2),可供重复使用。[全局合约](../smart-contracts/global-contracts)只需部署一次,任何账户均可复用,无需承担高额的存储成本。 + + + + + ```bash + near contract deploy use-global-account-id dao.globals.primitives.testnet \ + with-init-call new \ + json-args '{"config": {"name": "Primitives", "purpose": "Building primitives on NEAR", "metadata":""}, "policy": [""]}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain \ + send + ``` + + + + + ```bash + near contract deploy use-global-hash Ea8tHXFSQVszVwGASyzAfLq65DjcRDhkfab4FcPaRpgD \ + with-init-call new \ + json-args '{"config": {"name": "Primitives", "purpose": "Building primitives on NEAR", "metadata":""}, "policy": [""]}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain \ + send + ``` + + + + + + 通过**哈希**部署会创建一个永不更改的不可变合约。 + 通过**账户 ID** 部署则创建一个可更新的合约,当所引用账户的合约更新时,该合约也会随之更新。请根据您是否希望 FT 合约可更新或永久不变来做出选择。 + + +
+ +### 投票策略 + +目前,DAO 支持两种不同类型的[投票策略](https://github.com/near-daos/sputnik-dao-contract#voting-policy):`TokenWeight`(代币权重)和 `RoleWeight`(角色权重)。 + +当投票策略为 `TokenWeight` 时,委员会使用[代币](./ft/ft)进行投票。投票权重等于参与投票的代币数量占代币总供应量的比例。 + +当投票策略为 `RoleWeight(role)` 时,投票权重计算方式为"一除以拥有该角色的总人数"。 + + +两种投票策略均包含一个用于通过提案的 `threshold`(阈值),可以是比率或固定数值。 + +比率表示需要一定比例的人员/代币批准提案(例如,需要半数人员投票,且投票结果为赞成)。固定数值表示需要特定数量的投票/代币才能通过提案(例如,3 个人员/代币的投票即可批准提案)。 + + + +--- + +## DAO 列表 + +查询 Sputnik Dao 中现有的 DAO 列表。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const DAO_FACTORY_CONTRACT_ADDRESS = 'sputnik-dao.near'; + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'get_dao_list', + args: {}, + contractId: DAO_FACTORY_CONTRACT_ADDRESS, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near view sputnik-dao.near get_dao_list '{}' + ``` + + +

+ + ```bash + [ + 'ref-finance.sputnik-dao.near' + 'gaming-dao.sputnik-dao.near', + ... + ] + ``` + +

+
+
+ + + + +```bash +[ + 'ref-finance.sputnik-dao.near' + 'gaming-dao.sputnik-dao.near', + ... +] +``` + + + +
+ +--- + +## 查询现有提案 + +以下代码片段将帮助您查询特定 DAO 中的现有提案。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const DAO_CONTRACT_ADDRESS = 'nearweek-news-contribution.sputnik-dao.near'; + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'get_proposals', + args: { from_index: 9262, limit: 2 }, + contractId: DAO_CONTRACT_ADDRESS, + }); + ``` + +了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near view nearweek-news-contribution.sputnik-dao.near get_proposals '{"from_index": 9262, "limit": 2}' + ``` + + +

+ + ```bash + [ + { + id: 9262, + proposer: 'pasternag.near', + description: 'NEAR, a top non-EVM blockchain, has gone live on Router's Testnet Mandara. With Router Nitro, our flagship dApp, users in the NEAR ecosystem can now transfer test tokens to and from NEAR onto other supported chains. $$$$https://twitter.com/routerprotocol/status/1727732303491961232', + kind: { + Transfer: { + token_id: '', + receiver_id: 'pasternag.near', + amount: '500000000000000000000000', + msg: null + } + }, + status: 'Approved', + vote_counts: { council: [ 1, 0, 0 ] }, + votes: { 'brzk-93444.near': 'Approve' }, + submission_time: '1700828277659425683' + }, + { + id: 9263, + proposer: 'fittedn.near', + description: 'How to deploy BOS component$$$$https://twitter.com/BitkubAcademy/status/1728003163318563025?t=PiN6pwS380T1N4JuQXSONA&s=19', + kind: { + Transfer: { + token_id: '', + receiver_id: 'fittedn.near', + amount: '500000000000000000000000', + msg: null + } + }, + status: 'InProgress', + vote_counts: { 'Whitelisted Members': [ 1, 0, 0 ] }, + votes: { 'trendheo.near': 'Approve' }, + submission_time: '1700832601849419123' + } + ] + ``` + +

+
+
+ + + + +```bash +[ + { + "id": 9262, + "proposer": "pasternag.near", + "description": "NEAR, a top non-EVM blockchain, has gone live on Router's Testnet Mandara. With Router Nitro, our flagship dApp, users in the NEAR ecosystem can now transfer test tokens to and from NEAR onto other supported chains. $$$$https://twitter.com/routerprotocol/status/1727732303491961232", + "kind": { + "Transfer": { + "token_id": "", + "receiver_id": "pasternag.near", + "amount": "500000000000000000000000", + "msg": null + } + }, + "status": "Approved", + "vote_counts": { + "council": [1, 0, 0] + }, + "votes": { + "brzk-93444.near": "Approve" + }, + "submission_time": "1700828277659425683" + }, + { + "id": 9263, + "proposer": "fittedn.near", + "description": "How to deploy BOS component$$$$https://twitter.com/BitkubAcademy/status/1728003163318563025?t=PiN6pwS380T1N4JuQXSONA&s=19", + "kind": { + "Transfer": { + "token_id": "", + "receiver_id": "fittedn.near", + "amount": "500000000000000000000000", + "msg": null + } + }, + "status": "Expired", + "vote_counts": { + "Whitelisted Members": [2, 0, 0] + }, + "votes": { + "trendheo.near": "Approve", + "vikash.near": "Approve" + }, + "submission_time": "1700832601849419123" + } +] +``` + + + +
+ +--- + +## 创建提案 + +创建提案,以便其他用户可以投票支持或反对。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const DAO_CONTRACT_ADDRESS = 'primitives.sputnik-dao.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: DAO_CONTRACT_ADDRESS, + method: 'add_proposal', + args: { + proposal: { + description: 'My first proposal', + kind: { + Transfer: { + token_id: '', + receiver_id: 'bob.near', + amount: '10000000000000000000000000', + }, + }, + }, + }, + gas: 300000000000000, + deposit: 100000000000000000000000, + }); + ``` + +了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call primitives.sputnik-dao.near add_proposal '{"proposal": {"description": "My first proposal", "kind": { "Transfer": {"token_id": "", "receiver_id": "bob.near", "amount": "10000000000000000000000000"}}}}' --deposit 0.1 --gas 300000000000000 --useAccount bob.near + ``` + + + + + + + ```rust + // Account ID that represents a token in near-sdk v3 + // Need to keep it around for backward compatibility + pub type OldAccountId = String; + +// How the voting policy votes get weighted. +#[near(serializers = [json, borsh]) #[derive(Clone, PartialEq)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum WeightKind { +// Using token amounts and total delegated at the moment. +TokenWeight, +// Weight of the group role. Roles that don't have scoped group are not supported. +RoleWeight, +} + +// Direct weight or ratio to total weight, used for the voting policy +#[near(serializers = [json, borsh]) #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] #[serde(untagged)] +pub enum WeightOrRatio { +Weight(U128), +Ratio(u64, u64), +} + +// Defines configuration of the vote +#[near(serializers = [json, borsh]) #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] +pub struct VotePolicy { +// Kind of weight to use for votes. +pub weight_kind: WeightKind, +// Minimum number required for vote to finalize. +// If weight kind is TokenWeight - this is minimum number of tokens required. +// This allows to avoid situation where the number of staked tokens from total supply is too small. +// If RoleWeight - this is minimum number of votes. +// This allows to avoid situation where the role is got too small but policy kept at 1/2, for example. +pub quorum: U128, +// How many votes to pass this vote. +pub threshold: WeightOrRatio, +} + +#[near(serializers = [json, borsh])] #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] +pub enum RoleKind { +// Matches everyone, who is not matched by other roles. +Everyone, +// Member greater or equal than given balance. Can use `1` as non-zero balance. +Member(U128), +// Set of accounts. +Group(HashSet), +} + +#[near(serializers = [json, borsh])] #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] +pub struct RolePermission { +// Name of the role to display to the user. +pub name: String, +// Kind of the role: defines which users this permissions apply. +pub kind: RoleKind, +// Set of actions on which proposals that this role is allowed to execute. +// : +pub permissions: HashSet, +// For each proposal kind, defines voting policy. +pub vote_policy: HashMap, +} + +// Defines voting / decision making policy of this DAO +#[near(serializers = [json, borsh])] #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] +pub struct Policy { +// List of roles and permissions for them in the current policy. +pub roles: Vec, +// Default vote policy. Used when given proposal kind doesn't have special policy. +pub default_vote_policy: VotePolicy, +// Proposal bond. +pub proposal_bond: U128, +// Expiration period for proposals. +pub proposal_period: U64, +// Bond for claiming a bounty. +pub bounty_bond: U128, +// Period in which giving up on bounty is not punished. +pub bounty_forgiveness_period: U64, +} + +// Versioned policy +#[near(serializers = [json, borsh])] #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))] +pub enum VersionedPolicy { +// Default policy with given accounts as council. +Default(Vec), +Current(Policy), +} + +// Function call arguments +#[near(serializers = [json, borsh])] #[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))] +pub struct ActionCall { +method_name: String, +args: Base64VecU8, +deposit: U128, +gas: U64, +} + +// Bounty information. +#[near(serializers = [json, borsh])] #[derive(Clone)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct Bounty { +/// Description of the bounty. +pub description: String, +/// Token the bounty will be paid out. +/// Can be "" for `$NEAR` or a valid account id. +pub token: OldAccountId, +/// Amount to be paid out. +pub amount: U128, +/// How many times this bounty can be done. +pub times: u32, +/// Max deadline from claim that can be spend on this bounty. +pub max_deadline: U64, +} + +// Info about factory that deployed this contract and if auto-update is allowed +#[near(serializers = [json, borsh])] #[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))] +pub struct FactoryInfo { +pub factory_id: AccountId, +pub auto_update: bool, +} + +// Function call arguments +#[near(serializers = [json, borsh])] #[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))] +pub struct PolicyParameters { +pub proposal_bond: Option, +pub proposal_period: Option, +pub bounty_bond: Option, +pub bounty_forgiveness_period: Option, +} + +// Votes recorded in the proposal +#[near(serializers = [json, borsh])] #[derive(Clone, Debug)] +pub enum Vote { +Approve = 0x0, +Reject = 0x1, +Remove = 0x2, +} + +// Configuration of the DAO +#[near(serializers = [json, borsh])] #[derive(Clone, Debug)] +pub struct Config { +// Name of the DAO. +pub name: String, +// Purpose of this DAO. +pub purpose: String, +// Generic metadata. Can be used by specific UI to store additional data. +// This is not used by anything in the contract. +pub metadata: Base64VecU8, +} + +// Kinds of proposals, doing different action +#[near(serializers = [json, borsh])] #[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))] +pub enum ProposalKind { +// Change the DAO config. +ChangeConfig { config: Config }, +// Change the full policy. +ChangePolicy { policy: VersionedPolicy }, +// Add member to given role in the policy. This is short cut to updating the whole policy. +AddMemberToRole { member_id: AccountId, role: String }, +// Remove member to given role in the policy. This is short cut to updating the whole policy. +RemoveMemberFromRole { member_id: AccountId, role: String }, +// Calls `receiver_id` with list of method names in a single promise. +// Allows this contract to execute any arbitrary set of actions in other contracts. +FunctionCall { +receiver_id: AccountId, +actions: Vec, +}, +// Upgrade this contract with given hash from blob store. +UpgradeSelf { hash: Base58CryptoHash }, +// Upgrade another contract, by calling method with the code from given hash from blob store. +UpgradeRemote { +receiver_id: AccountId, +method_name: String, +hash: Base58CryptoHash, +}, +// Transfers given amount of `token_id` from this DAO to `receiver_id`. +// If `msg` is not None, calls `ft_transfer_call` with given `msg`. Fails if this base token. +// For `ft_transfer` and `ft_transfer_call` `memo` is the `description` of the proposal. +Transfer { +// Can be "" for `$NEAR` or a valid account id. +token_id: OldAccountId, +receiver_id: AccountId, +amount: U128, +msg: Option, +}, +// Sets staking contract. Can only be proposed if staking contract is not set yet. +SetStakingContract { staking_id: AccountId }, +// Add new bounty. +AddBounty { bounty: Bounty }, +// Indicates that given bounty is done by given user. +BountyDone { +bounty_id: u64, +receiver_id: AccountId, +}, +// Just a signaling vote, with no execution. +Vote, +// Change information about factory and auto update. +FactoryInfoUpdate { factory_info: FactoryInfo }, +// Add new role to the policy. If the role already exists, update it. This is short cut to updating the whole policy. +ChangePolicyAddOrUpdateRole { role: RolePermission }, +// Remove role from the policy. This is short cut to updating the whole policy. +ChangePolicyRemoveRole { role: String }, +// Update the default vote policy from the policy. This is short cut to updating the whole policy. +ChangePolicyUpdateDefaultVotePolicy { vote_policy: VotePolicy }, +// Update the parameters from the policy. This is short cut to updating the whole policy. +ChangePolicyUpdateParameters { parameters: PolicyParameters }, +} + +#[near(serializers = [json])] +pub struct ProposalInput { +/// Description of this proposal. +pub description: String, +/// Kind of proposal with relevant information. +pub kind: ProposalKind, +} + +// Validator interface, for cross-contract calls #[ext_contract(ext_dao_contract)] +trait ExternalDaoContract { +fn add_proposal(&mut self, proposal: ProposalInput) -> Promise; +} + +// Implement the contract structure #[near] +impl Contract { #[payable] +pub fn create_proposal(&mut self, proposal: ProposalInput) -> Promise { +let promise = ext_dao_contract::ext(self.dao_contract.clone()) +.with_attached_deposit(env::attached_deposit()) +.with_static_gas(Gas(5\*TGAS)) +.add_proposal(proposal); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(50*TGAS)) + .external_proposal_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn external_proposal_callback(&self, #[callback_result] call_result: Result) -> Option { + if call_result.is_err() { + log!("There was an error contacting external contract"); + return None; + } + + // Return the proposal id + let id = call_result.unwrap(); + return Some(id); + } + +} + +```` + + + + + +默认情况下,只有**委员会成员**才能创建提案。 + + +--- + +## 为提案投票 + +以下代码片段将帮助您的用户对特定 DAO 的提案进行投票。 + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const DAO_CONTRACT_ADDRESS = 'primitives.sputnik-dao.near'; + +const { callFunction } = useNearWallet(); + +await callFunction({ + contractId: DAO_CONTRACT_ADDRESS, + method: 'act_proposal', + args: { id: 0, action: 'VoteApprove' }, + gas: 300000000000000, +}); +```` + +可用的投票选项:`VoteApprove`(赞成)、`VoteReject`(反对)、`VoteRemove`(移除)。 + +了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call primitives.sputnik-dao.near act_proposal '{"id": 0, "action": "VoteApprove"}' --gas 300000000000000 --useAccount bob.near + ``` + + 可用的投票选项:`VoteApprove`(赞成)、`VoteReject`(反对)、`VoteRemove`(移除)。 + + + + + + + + 可用的投票选项:`VoteApprove`(赞成)、`VoteReject`(反对)、`VoteRemove`(移除)。 + + + + + + ```rust +// Set of possible action to take +#[near(serializers = [json, borsh])] +#[derive(Debug)] +pub enum Action { + // Action to add proposal. Used internally. + AddProposal, + // Action to remove given proposal. Used for immediate deletion in special cases. + RemoveProposal, + // Vote to approve given proposal or bounty. + VoteApprove, + // Vote to reject given proposal or bounty. + VoteReject, + // Vote to remove given proposal or bounty (because it's spam). + VoteRemove, + // Finalize proposal, called when it's expired to return the funds + // (or in the future can be used for early proposal closure). + Finalize, + // Move a proposal to the hub to shift into another DAO. + MoveToHub, +} + +// Validator interface, for cross-contract calls #[ext_contract(ext_dao_contract)] +trait ExternalDaoContract { +fn act_proposal(&mut self, id: u64, action: Action, memo: Option) -> Promise; +} + +// Implement the contract structure #[near] +impl Contract { #[payable] +pub fn act_proposal(&mut self, id: u64, action: Action, memo: Option) -> Promise { +let promise = ext_dao_contract::ext(self.dao_contract.clone()) +.with_attached_deposit(env::attached_deposit()) +.with_static_gas(Gas(10\*TGAS)) +.act_proposal(id, action, memo); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .external_common_callback() + ) + +} + +#[private] // Public - but only callable by env::current_account_id() +pub fn external_common_callback(&self, #[callback_result] call_result: Result<(), PromiseError>) { +// Check if the promise succeeded +if call_result.is_err() { +log!("There was an error contacting external contract") +} +} +} + +``` + + + +--- + +## 其他资源 + +1. [NEAR Treasury](https://neartreasury.com/) - 一个基于 Sputnik DAO 合约构建的国库管理 Web 应用,帮助用户轻松创建和管理资金。 diff --git a/zh/primitives/dex.mdx b/zh/primitives/dex.mdx new file mode 100644 index 00000000000..9544dcee9fc --- /dev/null +++ b/zh/primitives/dex.mdx @@ -0,0 +1,592 @@ +--- +title: 去中心化交易所(DEX) +description: "了解如何在 NEAR 协议上与去中心化交易所进行交互,包括代币兑换、流动性池以及与 Ref Finance DEX 的集成。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; + +去中心化交易所(DEX)是一种允许用户通过智能合约交易代币(原生代币和同质化代币)的应用程序。 + +![dex](/assets/docs/primitives/dex.png) + +简而言之,DEX 通过持有代币对的[流动性池](https://guide.rhea.finance/products/overview/pooling)(例如 NEAR-USDC)来运作,用户可以向这些池子存入代币。 + +池子中代币的比率决定了兑换汇率。实际上,兑换就是向池子一侧添加代币,同时从另一侧取出代币。 + + + +本文档参考的是 [Ref Finance](https://www.ref.finance/),这是 NEAR 上由社区构建的 DEX。 + +更多信息请查看他们的[文档](https://guide.rhea.finance/)。 + + + +--- + +## 查询代币汇率 +可以通过调用 DEX 合约的 `get-token-price` 方法来查询代币对的汇率。 + + + + + ```js + const tokenContract = 'token.v2.ref-finance.near'; + const tokenPriceResult = await fetch( + `https://indexer.ref.finance/get-token-price?token_id=${tokenContract}`, + ); + const tokenPriceValue = await tokenPriceResult.json(); + ``` + + +

+ + ```json + { + "token_contract_id": "token.v2.ref-finance.near", + "price": "0.08153090" + } + ``` +

+
+ + + Ref Finance 提供了一种[一次性获取所有代币价格](https://indexer.ref.finance/list-token-price)的方法。 + + +
+
+ +--- + +## 查询白名单代币 +任何人都可以在 DEX 上挂牌出售代币。因此,为了保护用户,DEX 合约维护了一份可交易代币的白名单。 + + + + +```bash +near view v2.ref-finance.near get_whitelisted_tokens +``` + + +```bash + 'wrap.near', + 'usdt.tether-token.near', + 'berryclub.ek.near', + 'farm.berryclub.ek.near', + 'token.v2.ref-finance.near', + 'token.paras.near', + 'marmaj.tkn.near', + 'meta-pool.near', + ... +``` + + + + + + + + + ```bash + 'wrap.near', + 'usdt.tether-token.near', + 'berryclub.ek.near', + 'farm.berryclub.ek.near', + 'token.v2.ref-finance.near', + 'token.paras.near', + 'marmaj.tkn.near', + 'meta-pool.near', + ... + ``` + + + + + +--- + +## 在 DEX 中注册 +要使用该合约,请确保通过支付存储费用在 DEX 中注册您的账户,以便跟踪您的余额。 + + + + +```bash +near call v2.ref-finance.near storage_deposit '' --useAccount --amount 0.1 +``` + + + + + + + + +--- + +## 存入资金 + +要兑换代币,首先需要将代币存入 DEX。为此,您需要将想要兑换的 FT 转入 DEX 合约。 + + + + +```bash +near call token.v2.ref-finance.near ft_transfer_call {"receiver_id": "v2.ref-finance.near", "amount": "1000000000000", "msg": ""} --gas 300000000000000 --depositYocto 1 --useAccount +``` + + + + + + + + + +**不要**将 **NEAR** 代币直接转入 Ref Finance。请调用 [`wrap.near`](https://nearblocks.io/address/wrap.near) 合约中的 `near_deposit`,并附上您想兑换的 NEAR 数量。 + +这将为您铸造 `wrap.near`,然后您可以将其转入 Ref Finance。 + + + +--- + +## 查询存款余额 + +通过调用 `get_deposits` 方法查询您的存款余额: + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const AMM_CONTRACT_ADDRESS = 'v2.ref-finance.near'; + + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'get_deposits', + args: { + account_id: 'bob.near', + }, + contractId: AMM_CONTRACT_ADDRESS, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + +

+ + ```json + { + "token.v2.ref-finance.near": "0", + "wrap.near": "0" + } + ``` +

+
+ +
+ + + ```bash + near view v2.ref-finance.near get_deposits '{"account_id": "bob.near"}' + ``` + + +

+ + ```bash + { + 'token.v2.ref-finance.near': '0', + 'wrap.near': "0" + } + ``` +

+
+
+ + + + + +

+ + ```bash + { + 'token.v2.ref-finance.near': '0', + 'wrap.near': "0" + } + ``` + +

+
+
+ + + ```rust + // Validator interface, for cross-contract calls + #[ext_contract(ext_amm_contract)] + trait ExternalAmmContract { + fn get_deposits(&self, account_id: AccountId) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + #[private] // Public - but only callable by env::current_account_id() + pub fn external_get_deposits_callback(&self, #[callback_result] call_result: Result, PromiseError>) -> Option> { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting external contract"); + return None; + } + + // Return the pools data + let deposits_data = call_result.unwrap(); + return Some(deposits_data); + } + + pub fn get_contract_deposits(&self) -> Promise { + let promise = ext_amm_contract::ext(self.amm_contract.clone()) + .get_deposits(env::current_account_id()); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .external_get_deposits_callback() + ) + } + } + ``` + +
+ +--- + +### 查询流动性池 + +DEX 通过持有多个代币对流动性池(例如 NEAR-USDC)来运作,用户可以向这些池子存入代币。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const AMM_CONTRACT_ADDRESS = 'v2.ref-finance.near'; + + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'get_pools', + args: { + from_index: 0, + limit: 1000, + }, + contractId: AMM_CONTRACT_ADDRESS, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + +

+ + ```js + [ + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: ['token.skyward.near', 'wrap.near'], + amounts: ['51865812079751349630100', '6254162663147994789053210138'], + total_fee: 30, + shares_total_supply: '1305338644973934698612124055', + amp: 0, + }, + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: [ + 'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.factory.bridge.near', + 'wrap.near', + ], + amounts: ['783621938569399817', '1100232280852443291118200599'], + total_fee: 30, + shares_total_supply: '33923015415693335344747628', + amp: 0, + }, + ]; + ``` +

+
+ +
+ + + ```bash + near view v2.ref-finance.near get_pools '{"from_index": 0, "limit": 1000}' + ``` + +

+ + ```bash + [ + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: [ 'token.skyward.near', 'wrap.near' ], + amounts: [ '51865812079751349630100', '6254162663147994789053210138' ], + total_fee: 30, + shares_total_supply: '1305338644973934698612124055', + amp: 0 + }, + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: [ + 'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.factory.bridge.near', + 'wrap.near' + ], + amounts: [ '783621938569399817', '1100232280852443291118200599' ], + total_fee: 30, + shares_total_supply: '33923015415693335344747628', + amp: 0 + } + ] + ``` +

+
+ +
+ + + + + +

+ + ```bash + [ + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: [ 'token.skyward.near', 'wrap.near' ], + amounts: [ '51865812079751349630100', '6254162663147994789053210138' ], + total_fee: 30, + shares_total_supply: '1305338644973934698612124055', + amp: 0 + }, + { + pool_kind: 'SIMPLE_POOL', + token_account_ids: [ + 'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.factory.bridge.near', + 'wrap.near' + ], + amounts: [ '783621938569399817', '1100232280852443291118200599' ], + total_fee: 30, + shares_total_supply: '33923015415693335344747628', + amp: 0 + } + ] + ``` +

+
+ +
+ + + ```rust + #[near(serializers = [json])] + pub struct PoolInfo { + /// Pool kind. + pub pool_kind: String, + /// List of tokens in the pool. + pub token_account_ids: Vec, + /// How much NEAR this contract has. + pub amounts: Vec, + /// Fee charged for swap. + pub total_fee: u32, + /// Total number of shares. + pub shares_total_supply: U128, + pub amp: u64, + } + + // Validator interface, for cross-contract calls + #[ext_contract(ext_amm_contract)] + trait ExternalAmmContract { + fn get_pools(&self, from_index: u64, limit: u64) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + #[private] // Public - but only callable by env::current_account_id() + pub fn external_get_pools_callback(&self, #[callback_result] call_result: Result, PromiseError>) -> Option> { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting external contract"); + return None; + } + + // Return the pools data + let pools_data = call_result.unwrap(); + return Some(pools_data); + } + + pub fn get_amm_pools(&self, from_index: u64, limit: u64) -> Promise { + let promise = ext_amm_contract::ext(self.amm_contract.clone()) + .get_pools(from_index, limit); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .external_get_pools_callback() + ) + } + } + ``` + +
+ +--- + +## 兑换代币 +要将一种代币兑换为另一种代币,您需要[拥有资金](#deposit-funds),并且需要[**存在一个同时包含两种代币**的流动性池](#query-pools)。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const AMM_CONTRACT_ADDRESS = 'v2.ref-finance.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: AMM_CONTRACT_ADDRESS, + method: 'swap', + args: { + actions: [ + { + pool_id: 79, + token_in: 'token.v2.ref-finance.near', + token_out: 'wrap.near', + amount_in: '100000000000000000', + min_amount_out: '1', + }, + ], + }, + gas: 300000000000000, + deposit: 1, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../web3-apps/tutorials/wallet-login) 的信息 + + + + ```json + "5019606679394603179450" + ``` + + + + + + ```bash + near call v2.ref-finance.near swap "{\"actions\": [{\"pool_id\": 79, \"token_in\": \"token.v2.ref-finance.near\", \"amount_in\": \"100000000000000000\", \"token_out\": \"wrap.near\", \"min_amount_out\": \"1\"}]}" --gas 300000000000000 --depositYocto 1 + --useAccount bob.near + ``` + + +

+ + ```bash + '5019606679394603179450' + ``` +

+
+ +
+ + + +

+ + ```bash + '5019606679394603179450' + ``` +

+
+
+ + + ```rust + #[near(serializers = [json])] + pub struct SwapAction { + /// Pool which should be used for swapping. + pub pool_id: u64, + /// Token to swap from. + pub token_in: AccountId, + /// Amount to exchange. + /// If amount_in is None, it will take amount_out from previous step. + /// Will fail if amount_in is None on the first step. + pub amount_in: Option, + /// Token to swap into. + pub token_out: AccountId, + /// Required minimum amount of token_out. + pub min_amount_out: U128, + } + + // Validator interface, for cross-contract calls + #[ext_contract(ext_amm_contract)] + trait ExternalAmmContract { + fn swap(&self, actions: Vec) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + #[private] // Public - but only callable by env::current_account_id() + pub fn external_call_callback(&self, #[callback_result] call_result: Result) { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting external contract"); + } + } + + #[payable] + pub fn swap_tokens(&mut self, pool_id: u64, token_in: AccountId, token_out: AccountId, amount_in: U128, min_amount_out: U128) -> Promise { + assert_eq!(env::attached_deposit(), 1, "Requires attached deposit of exactly 1 yoctoNEAR"); + + let swap_action = SwapAction { + pool_id, + token_in, + token_out, + amount_in: Some(amount_in), + min_amount_out + }; + + let mut actions = Vec::new(); + actions.push(swap_action); + + let promise = ext_amm_contract::ext(self.amm_contract.clone()) + .with_static_gas(Gas(150*TGAS)) + .with_attached_deposit(YOCTO_NEAR) + .swap(actions); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(100*TGAS)) + .external_call_callback() + ) + } + } + ``` + +
+ +--- + +## 其他资源 + +1. [从锁定合约领取同质化代币](https://near.org/near/widget/ComponentDetailsPage?src=whtt.near/widget/Draft-0) - 演示如何从 `lockup.burrow.near` 合约领取锁定代币的示例。 +2. [BSC DEX 合集](https://near.org/near/widget/ComponentDetailsPage?src=bluebiu.near/widget/Bsc.Swap.Dex) - 展示如何为 DEX 构建简单兑换页面的示例。 diff --git a/zh/primitives/didnear.mdx b/zh/primitives/didnear.mdx new file mode 100644 index 00000000000..b03f57c38dc --- /dev/null +++ b/zh/primitives/didnear.mdx @@ -0,0 +1,243 @@ +--- +title: 去中心化标识符(DID) +description: "了解 NEAR 上符合 W3C 标准的身份解析。" +--- + +本文档详细介绍了如何在 NEAR 区块链上使用 `did:near` 方法实现去中心化标识符、其与 [W3C DID Core](https://www.w3.org/TR/did-core/) 的兼容性,以及如何与可验证凭证(VC)、证明注册表和解析工具集成。 + +--- + +## 什么是 DID? + +**去中心化标识符(DID)** 是一种持久的、唯一的标识符,无需中心化注册机构。在 NEAR 上,DID 基于账户名称或原始公钥创建,并完全兼容 W3C DID 标准。 + + + + 与 NTT DATA Innovation Center Web3 合作构建 + + +--- + +## `did:near` 方法 + +该方法支持两种具有不同解析策略的 DID 类型: + +| 类型 | 示例 | 描述 | +|--------------------|-----------------------------------------|--------------------------------------| +| 命名账户 | `did:near:alice.testnet` 或 `did:near:alice.near` | 直接从链上 NEAR 账户密钥(完全访问密钥)解析 | +| 注册表 DID(Base58) | `did:near:CF5RiJYh4EVmEt8UADTjoP3XaZo1NPWxv6w5TmkLqjpR` | 通过智能合约注册表使用 `identity_owner` 方法解析 | + +--- + +## DID 文档格式 + +解析 `did:near` 的示例输出: + +```json +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "id": "did:near:alice.testnet", + "verificationMethod": [ + { + "id": "did:near:alice.testnet#owner", + "type": "Ed25519VerificationKey2018", + "controller": "did:near:alice.testnet", + "publicKeyBase58": "7WLUHT69sw5UpYK9xAY5cbdWKf4vSMruXzwfbL999zXo" + } + ], + "authentication": ["did:near:alice.testnet#owner"], + "assertionMethod": ["did:near:alice.testnet#owner"] +} +``` + +该结构遵循 [W3C DID Core 规范](https://www.w3.org/TR/did-core/),包括: + +- DID 标识符(`id`) +- 密钥引用(`verificationMethod`) +- 认证和断言能力 +- Base58 编码的 Ed25519 公钥 + +--- + +## ⚙️ 代码仓库 + +| 组件 | 仓库地址 | +|----------------|----------------| +| DID 注册表(合约) | https://github.com/DTI-web3/did-near | +| ProofType 合约 | https://github.com/DTI-web3/did-near-prooftype | +| ProofType SDK | https://www.npmjs.com/package/@kaytrust/prooftypes | +| DID 解析器 SDK | https://github.com/DTI-web3/did-near-resolver | + +--- + +## 解析原理 + +解析器根据 DID 格式使用不同的策略: + +### 命名账户 DID(`did:near:alice.testnet`) +1. 从 DID 中解析账户 ID(例如 `alice.testnet`)。 +2. 根据后缀确定网络(`.testnet` → 测试网,`.near` → 主网)。 +3. 使用 `near-api-js` 查询 NEAR RPC 以获取账户访问密钥。 +4. 筛选具有 `permission: "FullAccess"` 的密钥。 +5. 将所有完全访问公钥作为验证方法构造 DID 文档。 + +### 注册表 DID(`did:near:CF5RiJYh4EVmEt8UADTjoP3XaZo1NPWxv6w5TmkLqjpR`) +1. 识别 DID 为 Base58 格式(符合 `[1-9A-HJ-NP-Za-km-z]+` 的 44-50 字符字符串)。 +2. 在已配置的注册表合约上调用 `identity_owner` 查看方法。 +3. 获取为该 Base58 标识符注册的所有者 DID。 +4. 使用注册表中的公钥构造 DID 文档。 + +--- + +## 使用方法 + +### 创建 DID + +#### 命名账户 DID(直接解析) +如果使用 NEAR 钱包账户,DID 直接从账户名称派生: +```ts +const did = "did:near:yourname.testnet"; // 测试网 +const did = "did:near:yourname.near"; // 主网 +``` + +#### 注册表 DID(基于合约) +如果使用在智能合约中注册的 Base58 编码标识符: +```ts +const did = "did:near:CF5RiJYh4EVmEt8UADTjoP3XaZo1NPWxv6w5TmkLqjpR"; +``` + +#### 指定网络的 DID +您可以显式指定网络: +```ts +const did = "did:near:testnet:CF5RiJYh4EVmEt8UADTjoP3XaZo1NPWxv6w5TmkLqjpR"; // 明确指向测试网 +const did = "did:near:near:CF5RiJYh4EVmEt8UADTjoP3XaZo1NPWxv6w5TmkLqjpR"; // 明确指向主网 +``` + +--- + +## 发行和验证凭证 + +### 第一步 – 对凭证进行哈希处理 + +```ts +const cid = SHA256(JSON.stringify(credential)).toString(CryptoJS.enc.Base64); +``` + +### 第二步 – 在 NEAR 上发行 + +```ts +await proofType.generateProof(credential, { + wallet, + cid +}); +``` + +这将在链上记录哈希值(`cid`)及对应的 `subject_did`。 + +### 第三步 – 链上验证 + +```ts +const isValid = await proofType.verifyProof("did:near:subject|bafy..."); // 返回 true 或 false +``` + +--- + +## SDK 使用 + +### 解析器 SDK + +#### 独立使用 + +```ts +import { NearDIDResolver } from '@kaytrust/did-near-resolver'; + +const resolver = new NearDIDResolver({ + networks: [ + { + networkId: 'testnet', + rpcUrl: 'https://rpc.testnet.near.org', + contractId: 'neardidregistry.testnet' // 可选:仅用于注册表 DID + }, + { + networkId: 'mainnet', // 可以使用 'near' 表示主网('mainnet' 和 'near' 等效) + rpcUrl: 'https://rpc.mainnet.near.org', + contractId: 'neardidregistry.near' // 可选 + } + ] +}); + +const doc = await resolver.resolveDID("did:near:alice.testnet"); +console.log(doc.id); // "did:near:alice.testnet" +``` + +#### 与 `did-resolver` 集成 + +```ts +import { Resolver } from 'did-resolver'; +import { getResolver } from '@kaytrust/did-near-resolver'; + +const nearResolver = getResolver({ + networks: [ + { + networkId: 'testnet', + rpcUrl: 'https://rpc.testnet.near.org', + contractId: 'neardidregistry.testnet' + } + ] +}); + +const resolver = new Resolver({ + ...nearResolver, + // ... 其他 DID 方法 +}); + +const result = await resolver.resolve('did:near:alice.testnet'); +console.log(result.didDocument); +``` + +### ProofType SDK + +```ts +import { ProofTypeNear } from '@kaytrust/prooftypes'; + +const proofType = new ProofTypeNear(); +const proof = await proofType.generateProof(vcPayload, { wallet, cid }); +const isValid = await proofType.verifyProof(proof); +``` + +--- + +## 安全性与合规性 + +- 每个 `(did, cid)` 对只能注册一次。 +- 发行者通过 NEAR 交易与签名账户绑定。 +- DID 解析和结构完全符合 W3C 标准。 +- `cid` 始终是 VC 内容的 Base64 编码 SHA-256 哈希值。 +- **密钥安全**:DID 文档的 `verificationMethod` 数组中仅包含 `FullAccess` 密钥,确保只有具备完整账户权限的密钥才能用于认证。 +- 公钥通过 NEAR 原生账户或自定义注册表安全处理。 +- **多网络支持**:解析器根据 DID 后缀自动将请求路由到正确的网络(主网/测试网)。 + +--- + +## 测试网部署 + +- 注册表合约:`neardidregistry.testnet` +- ProofType 合约:`neardtiprooftype.testnet` +- 网络:`testnet` +- 语言:Rust(`near-sdk`) + +--- + +## 结论 + +`did:near` 方法在 NEAR 上实现了完整的去中心化身份流程,包括: + +- 基于账户和密钥的标识符 +- 符合 W3C 标准的 DID 文档 +- 由智能合约支持的凭证证明 +- 用于解析和证明的 SDK + +使用此技术栈,在 NEAR 上构建可移植、可验证且安全的 Web3 身份解决方案。 diff --git a/zh/primitives/ft/ft.mdx b/zh/primitives/ft/ft.mdx new file mode 100644 index 00000000000..a591a907af7 --- /dev/null +++ b/zh/primitives/ft/ft.mdx @@ -0,0 +1,561 @@ +--- +title: 使用同质化代币(FT) +description: "了解如何在您的 dApp 中创建、转移和集成 FT" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; +import { Github } from '/snippets/github.jsx'; + +想要在您的 dApp 中使用同质化代币(FT)?在这里您可以找到所有入门所需的信息,包括创建代币、注册用户、查询余额、转移代币以及将其集成到智能合约中。 + +--- + +## 创建新代币 + +创建新同质化代币最简便的方式是与工厂合约交互,您只需提供代币元数据,工厂合约会自动部署并初始化一个[标准 FT 合约](https://github.com/near-examples/FT)。 + + + +以下是如何通过您的应用程序直接与工厂合约交互: + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_FACTORY_ADDRESS = 'token.primitives.near'; + + const { callFunction } = useNearWallet(); + + const args = { + args: { + owner_id: 'bob.near', + total_supply: '1000000000', + metadata: { + spec: 'ft-1.0.0', + name: 'Test Token', + symbol: 'test', + icon: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', + decimals: 18, + }, + }, + account_id: 'bob.near', + }; + + await callFunction({ + contractId: TOKEN_FACTORY_ADDRESS, + method: 'create_token', + args, + gas: 300000000000000, + deposit: '2234830000000000000000', + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call token.primitives.near create_token '{"args":{"owner_id": "bob.near","total_supply": "1000000000","metadata":{"spec": "ft-1.0.0","name": "Test Token","symbol": "TTTEST","icon": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","decimals": 18}},"account_id": "bob.near"}' --gas 300000000000000 --depositYocto 2234830000000000000000000 --useAccount bob.near + ``` + + + + + + +您创建的 FT 将存在于账户 `.token.primitives.near`(例如 `test.token.primitives.near`)中。 + + + +--- + +## 部署您自己的合约 + +您也可以通过部署并初始化[标准 FT 合约](https://github.com/near-examples/FT)来创建同质化代币。 + +初始化时,您需要定义代币的元数据,例如名称(如 Ethereum)、符号(如 ETH)和总供应量(如 1000 万)。您还需要定义一个 `owner`(所有者),该所有者将拥有代币的**全部总供应量**。 + +要初始化 FT 合约,您需要先部署它,然后调用 `new` 方法定义代币的元数据。 + + + + + ```bash + cargo near deploy build-non-reproducible-wasm \ + with-init-call new \ + json-args '{ + "owner_id": "", + "total_supply": "1000000000000000", + "metadata": { + "spec": "ft-1.0.0", + "name": "Example Token Name", + "symbol": "EXLT", + "decimals": 8 + } + }' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain send + ``` + + + + + + +### 全局合约 + +您可以使用我们的全局 FT 合约部署新的同质化代币——这是一个预先部署的[标准 FT 合约](https://github.com/near-examples/FT),可供重复使用。[全局合约](../../smart-contracts/global-contracts)只需部署一次,任何账户均可复用,无需承担高额的存储成本。 + + + + + ```bash + near contract deploy use-global-account-id ft.globals.primitives.testnet \ + with-init-call \ + new_default_meta \ + json-args '{"owner_id": "", "total_supply": "100000000000000000000000000000"}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain send + ``` + + + + ```bash + near contract deploy use-global-hash 3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX \ + with-init-call \ + new_default_meta \ + json-args '{"owner_id": "", "total_supply": "100000000000000000000000000000"}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain send + ``` + + + + +通过**哈希**部署会创建一个永不更改的不可变合约。通过**账户 ID** 部署则创建一个可更新的合约,当所引用账户的合约更新时,该合约也会随之更新。请根据您是否希望 FT 合约可更新或永久不变来做出选择。 + + +--- + +## 查询元数据 +您可以通过调用 `ft_metadata` 查询 FT 的元数据。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_CONTRACT_ADDRESS = 'token.v2.ref-finance.near'; + + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'ft_metadata', + args: {}, + contractId: TOKEN_CONTRACT_ADDRESS, + }); + ``` + + +

+ + ```json + { + "spec": "ft-1.0.0", + "name": "Ref Finance Token", + "symbol": "REF", + "icon": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='16 24 248 248' style='background: %23000'%3E%3Cpath d='M164,164v52h52Zm-45-45,20.4,20.4,20.6-20.6V81H119Zm0,18.39V216h41V137.19l-20.6,20.6ZM166.5,81H164v33.81l26.16-26.17A40.29,40.29,0,0,0,166.5,81ZM72,153.19V216h43V133.4l-11.6-11.61Zm0-18.38,31.4-31.4L115,115V81H72ZM207,121.5h0a40.29,40.29,0,0,0-7.64-23.66L164,133.19V162h2.5A40.5,40.5,0,0,0,207,121.5Z' fill='%23fff'/%3E%3Cpath d='M189 72l27 27V72h-27z' fill='%2300c08b'/%3E%3C/svg%3E%0A", + "reference": null, + "reference_hash": null, + "decimals": 18 + } + ``` + +

+
+ + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + +
+ + + ```bash + near view token.v2.ref-finance.near ft_metadata + ``` + + +

+ + ```bash + { + spec: "ft-1.0.0", + name: "Ref Finance Token", + symbol: "REF", + icon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='16 24 248 248' style='background: %23000'%3E%3Cpath d='M164,164v52h52Zm-45-45,20.4,20.4,20.6-20.6V81H119Zm0,18.39V216h41V137.19l-20.6,20.6ZM166.5,81H164v33.81l26.16-26.17A40.29,40.29,0,0,0,166.5,81ZM72,153.19V216h43V133.4l-11.6-11.61Zm0-18.38,31.4-31.4L115,115V81H72ZM207,121.5h0a40.29,40.29,0,0,0-7.64-23.66L164,133.19V162h2.5A40.5,40.5,0,0,0,207,121.5Z' fill='%23fff'/%3E%3Cpath d='M189 72l27 27V72h-27z' fill='%2300c08b'/%3E%3C/svg%3E%0A", + reference: null, + reference_hash: null, + decimals: 18 + } + ``` +

+
+
+ + + +
+ +--- + +## 查询余额 +要了解用户持有多少代币,您需要查询 `ft_balance_of` 方法。 + + + + + + 请注意同质化代币的精度。您可能需要使用该值,以便在应用程序中以用户易于理解的方式显示余额查询结果。获取精度值(decimals)的方法请参见[此处](#querying-metadata)。 + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_CONTRACT_ADDRESS = 'token.v2.ref-finance.near'; + + const { viewFunction } = useNearWallet(); + + await viewFunction({ + method: 'ft_balance_of', + args: { + account_id: 'bob.near', + }, + contractId: TOKEN_CONTRACT_ADDRESS, + }); + ``` + + +

+ + ```json + "3479615037675962643842" + ``` + +

+
+ + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + +
+ + + ```bash + near view token.v2.ref-finance.near ft_balance_of '{"account_id": "bob.near"}' + ``` + + +

+ + ```bash + '376224322825327177426' + ``` +

+
+
+ + + +
+ +--- + +## 注册用户 +用户若要持有和转移代币,首先需要在合约中**注册**。注册方式是调用 `storage_deposit` 并附上 0.00125Ⓝ。 + +通过调用 `storage_deposit`,用户可以注册自己或**替其他用户注册**。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_CONTRACT_ADDRESS = 'token.v2.ref-finance.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: TOKEN_CONTRACT_ADDRESS, + method: 'storage_deposit', + args: { + account_id: 'alice.near', + }, + deposit: 1250000000000000000000, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call token.v2.ref-finance.near storage_deposit '{"account_id": "alice.near"}' --depositYocto 1250000000000000000000 --useAccount bob.near + ``` + + + + + + + +您可以通过调用 `storage_balance_of` 确认用户已成功注册。 + + + +用户调用 `storage_deposit` 后,FT 将出现在其钱包中。 + + +--- + +## 转移代币 +要将 FT 发送给其他账户,您需要使用 `ft_transfer` 方法,指定接收者和要发送的 FT 数量。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_CONTRACT_ADDRESS = 'token.v2.ref-finance.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: TOKEN_CONTRACT_ADDRESS, + method: 'ft_transfer', + args: { + receiver_id: 'alice.near', + amount: '100000000000000000', + }, + deposit: 1, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call token.v2.ref-finance.near ft_transfer '{"receiver_id": "alice.near", "amount": "100000000000000000"}' --depositYocto 1 --useAccount bob.near + ``` + + + + + + + ```rust + #[near] + impl Contract { + #[payable] + pub fn send_tokens(&mut self, receiver_id: AccountId, amount: U128) -> Promise { + assert_eq!(env::attached_deposit(), 1, "Requires attached deposit of exactly 1 yoctoNEAR"); + + let promise = ext(self.ft_contract.clone()) + .with_attached_deposit(YOCTO_NEAR) + .ft_transfer(receiver_id, amount, None); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(30*TGAS)) + .external_call_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn external_call_callback(&self, #[callback_result] call_result: Result<(), PromiseError>) { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting external contract"); + } + } + } + ``` + + _此代码片段假设合约已持有一些 FT,并且您希望将其发送给其他账户。_ + + + +--- + +## 将 FT 附加到调用 + +原生情况下,只有 NEAR 代币(Ⓝ)可以附加到函数调用中。然而,FT 标准允许通过以 FT 合约为中介的方式**将同质化代币附加**到调用中。 + +这意味着您不是直接将代币附加到调用中,而是请求 FT 合约代表您同时执行转账和函数调用。 + +假设您需要在 [Ref Finance](https://rhea.finance/) 上存入 FT。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const TOKEN_CONTRACT_ADDRESS = 'token.v2.ref-finance.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: TOKEN_CONTRACT_ADDRESS, + method: 'ft_transfer_call', + args: { + receiver_id: 'v2.ref-finance.near', + amount: '100000000000000000', + msg: '', + }, + gas: 300000000000000, + deposit: 1, + }); + ``` + + +

+ + ```json + "100000000000000000" + ``` + +

+
+ + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + +
+ + + ```bash + near call token.v2.ref-finance.near ft_transfer_call '{"receiver_id": "v2.ref-finance.near", "amount": "100000000000000000", "msg": ""}' --gas 300000000000000 --depositYocto 1 --useAccount bob.near + ``` + + +

+ + ```bash + '100000000000000000' + ``` + +

+
+ +
+ + + + + + ```rust + #[payable] + pub fn call_with_attached_tokens(&mut self, receiver_id: AccountId, amount: U128) -> Promise { + assert_eq!(env::attached_deposit(), 1, "Requires attached deposit of exactly 1 yoctoNEAR"); + + let promise = ext(self.ft_contract.clone()) + .with_static_gas(Gas(150*TGAS)) + .with_attached_deposit(YOCTO_NEAR) + .ft_transfer_call(receiver_id, amount, None, "".to_string()); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(100*TGAS)) + .external_call_callback() + ) + } + ``` + +
+ +工作原理: + +1. 您调用 FT 合约的 `ft_transfer_call`,传入接收者、消息和数量。 +2. FT 合约将数量转入接收者账户。 +3. FT 合约调用 `receiver.ft_on_transfer(sender, msg, amount)`。 +4. FT 合约在 `ft_resolve_transfer` 回调中处理错误。 +5. FT 合约返回实际使用的附加数量。 + +--- + +## 处理存款 + +如果您希望合约处理 FT 存款,您需要实现 `ft_on_transfer` 方法。执行时,该方法将获知: + +- 哪种 FT 被转入,因为它是前驱账户。 +- 谁在发送 FT,因为它是参数。 +- 转入了多少 FT,因为它是参数。 +- 是否有任何编码为消息的参数。 + +`ft_on_transfer` 必须返回**需要退还**的 FT 数量,FT 合约会将其返还给发送者。 + +以下是来自我们[拍卖教程](../../web3-apps/tutorials/mastering-near/3.2-ft)的示例,其中实现了 `ft_on_transfer` 来处理 FT 出价: + + + +_注意:[`near_contract_standards::fungible_token::receiver`](https://docs.rs/near-contract-standards/latest/near_contract_standards/fungible_token/receiver/trait.FungibleTokenReceiver.html) 模块提供了一个 `FungibleTokenReceiver` 特征,您可以在合约中实现它_ + +--- + +## 销毁代币 + +虽然 FT 标准没有定义 `burn` 方法,但您可以直接将代币转入无人控制的账户,例如 [`0000000000000000000000000000000000000000000000000000000000000000`](https://nearblocks.io/es/address/0000000000000000000000000000000000000000000000000000000000000000)(64 个零)。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: 'token.v2.ref-finance.near', + method: 'ft_transfer', + args: { + receiver_id: '0000000000000000000000000000000000000000000000000000000000000000', + amount: '100000000000000000', + }, + deposit: 1, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call token.v2.ref-finance.near ft_transfer '{"receiver_id": "0000000000000000000000000000000000000000000000000000000000000000", "amount": "100000000000000000"}' --depositYocto 1 --useAccount bob.near + ``` + + + +--- + +## 其他资源 + +1. [NEP-141 标准](https://github.com/near/NEPs/tree/master/neps/nep-0141.md) +2. [NEP-148 标准](https://github.com/near/NEPs/tree/master/neps/nep-0148.md) +3. [FT 事件标准](https://github.com/near/NEPs/blob/master/neps/nep-0300.md) +4. [FT 参考实现](https://github.com/near-examples/FT) +5. [同质化代币 101](../../smart-contracts/tutorials/zero-to-hero/fts) - 一套涵盖如何使用 Rust 创建 FT 合约的教程。 diff --git a/zh/primitives/ft/sdk-contract-tools.mdx b/zh/primitives/ft/sdk-contract-tools.mdx new file mode 100644 index 00000000000..af183f25a1d --- /dev/null +++ b/zh/primitives/ft/sdk-contract-tools.mdx @@ -0,0 +1,127 @@ +--- +title: 使用 Contract Tools 创建 FT +description: "了解如何使用 Contract Tools 包创建同质化代币(FT)" +--- + +import { Github } from '/snippets/github.jsx'; + +在本教程中,我们将使用 [NEAR SDK Contract Tools](https://github.com/near/near-sdk-contract-tools) 包创建一个同质化代币(FT)。该包是一套通用工具和模式的集合,旨在简化智能合约开发,包括: + +- 存储费用管理 +- 托管模式和派生宏 +- 所有者模式和派生宏 +- 暂停模式和派生宏 +- 基于角色的访问控制 +- 用于 [NEP 标准](./standard)的派生宏 + - NEP-141(同质化代币),扩展 NEP-148 + - NEP-145(存储管理),以及与同质化代币和非同质化代币标准的集成 + - NEP-171(非同质化代币),扩展 NEP-177、NEP-178、NEP-181 + - NEP-297(事件) + +--- + +## 简介 + +虽然可以仅使用 `near-sdk` 和 `near_contract_standards` 从头开始创建同质化代币(FT)合约(例如 [FT 合约](https://github.com/near-examples/FT)),但使用 `near-sdk-contract-tools` 是更简便的方式。 + +`near-sdk-contract-tools` 允许我们通过在合约结构体上派生宏来实现铸造/销毁逻辑、访问控制和其他 FT 标准,就像 `OpenZeppelin` 为以太坊合约所做的那样。 + +--- + +## 基本 FT 方法 + +要为合约派生基本 FT 方法,我们需要在合约结构体上派生 `FungibleToken` 宏: + + + +这将为合约引入 NEP-141 标准中定义的所有基本 FT 方法: +- `new` +- `contract_source_metadata` +- `ft_balance_of` +- `ft_metadata` +- `ft_total_supply` +- `storage_balance_bounds` +- `storage_balance_of` +- `ft_resolve_transfer` +- `ft_transfer` +- `ft_transfer_call` +- `storage_deposit` +- `storage_unregister` +- `storage_withdraw` + +要为合约引入基本所有者方法,我们还可以派生 `Owner` 宏,它将添加以下方法: +- `own_get_owner` +- `own_get_proposed_owner` +- `own_accept_owner` +- `own_propose_owner` +- `own_renounce_owner` + +--- + +## 初始化 + +要使用自定义所有者、元数据和存储限制初始化基本 FT 合约,请实现 `new` 方法: + + + +--- + +## 转账钩子 + +如果我们想自定义代币转账的工作方式(即修改 `ft_transfer` 方法),我们需要实现一个钩子。钩子是一种**包装(在组件函数前后注入代码)**的方式: + + + +然后将其派生到合约结构体: + + + +--- + +## 铸造 + +默认情况下,FT 标准不包含铸造方法。但是,我们可以通过实现一个只有所有者才能调用的 `mint` 方法来轻松为所有者铸造代币: + + + + + +您可以根据需要修改此方法,例如,仅在合约未暂停时允许铸造(需要派生 [`Pausable`](https://github.com/near/near-sdk-contract-tools/tree/develop?tab=readme-ov-file#macro-combinations) 钩子),或仅允许具有特定角色的账户或白名单账户在自定义限制下进行铸造。 + + + +--- + +## 销毁 + +与铸造类似,销毁也不包含在 FT 标准中。但是,我们同样可以轻松实现它。 + +要从所有者账户销毁代币,我们可以添加一个只有所有者才能调用的 `burn` 方法: + + + +--- + +## 结论 + +使用 `near-sdk-contract-tools` 是一种非常简单灵活的方式,能以最少的样板代码创建 FT 合约,让我们专注于业务逻辑。 + +您可以通过从该包中派生相应的宏,进一步扩展此合约,添加暂停、基于角色的访问控制、托管模式等更多功能。 + + +**需要帮助?** + +如有疑问或需要支持,请访问 [NEAR Discord](https://near.chat) 或 [NEAR 论坛](https://forum.near.org)。 + diff --git a/zh/primitives/ft/standard.mdx b/zh/primitives/ft/standard.mdx new file mode 100644 index 00000000000..36d0df9ad97 --- /dev/null +++ b/zh/primitives/ft/standard.mdx @@ -0,0 +1,179 @@ +--- +title: 标准规范 +description: "了解 NEAR 上同质化代币(FT)的定义" +--- + +除原生 NEAR 代币外,用户还可以访问由机构和其他用户创建的大量代币,即同质化代币。 + +与原生代币不同,同质化代币(FT)**并不存储**在用户的账户中,而是存储在智能合约里。该合约负责**账本管理**,即追踪每个用户持有的代币数量,并在内部处理转账。 + +![FT](/assets/docs/primitives/ft.png) + +一个合约若要被视为 FT 合约,必须遵循 [**NEP-141**](https://github.com/near/NEPs/tree/master/neps/nep-0141.md) 和 [**NEP-148 标准**](https://github.com/near/NEPs/tree/master/neps/nep-0148.md),这两个标准定义了需要实现的**最小接口**以及预期的功能。 + +--- + +## NEP-141(同质化代币接口) + +[NEP-141](https://github.com/near/NEPs/tree/master/neps/nep-0141.md) 是 NEAR 上所有同质化代币(如稳定币、治理代币等)的蓝图。 + +它定义了合约必须实现的**一套通用规则**和**函数**,才能被视为同质化代币合约。 + + + +请注意,NEP-141 定义了同质化代币合约的**接口**和**预期行为**,但不规定内部逻辑的实现方式。 + +不同的 FT 合约可以有不同的内部实现,同时仍然遵守 NEP-141 标准。 + + + +### 接口 + +#### `ft_total_supply`(*只读*) + +返回代币的总供应量 + +```ts +ft_total_supply(): string +``` + +
+ +#### `ft_balance_of`(*只读*) + +返回给定账户的余额 + +```ts +ft_balance_of(account_id: string): string +``` + +
+ +#### `ft_transfer` + +将 `amount` 数量的代币从调用函数的账户转移到 `receiver_id`,可选地可以包含 `memo` 字段以向合约提供附加信息 + +> *要求:调用者必须附加[恰好 1 yoctoNEAR](../../smart-contracts/security/one_yocto)* + +```ts +ft_transfer(receiver_id: string, amount: string, memo: string?): void +``` + +
+ +#### `ft_transfer_call` + +该函数将 `amount` 数量的代币转移到 `receiver_id`,**并在 `receiver_id` 上调用 `ft_on_transfer(sender_id, amount, msg)` 方法**。 + +可选地,该函数可以为 FT 合约提供 `memo`,以及一个 `msg` 字段,该字段将被发送到接收合约。 + +> 📖 此函数可用于在单个交易中将代币转移到合约并触发接收方的某些操作,从而实现**将同质化代币附加到函数调用**的效果。 + +> *要求:调用者必须附加[恰好 1 yoctoNEAR](../../smart-contracts/security/one_yocto)* + +```ts +ft_transfer_call(receiver_id: string, amount: string, memo: string?, msg: string): void +``` + + + + 期望**接收**同质化代币的智能合约**必须**实现此方法。 + + 该方法**必须**返回接收者**未使用**的代币数量,以便**退还给发送者**。 + + ```ts + ft_on_transfer(sender_id: string, amount: string, msg: string): string + ``` + + ⚠️ 注意,此方法不需要由 FT 合约本身实现,而是由任何期望接收同质化代币的合约实现。 + + 请参见[使用 FT 页面](./ft#handling-deposits)中的参考实现。 + + + +
+ +#### `ft_resolve_transfer` + +此方法用作[回调](../../smart-contracts/anatomy/crosscontract#callback-function)来解析 `ft_transfer_call` 交易,必要时处理退款。 + +```js +ft_resolve_transfer(sender_id: string, receiver_id: string, amount: string): string +``` + +--- + +## NEP-145(存储管理) + +在 NEAR 上,账户需要为其在网络上使用的存储付费。随着持有某个同质化代币合约代币的用户增多,需要存储的信息也随之增加,因此合约需要预留更多存储空间。 + +[NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) 是一个定义用户注册通用接口的标准,允许 FT 合约**向用户收取存储使用费**。 + + + +虽然不是强制要求,但强烈建议 FT 合约实现 NEP-145 标准,以避免存储空间耗尽的情况。 + + + +### 接口 + +#### `storage_balance_bounds`(*只读*) +返回账户在合约中注册所需的最小和最大存储余额 + +```ts +storage_balance_bounds(): { min: string, max?: string} | null +``` + +#### `storage_balance_of`(*只读*) +返回给定账户的存储余额,若账户未注册则返回 `null` + +```ts +storage_balance_of(account_id: string): { total: string, available: string } | null +``` + +#### `storage_unregister` +从合约中删除账户的所有信息,将存储押金返还给用户。该函数只能由用户本人调用。 + +```ts +storage_unregister(force?: boolean): boolean +``` + +#### `storage_deposit` +在合约中注册账户,预留足够的存储空间以追踪用户的余额。该函数可由用户本人或**第三方**代表用户调用。 + +```ts +storage_deposit(account_id?: string, registration_only?: boolean): { total: string, available: string } +``` + +#### `storage_withdraw` +从合约中注销账户,将存储押金返还给用户。该函数只能由用户本人调用。 + +```ts +storage_withdraw(amount: string): { total: string, available: string } +``` + +--- + +## NEP-148(代币元数据) + +[NEP-148](https://github.com/near/NEPs/tree/master/neps/nep-0141.md) 是 NEP-141 标准的扩展,定义了同质化代币的**元数据**。 + +元数据提供了关于代币的**关键信息**,例如其**名称、符号和小数精度**。特别地,代币元数据中必须包含以下字段: + +- `spec`:字符串。应为 `ft-1.0.0`,表示同质化代币合约遵循当前版本的元数据规范和[同质化代币核心][FT Core]规范。 +- `name`:代币的人类可读名称。 +- `symbol`:缩写,如 wETH 或 AMPL。 +- `decimals`:前端界面用于显示代币正确有效位数的精度值。 + +元数据对钱包和其他用户界面正确显示代币非常有用,例如,若代币定义为: + +```json +{ + "spec": "ft-1.0.0", + "name": "My Awesome Token", + "symbol": "MAT", + "decimals": 4 +} +``` + +该代币 `123456` 个单位在用户界面中应显示为 `12.3456 MAT`。 diff --git a/zh/primitives/linkdrop/linkdrop.mdx b/zh/primitives/linkdrop/linkdrop.mdx new file mode 100644 index 00000000000..5374f6a5a30 --- /dev/null +++ b/zh/primitives/linkdrop/linkdrop.mdx @@ -0,0 +1,411 @@ +--- +title: 使用链接投放(Linkdrop) +description: "了解遵循 NEP-452 标准的链接投放——通过访问密钥和 Keypom 平台,使用简单的网络链接分发资产并引导用户进入 Web3 应用。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; + +想要在您的 dApp 中使用链接投放(Linkdrop)?在这里您可以找到所有入门所需的信息。 + + + +创建链接投放最简便的方式是使用我们的 LinkDrop 生成器进行交互。 + + + +--- + +## 访问密钥 + +要创建任何类型的投放,您首先需要生成密钥对。**每个投放需要创建一个密钥**。 + +- `linkdrop` 合约将存储密钥的**公开**部分。 +- 您将把密钥的**私有**部分提供给您希望接收投放的用户。 + + + + + ```js + import { KeyPair } from 'near-api-js'; + + const newKeyPair = KeyPair.fromRandom('ed25519'); + newKeyPair.public_key = newKeyPair.publicKey.toString(); + ``` + + + + + ```bash + near generate-key + + # Key pair with ed25519:33Vn9VtNEtWQPPd1f4jf5HzJ5weLcvGHU8oz7o5UnPqy public key for an account "1e5b1346bdb4fc5ccd465f6757a9082a84bcacfd396e7d80b0c726252fe8b3e8" + ``` + + + + + +

在 [Lantstool](https://app.lantstool.dev/) 上生成新密钥

+ ![lantstool](/assets/docs/tools/lantstool-near_protocol-utils-key_generator.png) + +
+
+ +--- + +## `$NEAR` 投放 + +要创建 `$NEAR` 投放,您需要请求合约创建一个投放(`create_drop`),传入您生成的密钥的公开部分,以及每次密钥使用时希望投放的数量(`deposit_per_use`)。 + +合约将创建一个投放,并**返回标识该投放的数字 ID**。 + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const DROP_AMOUNT = "10000000000000000000000"; // 0.1 NEAR + +const { callFunction } = useNearWallet(); + +await callFunction({ + contractId: KEYPOM_CONTRACT_ADDRESS, + method: "create_drop", + args: { + public_keys: state.publicKeys, + deposit_per_use: DROP_AMOUNT, + }, + deposit: "23000000000000000000000", // state.publicKeys.length * dropAmount + 3000000000000000000000 + gas: "100000000000000", +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call v2.keypom.near create_drop '{"public_keys": , "deposit_per_use": "10000000000000000000000"}' --depositYocto 23000000000000000000000 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + + +要领取投放,您需要向用户发送一个[包含私钥的链接](#building-drop-links)。 + +--- + +## NFT 投放 + +要投放现有的 NFT,您需要(1)创建投放,然后(2)**将 NFT 转入** keypom。 + +#### 1. 创建投放 + +要创建 NFT 投放,您需要调用 `create_drop` 方法,传入 `nft` 参数,这将告知链接投放合约等待 NFT 的转入。 + +合约将创建一个投放,并**返回标识该投放的数字 ID**。 + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const NFT_CONTRACT_ADDRESS = "nft.primitives.near"; +const DROP_AMOUNT = "10000000000000000000000"; + +const { callFunction, accountId } = useNearWallet(); + +await callFunction({ + contractId: KEYPOM_CONTRACT_ADDRESS, + method: "create_drop", + args: { + public_keys: state.publicKeys, + deposit_per_use: DROP_AMOUNT, + nft: { + // Who will be sending the NFTs to the Keypom contract + sender_id: accountId, + // NFT Contract Id that the tokens will come from + contract_id: NFT_CONTRACT_ADDRESS, + }, + }, + deposit: "23000000000000000000000", // state.publicKeys.length * dropAmount + 3000000000000000000000 + gas: "100000000000000", +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call v2.keypom.near create_drop '{"public_keys": , "deposit_per_use": "10000000000000000000000", "nft": {"sender_id": "bob.near", "contract_id": "nft.primitives.near"}}' --depositYocto 23000000000000000000000 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + +#### 2. 转移 NFT + +获得投放 ID 后,您现在需要将 NFT 转移到链接投放合约,并指定要添加到哪个投放。 + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const NFT_CONTRACT_ADDRESS = "nft.primitives.near"; +const NFT_TOKEN_ID = "1"; + +const { callFunction } = useNearWallet(); + +await callFunction({ + contractId: NFT_CONTRACT_ADDRESS, + method: "nft_transfer_call", + args: { + receiver_id: KEYPOM_CONTRACT_ADDRESS, + token_id: NFT_TOKEN_ID, + msg: dropId.toString() + }, + deposit: 1, + gas: "100000000000000", +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call nft.primitives.near nft_transfer_call '{"receiver_id": "v2.keypom.near", "token_id": , "msg": }' --depositYocto 1 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + + +`linkdrop` 合约将验证您正在将 NFT 转移到属于您的投放 + + + + +创建链接投放最简便的方式是使用我们的 LinkDrop 生成器进行交互。 + + + +--- + +## FT 投放 + +投放同质化代币的过程与创建 [NFT 投放](#nft-drops)非常相似。您将首先创建投放,然后用 FT 为其注资。 + +#### 1. 创建投放 + +要创建 FT 投放,您需要调用 `create_drop` 方法,传入 `ftData` 参数,这将告知链接投放合约等待一定数量的 FT 转入。 + +合约将创建一个投放,并**返回标识该投放的数字 ID**。 + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const FT_CONTRACT_ADDRESS = "ft.primitives.near"; +const DROP_AMOUNT = "10000000000000000000000"; + +const { callFunction, accountId } = useNearWallet(); + +await callFunction({ + contractId: KEYPOM_CONTRACT_ADDRESS, + method: "create_drop", + args: { + public_keys: state.publicKeys, + deposit_per_use: DROP_AMOUNT, + ftData: { + contractId: FT_CONTRACT_ADDRESS, + senderId: accountId, + // This balance per use is balance of human readable FTs per use. + amount: "1" + // Alternatively, you could use absoluteAmount, which is dependent on the decimals value of the FT + // ex. if decimals of an ft = 8, then 1 FT token would be absoluteAmount = 100000000 + }, + }, + deposit: "23000000000000000000000", // state.publicKeys.length * dropAmount + 3000000000000000000000 + gas: "100000000000000", +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call v2.keypom.near create_drop '{"public_keys": , "deposit_per_use": "10000000000000000000000", "ftData": {"contractId": "ft.primitives.near","senderId": "bob.near", "amount": "1"}}}' --depositYocto 23000000000000000000000 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + +#### 2. 转移 FT + +获得投放 ID 后,您现在需要将同质化代币转移到链接投放合约。 + + +要将 FT 转移到某个账户,您需要先在 FT 合约上[注册](/primitives/ft/ft#registering-a-user)接收账户(例如 keypom 合约)。 + + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const FT_CONTRACT_ADDRESS = "ft.primitives.near"; + +const { callFunction } = useNearWallet(); + +await callFunction({ + contractId: FT_CONTRACT_ADDRESS, + method: "ft_transfer", + args: { + receiver_id: KEYPOM_CONTRACT_ADDRESS, + amount: "1" + }, + deposit: "1", + gas: "100000000000000" +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call ft.primitives.near ft_transfer '{"receiver_id": "v2.keypom.near", "amount": "1"}' --depositYocto 1 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + + + +创建链接投放最简便的方式是使用我们的 LinkDrop 生成器进行交互。 + + + +--- + +## 函数调用投放 +链接投放合约允许创建`函数调用`投放。这类投放将在用户领取时,在合约上执行一个或多个方法。 + + +函数调用投放可以被视为其他投放类型的抽象版本:您可以创建一个用于铸造 NFT、在 DAO 中注册用户或支付服务费用的投放。 + + + + + +```js +import { useNearWallet } from "near-connect-hooks"; + +const KEYPOM_CONTRACT_ADDRESS = "v2.keypom.near"; +const NFT_CONTRACT_ADDRESS = "nft.primitives.near"; +const NFT_TOKEN_ID = "1"; +const DROP_AMOUNT = "10000000000000000000000"; + +const { callFunction } = useNearWallet(); + +await callFunction({ + contractId: KEYPOM_CONTRACT_ADDRESS, + method: "create_drop", + args: { + public_keys: state.publicKeys, + deposit_per_use: DROP_AMOUNT, + fcData: { + // 2D array of function calls. In this case, there is 1 function call to make for a key use + // By default, if only one array of methods is present, this array of function calls will be used for all key uses + methods: [ + // Array of functions for Key use 1. + [{ + receiverId: NFT_CONTRACT_ADDRESS, + methodName: "nft_mint", + args: JSON.stringify({ + // Change this token_id if it already exists -> check explorer transaction + token_id: NFT_TOKEN_ID, + metadata: { + title: "My NFT drop", + description: "", + media: "", + } + }), + accountIdField: "receiver_id", + // Attached deposit for when the receiver makes this function call + attachedDeposit: "10000000000000000000000" + }] + ] + } + }, + deposit: "23000000000000000000000", // state.publicKeys.length * dropAmount + 3000000000000000000000 + gas: "100000000000000", +}); +``` + +了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + +```bash +near call v2.keypom.near create_drop '{"public_keys": , "deposit_per_use": "10000000000000000000000", "fcData": {"methods": [[{"receiverId": "nft.primitives.near","methodName": "nft_mint","args": {"token_id": "1", "metadata": {"title": "My NFT drop","description": "","media": ""}, "accountIdField": "receiver_id", "attachedDeposit": "10000000000000000000000"}]]}}' --depositYocto 23000000000000000000000 --gas 100000000000000 --useAccount bob.near +``` + + + + + + + + +--- + +## 构建投放链接 + +要创建链接投放链接,只需将私钥追加到 `claim` 页面即可: + +```text +http://localhost:3001/claim/linkdrop?id=ed25519:5Ly2arHZ4niWBVyEuzpN3J8QQX1BrYfWsirGqdYR3JfqUDhJ3SRK7JeQfVsh4UL8Wn6uf8RzWE4RPHymkePywVVd +``` diff --git a/zh/primitives/linkdrop/standard.mdx b/zh/primitives/linkdrop/standard.mdx new file mode 100644 index 00000000000..b18cce3a878 --- /dev/null +++ b/zh/primitives/linkdrop/standard.mdx @@ -0,0 +1,80 @@ +--- +title: 标准规范 +description: "了解 NEAR 上链接投放(Linkdrop)的定义" +--- + +链接投放允许用户通过简单的网络链接分发资产并引导人们进入 Web3 应用。 + +![Linkdrop](/assets/docs/primitives/linkdrop.png) + +其工作原理是存储资产并将[访问密钥(AccessKeys)](../../protocol/accounts-contracts/access-keys)与其关联。`AccessKeys` 随后以网络链接的形式分发给用户。这些链接将用户引导至一个网站,该网站自动使用密钥调用 `linkdrop` 合约中的 `claim` 方法。 + +一个合约若要被视为链接投放合约,必须遵循 [**NEP-452 标准**](https://github.com/near/NEPs/blob/master/neps/nep-0452.md)。**NEP-452** 说明了需要实现的**最小接口**以及预期的功能。 + +--- + +## NEP-452(链接投放标准) + +[NEP-452](https://github.com/near/NEPs/blob/master/neps/nep-0452.md) 是 NEAR 上所有链接投放合约的蓝图。它定义了合约必须实现的**一套通用规则**和**函数**,才能被视为链接投放合约。 + + + +请注意,NEP-452 定义了链接投放合约的**接口**和**预期行为**,但不规定内部逻辑的实现方式。 + +不同的链接投放合约可以有不同的内部实现,同时仍然遵守 NEP-452 标准。 + + + +### 接口 + +#### `get_key_balance`(*只读*) + +允许查询分配给特定链接投放密钥的 NEAR 代币数量 + +```ts +get_key_balance(key: string): string; +``` + +
+ +#### `get_key_information`(*只读*) + +返回特定链接投放密钥的信息,包括分配给它的 NEAR 代币数量、接收者 ID 以及密钥是否已被领取 + +```ts +interface NFTData { + contract_id: string; + token_id: string; +} + +interface FTData { + contract_id: string; + amount: string; +} + +get_key_information(key: string): { required_gas: string, yoctonear: string, nft_list: NFTData[], ft_list: FTData[] }; +``` + +
+ +#### `claim` + +允许用户领取与特定密钥关联的资产。该函数将分配给提供的 `account_id` 的 NEAR 代币、NFT 和 FT 进行转移,调用此方法前该账户**必须**已存在。 + +> ⚠️ 用户需要使用**收到的链接投放密钥**对交易进行签名来调用此方法。 + +```ts +claim(account_id: string): boolean; +``` + +
+ +#### `create_account_and_claim` + +允许用户**创建新账户**并**领取与特定密钥关联的资产**。该函数使用提供的 `new_account_id` 创建新账户,并将分配给它的 NEAR 代币、NFT 和 FT 进行转移。 + +> ⚠️ 用户需要使用**收到的链接投放密钥**对交易进行签名来调用此方法。 + +```ts +create_account_and_claim(new_account_id: string, new_public_key: string): Promise; +``` diff --git a/zh/primitives/liquid-staking/deploy-your-own-contract.mdx b/zh/primitives/liquid-staking/deploy-your-own-contract.mdx new file mode 100644 index 00000000000..551146c97c0 --- /dev/null +++ b/zh/primitives/liquid-staking/deploy-your-own-contract.mdx @@ -0,0 +1,152 @@ +--- +title: 部署您自己的合约 +description: "了解如何在 NEAR 上部署您自己的流动性质押合约" +--- + +从本质上讲,流动性质押合约发行一种遵循[同质化代币(NEP-141)](https://github.com/near/NEPs/blob/master/neps/nep-0141.md)标准的代币。 + +当您质押 `NEAR` 时,您不只是将其锁定——您还会收到一种流动性代币(例如 `rNEAR`),其行为与网络上任何其他同质化代币完全相同。您可以转移它、兑换它,或在 DeFi 协议中使用它,就像使用任何其他代币一样。 + +由于它完全兼容 NEP-141,该代币可与钱包、DEX 和其他智能合约无缝集成。这种兼容性正是支持提前退出机制的关键——您可以通过在交易所兑换流动性代币立即换回 `NEAR`。兑换汇率由市场供需决定,通常略低于链上汇率(大约低 0.5%),这代表了获取即时流动性的小额成本。 + +在幕后,流动性代币的汇率根据验证节点赚取的奖励持续调整。 +计算公式为 `exchange_rate = total_staked_near / total_liquid_token_supply`,随着每个时期积累质押奖励,质押的 NEAR 总量增加,而代币供应量只在用户存入或提取时才会变化。这就是为什么即使您持有相同的余额,汇率和代币价值也会随时间稳步增加。 + +--- + +## 存储押金(NEP-145) + +由于流动性质押合约发行 [NEP-141 代币](https://github.com/near/NEPs/blob/master/neps/nep-0141.md),它也实现了[存储管理(NEP-145)](https://github.com/near/NEPs/blob/master/neps/nep-0145.md)标准。 + +每次您首次与合约交互时(例如存入 `NEAR` 并获得流动性代币时),您需要附加一笔小额存储押金。此押金用于支付您的账户信息在合约存储中占用的空间。好处是这笔资金并未被消耗,只是在您使用合约期间保持预留状态。如果您停止使用它,可以通过注销账户取回押金。 + +简而言之,这是一种确保合约不用自己的余额为用户数据付费的方式,同时让所有人在离开时都能安全收回押金。 + +--- + +## 部署流动性质押合约 + +流动性质押合约可直接从[代码仓库](https://github.com/ref-finance/rnear-contract)部署,该仓库包含由 Rhea.finance 团队维护的实现。此合约遵循上述相同原则,并已针对生产环境进行了优化。 + +部署前,请确保目标账户余额中至少有 20.5 `NEAR`。此金额涵盖了初始存储成本、初始化所需的最低质押流动性以及小额 gas 缓冲。 + +```bash +# 克隆代码仓库 +git clone https://github.com/ref-finance/rnear-contract.git +cd rnear-contract/contracts/lst + +## 创建并为流动性质押账户注资 +near create-account --useAccount --initialBalance + +# 构建并部署流动性质押合约 +cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{ + "metadata": { + "decimals": 24, + "name": "Test Liquid Near Staking Token", + "spec": "ft-1.0.0", + "symbol": "tNEAR" + }, + "owner_id": "owner.near" +}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' +``` + +### 设置验证节点白名单账户 + +在添加验证节点之前,您首先需要设置验证节点白名单账户。该账户充当合约允许使用哪些验证节点的单一信息来源。 + +验证节点白名单账户必须实现以下接口,允许合约验证给定的验证节点是否已获批准。 + +```rust +#[ext_contract(ext_whitelist)] +pub trait ExtWhitelist { + fn is_whitelisted(&self, staking_pool_account_id: AccountId) -> bool; +} +``` + +要更新验证节点白名单账户,请运行以下命令: + +```bash +near contract call-function as-transaction set_whitelist_contract_id json-args '{ + "account_id": "whitelist_source.near" +}' prepaid-gas '30.0 Tgas' attached-deposit '1 yoctoNEAR' +``` + +### 添加验证节点 + +白名单账户配置完成后,您可以注册合约将委托代币的验证节点列表及其权重。每个权重定义了相对于其他验证节点,有多少比例的总质押量应流向该特定验证节点。 + +要添加验证节点,请运行以下命令: + +```bash +near contract call-function as-transaction add_validators json-args '{ + "validator_ids": ["validator1.pool.near", "validator2.pool.near", "validator3.pool.near"], + "weights": [40, 35, 25] +}' prepaid-gas '30.0 Tgas' attached-deposit '1 yoctoNEAR' +``` + +--- + +## 代币 + +在本节中,我们将介绍几种最常用的方法。要探索所有可用方法,请参阅 [NEP-141](https://github.com/near/NEPs/blob/master/neps/nep-0141.md) 的完整定义。 + +### 存储押金 + +在持有或接收任何流动性代币之前,您的账户需要在合约存储中注册。您只需执行一次,且该金额可在注销账户时全额退还。 + +要注册账户,请运行以下命令: + +```bash +near contract call-function as-transaction storage_deposit json-args '{ + "account_id":"", + "registration_only": true +}' prepaid-gas '30.0 Tgas' attached-deposit '0.00125 NEAR' +``` + +### 查询余额 + +要查询您持有多少流动性代币(例如 `tNEAR`),请运行以下命令: + +```bash +near contract call-function as-read-only ft_balance_of json-args '{"account_id": ""}' +``` + +### 查询总供应量 + +要查询流动性代币(例如 `tNEAR`)的总供应量,请运行以下命令: + +```bash +near contract call-function as-read-only ft_total_supply json-args '{}' +``` + +### 转移代币 + +要直接将代币转移到另一个账户,请运行以下命令: + +```bash +near contract call-function as-transaction ft_transfer json-args '{ + "amount": "10000000000000000000000000", + "receiver_id":"" +}' prepaid-gas '30.0 Tgas' attached-deposit '1 yoctoNEAR' +``` + +### 查询代币价格 + +虽然大多数方法遵循 NEP-141 标准,但流动性质押合约还提供了一个名为 `ft_price` 的附加辅助方法。此方法不属于标准本身——它专门为该合约实现,以便更轻松地查看流动性代币与 `NEAR` 之间的当前汇率,因为代币价格由以下公式决定:`exchange_rate = total_staked_near / total_liquid_token_supply` + +要查询当前价格,请运行以下命令: + +```bash +near contract call-function as-read-only ft_price json-args '{}' +``` + +返回的数字表示一个流动性代币单位当前价值多少 `yoctoNEAR`。 + +--- + +## 其他资源 + +更多信息请浏览以下官方资源: + +- [流动性质押合约代码仓库](https://github.com/ref-finance/rnear-contract) +- [NEAR CLI](https://github.com/near/near-cli-rs) diff --git a/zh/primitives/liquid-staking/liquid-staking.mdx b/zh/primitives/liquid-staking/liquid-staking.mdx new file mode 100644 index 00000000000..04568956545 --- /dev/null +++ b/zh/primitives/liquid-staking/liquid-staking.mdx @@ -0,0 +1,70 @@ +--- +title: 使用流动性质押 +description: "了解 NEAR 上的流动性质押——一种发行代表质押 NEAR 的同质化代币的智能合约,支持即时流动性和验证节点多样化。" +--- + +流动性质押是 NEAR 上常规质押的现代替代方案,解决了延迟提款的问题。在常规质押中,您无法立即从质押池中提取资金——首先需要请求解除质押,然后等待 4 个时期(大约 24-28 小时),资金才能可供提款。 + +流动性质押合约通过提供一种灵活的方式来解决这一限制,使您的资产保持流动性。 + + + +您是否正在寻找将 NEAR 代币委托给**流动性质押提供商**的方式?请查看 [Metapool](https://www.metapool.app/es/stake?token=near)、[Rhea Finance](https://app.rhea.finance/stake)。 + + + +--- + +## 质押池 + +流动性质押合约实现了质押池接口。从开发者的角度来看,与流动性质押合约的交互方式几乎与常规质押池相同。 + +您可以调用熟悉的方法,如 `deposit_and_stake`、`unstake` 和 `withdraw`,它们遵循相同的生命周期——唯一的区别是您的质押由流动性代币表示,而不是锁定在单个池子中。 + +在幕后,合约将代币委托给多个验证节点,通常从网络上表现最佳的验证节点中选择。这些验证节点被积极维护并受到密切监控,因此发生停机或性能问题的可能性极低。由于您的质押分散在许多验证节点中,因此单个验证节点下线导致奖励损失的风险极低。 + +值得注意的是,由于您的质押分散在多个验证节点中,平均收益率通常会略低于直接委托给单个高性能验证节点。但作为回报,您可以获得更好的验证节点故障防护能力,以及随时将流动性代币兑换回 `NEAR` 而无需等待解除质押延迟的能力。这是最大收益与最大灵活性之间的平衡权衡。 + +--- + +## 使用流动性质押 + +以下是您在质押流程中最常用的命令,以及 NEAR 上主要流动性质押提供商的合约地址。 + +| 提供商 | 测试网账户 | 主网账户 | +|--------------|---------------------------|------------------------| +| Metapool | `meta-v2.pool.testnet` | `meta-pool.near` | +| Rhea Finance | | `lst.rhealab.near` | +| Linear | `linear-protocol.testnet` | `linear-protocol.near` | + +### 存入并质押代币 + +要质押您的 `NEAR` 并获得流动性代币,请运行以下命令: + +```bash +near contract call-function as-transaction deposit_and_stake json-args '{}' prepaid-gas '30.0 Tgas' attached-deposit '10 NEAR' +``` + +从那时起,您的代币开始通过底层验证节点产生收益。 + +### 解除质押 + +当您准备好后,只需使用以下命令请求解除质押。您需要等待标准的 4 个时期延迟(24-28 小时),资金才能可供提款。 + +```bash +near contract call-function as-transaction unstake_all json-args '{}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + + +如果您立即需要 `NEAR`,无需等待 4 个时期——您可以直接在 Rhea.finance 等 DEX 上将流动性代币兑换为 `NEAR`。 + + +### 提款 + +4 个时期过后,运行以下命令将 `NEAR` 代币提回。 + +```bash +near contract call-function as-transaction withdraw_all json-args '{}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + +此时,您的流动性代币将被销毁,您将按当前汇率收到等值的 `NEAR`。 diff --git a/zh/primitives/lockup/introduction.mdx b/zh/primitives/lockup/introduction.mdx new file mode 100644 index 00000000000..3388a7414d7 --- /dev/null +++ b/zh/primitives/lockup/introduction.mdx @@ -0,0 +1,177 @@ +--- +title: 简介 +description: "了解 NEAR 上的锁定合约——持有代币并随时间逐步释放的智能合约,支持锁定期、归属计划、质押以及基金会终止。" +--- + +锁定合约充当托管账户,持有代币并随时间逐步释放。它们被广泛用于管理员工薪酬、投资者归属计划或长期代币分配。 + +锁定合约通过结合两个关键机制来限制代币流动性,直到满足预定义条件: + +- **锁定期(Lockup)** – 代币在特定日期到达之前保持锁定状态。 +- **归属期(Vesting)** – 代币可供用户使用,但可能会逐步释放。 + +通过锁定期和归属期,项目可以强制执行可预测的代币释放计划,使激励措施保持一致并提高透明度。 + +--- + +## 锁定计划 + +锁定期定义了代币如何随时间线性释放,由以下参数描述: + +- `lockup_timestamp` – 解锁开始的时间。 +- `release_duration` – 解锁持续的时间。 + 到期时,所有代币均可使用。 +- `finish_timestamp = lockup_timestamp + release_duration` + +```mermaid +--- +config: + theme: "neutral" + xyChart: + width: 800 + height: 300 + showDataLabel: true + themeVariables: + xyChart: + plotColorPalette: '#000000, #FF0000' +--- +xychart + title "Linear Schedule" + x-axis [before, lockup_timestamp, finish_timestamp, after] + y-axis "Tokens Unlocked (in %)" 0 --> 100 + line [0, 0, 100, 100] +``` + +--- + +## 归属计划 + +归属期增加了额外条件,通常用于就业或投资协议: + +- `start_timestamp` – 归属开始的时间(例如入职日期)。 +- `cliff_timestamp` – 首次代币归属的时间(例如 1 年)。 +- `end_timestamp` – 归属完成的时间。 + +示例: +**4 年归属期**配合 **1 年悬崖期**意味着: + +- 第 1 年:没有代币归属。 +- 满 1 年时:一次性归属 25%。 +- 剩余 75% 在接下来的 3 年内线性归属。 + +```mermaid +--- +config: + theme: "neutral" + xyChart: + width: 800 + height: 300 + showDataLabel: true + themeVariables: + xyChart: + plotColorPalette: '#000000' +--- +xychart + title "Vested Schedule" + x-axis [before, start_timestamp, cliff_timestamp, cliff_timestamp, end_timestamp, after] + y-axis "Tokens Unlocked (in %)" 0 --> 100 + line [0, 0, 0, 25, 100, 100] +``` + +--- + +## 组合计划 + +锁定期和归属期可以组合使用。代币只有在两个条件都满足时才具有流动性: + +`liquidity_timestamp = max(lockup_timestamp, cliff_timestamp)` + +根据哪个事件先发生,代币释放的结果有所不同。 + +### 情景 A:锁定期早于悬崖期 + +在这种情况下,锁定时间戳早于悬崖时间戳。虽然锁定计划通常允许代币开始解锁,但归属悬崖期尚未到来。因此,在悬崖期到来之前,没有代币具有流动性。 + +这引入了三个关键时间戳: + +- `lockup_timestamp` – 早于归属悬崖期 +- `cliff_timestamp` – 较晚到来,因此归属期延迟了流动性 +- `end_timestamp` – 归属完全完成的时间 + +```mermaid +--- +config: + theme: "neutral" + xyChart: + width: 800 + height: 300 + showDataLabel: true + themeVariables: + xyChart: + plotColorPalette: '#000000' +--- +xychart + title "Combined Schedule: #A" + x-axis [before, lockup_timestamp, cliff_timestamp, cliff_timestamp, end_timestamp, after] + y-axis "Tokens Unlocked (in %)" 0 --> 100 + line [0, 0, 0, 25, 100, 100] +``` + +### 情景 B:悬崖期早于锁定期 + +在这种情况下,到达悬崖期时,25% 的代币被视为已归属。但由于锁定期尚未结束,流动性仍然受阻。 + +这引入了三个关键时间戳: + +- `cliff_timestamp` – 早于锁定期 +- `lockup_timestamp` – 较晚到来,延迟了流动性解锁 +- `end_timestamp` – 归属完全完成的时间 + +```mermaid +--- +config: + theme: "neutral" + xyChart: + width: 800 + height: 300 + showDataLabel: true + themeVariables: + xyChart: + plotColorPalette: '#000000' +--- +xychart + title "Combined Schedule: #B" + x-axis [before,cliff_timestamp, lockup_timestamp, lockup_timestamp, end_timestamp, after] + y-axis "Tokens Unlocked (in %)" 0 --> 100 + line [0, 0, 0, 25, 100, 100] +``` + +--- + +## 基金会终止 + +当初始化时指定了 `foundation_account_id` 时,该账户被授予在自然完成之前终止归属的权利。终止的效果取决于终止发生在归属悬崖期之前还是之后。 + +- 如果终止发生在悬崖期日期之前,则没有代币被视为已归属,整个分配将退还给基金会。 + +- 如果终止发生在悬崖期之后,则到目前为止已归属的部分留给所有者,而所有剩余未归属代币将返还给基金会。 + +这确保了所有者不会收到超过已归属部分的代币,同时给予基金会一种在协议提前终止时收回锁定部分的机制。 + +您将在继续阅读时发现实际示例。 + +--- + +## 使用锁定代币质押 + +锁定合约允许所有者将代币委托给白名单质押池,让所有者在基础代币保持锁定状态的同时赚取额外奖励。 + +其工作流程如下: + +- 所有者从白名单中选择一个验证节点,并通过锁定合约质押代币。 + +- 质押金额本身仍受锁定期和归属计划的约束保持锁定。 + +- 验证节点随时间产生质押奖励。 + +一个关键区别是质押奖励是立即具有流动性的。它们不受原始锁定期或归属条件的约束。例如,如果锁定并质押了 1000 NEAR 一个月,并赚取了 10 NEAR 作为奖励,当所有者决定解除质押时,原始的 1000 NEAR 遵循正常的锁定期和归属限制,而任何已赚取的奖励可以直接转移到您的账户。 diff --git a/zh/primitives/lockup/lockup.mdx b/zh/primitives/lockup/lockup.mdx new file mode 100644 index 00000000000..57e69311c99 --- /dev/null +++ b/zh/primitives/lockup/lockup.mdx @@ -0,0 +1,221 @@ +--- +title: 锁定合约 +description: "了解 NEAR 上的锁定合约——持有代币并基于时间释放机制的智能合约,支持锁定期、归属计划、质押以及基金会终止。" +--- + +希望管理代币归属和锁定计划?NEAR 的锁定合约为基于时间的代币托管和释放机制提供了强大的解决方案。 + +--- + +## 部署锁定合约 + +部署新锁定合约最简便的方式是使用 NEAR 上已发布的现有[全局合约](../../smart-contracts/global-contracts)。 + +重要的是要理解,**锁定的代币总量由部署时账户的余额决定**。 +部署合约时账户中的所有代币都将按照您定义的计划锁定。 + +这意味着您必须始终**先创建并为锁定账户注资**,存入预期数量的代币,然后再部署合约。 + +```bash +# 创建并为锁定账户注资 +near create-account --useAccount --initialBalance + +# 部署锁定合约 +near contract deploy use-global-hash CAvU5MYQ4xk1SjFvbnQDQUj6qehuW5YhU3FXA6GMddCx with-init-call new json-args '{ + "owner_account_id": "employee.near", + "lockup_duration": "0", + "lockup_timestamp": "1535760000000000000", + "release_duration": "126230400000000000", + "transfers_information": { + "TransfersEnabled": { + "transfers_timestamp": "1602614338293769340" + } + }, + "vesting_schedule": { + "VestingSchedule": { + "start_timestamp": "1535760000000000000", + "cliff_timestamp": "1567296000000000000", + "end_timestamp": "1661990400000000000" + } + }, + "staking_pool_whitelist_account_id": "staking-pool-whitelist", + "foundation_account_id": "foundation.near" +}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + + + +- 仅使用锁定期:省略 `vesting_schedule` 属性。 + +- 仅使用归属期:将 `lockup_timestamp` 和 `release_duration` 设置为 0,仅提供 `vesting_schedule`。 + + +### 查询余额 + +要查看所有者的余额,请运行以下命令。返回的金额包括合约中的代币总量以及可供提取的质押奖励。 + +```bash +near contract call-function as-read-only get_owners_balance json-args '{}' +``` + +要查看流动余额,请运行以下命令。这将返回可供提取的金额,包括已解锁的代币和质押奖励。 + +```bash +near contract call-function as-read-only get_liquid_owners_balance json-args '{}' +``` + +### 提取代币 + +要将已解锁的代币转移到您的账户,请运行以下命令。 + +```bash +near contract call-function as-transaction transfer json-args '{ + "amount": "10000000000000000000000000", + "receiver_id":"owner.near" +}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + + +请求提取超过流动余额的金额将不会成功。 + + +### 完全解锁后提取代币 + +一旦所有代币完全归属并解锁,您可以通过添加完全访问密钥将锁定账户转换为普通 NEAR 账户。这允许您直接管理该账户,并利用高级技术来提取剩余代币,包括完全删除该账户并将您的账户指定为受益人。 + +生成一个新的密钥对,并在归属/锁定计划结束后运行以下命令,将其作为完全访问密钥添加到账户中。 + +```bash +near contract call-function as-transaction add_full_access_key json-args '{ + "new_public_key": "ed25519:8W9CiyPPehz2GRW8AYho9nx1z1GLdeZQCyn2wqYgJjiG" +}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + + +请记住,切勿使用上述命令中的密钥。请生成您自己的密钥对。 + + +添加密钥后,您可以使用以下命令删除该账户并将剩余资金转移到您的账户(将 `owner.near` 替换为您的地址)。 + +```bash +near account delete-account beneficiary owner.near +``` + + +在尝试删除账户之前,请确保所有质押代币已完全提取。如果有任何代币仍委托给质押池,账户删除后这些代币将会丢失。 + + +--- + +## 质押 + +锁定合约允许所有者将代币委托给白名单质押池,让所有者在基础代币保持锁定状态的同时赚取额外奖励。 + +让我们按照您通常执行命令的顺序,逐步介绍每个步骤。 + +### 选择质押池 + +从初始化时配置的白名单中选择验证节点,然后在锁定合约上设置它。 + +```bash +near contract call-function as-transaction select_staking_pool json-args '{ + "staking_pool_account_id": "pool.testnet" +}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + +### 存入并质押代币 + +质押锁定合约上可用的部分代币。金额必须以 yoctoNEAR 传递(1 NEAR = 10^24 yoctoNEAR)。 + +```bash +near contract call-function as-transaction deposit_and_stake json-args '{ + "amount": "1000000000000000000000000000" +}' prepaid-gas '125.0 Tgas' attached-deposit '0 NEAR' +``` + +### 刷新质押池余额 + +来自质押池的奖励不会自动反映在合约中。此命令将合约的内部余额与质押池同步。 + +```bash +near contract call-function as-transaction refresh_staking_pool_balance json-args '{}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' +``` + +完成后,请注意[流动余额](#checking-balance)已发生变化。 + +### 解除质押 + +当您准备好后,只需使用以下命令请求解除质押。您需要等待标准的 4 个时期延迟,资金才能从池子中提取。 + +```bash +near contract call unstake_all json-args '{}' prepaid-gas '125.0 Tgas' attached-deposit '0 NEAR' +``` + +### 提款 + +4 个时期过后,运行以下命令将资金从质押池移回合约,包括已赚取的奖励。 + +```bash +near contract call withdraw_all_from_staking_pool json-args '{}' prepaid-gas '175.0 Tgas' attached-deposit '0 NEAR' +``` + +完成后,已赚取的奖励成为流动余额的一部分,可以[立即提取到您的账户](#withdraw-tokens)。 + +--- + +## 终止(适用于基金会) + +如果在初始化时指定了 `foundation_account_id`,可以提前终止锁定期。在悬崖期之前,整个分配将退还给基金会。在悬崖期之后,只有未归属部分退还,而已归属部分仍留给所有者。 + +终止期间,某些所有者操作将暂时暂停,直到流程完成。 + +### 发起终止 + +启动终止流程。发起后,合约可能会限制所有者的操作,直到终止完成。 + +```bash +near contract call-function as-transaction terminate_vesting json-args '{}' prepaid-gas '25.0 Tgas' attached-deposit '0 NEAR' +``` + +### 查询状态 + +状态告知您下一步可以安全执行的操作,并防止调用失败。 + +例如,赤字状态表示质押池中还剩有一些代币,需要您采取[额外操作](#resolve-deficit-if-staked)。 + +一旦合约报告"准备提取"状态,基金会即可继续提取。 + +```bash +near contract call-function as-read-only get_termination_status json-args '{}' +``` + +### 解决赤字(如已质押) + +如果有些代币已质押,而合约显示赤字(没有足够的流动余额来退还未归属部分),请准备资金。 + +```bash +near contract call-function as-transaction termination_prepare_to_withdraw json-args '{}' prepaid-gas '175.0 Tgas' attached-deposit '0 NEAR' +``` + + +您需要调用两次——第一次开始解除质押,第二次在 4 个时期延迟后提取资金。 + + +### 提取未归属部分 + +状态就绪后,将未归属部分提取到基金会账户。 + +```bash +near contract call-function as-transaction termination_withdraw json-args '{ + "receiver_id": "foundation.near" +}' prepaid-gas '75.0 Tgas' attached-deposit '0 NEAR' +``` + +--- + +## 其他资源 + +更多信息请浏览以下官方资源: + +- [锁定合约代码仓库](https://github.com/near/core-contracts/tree/master/lockup) +- [NEAR CLI](https://github.com/near/near-cli-rs) diff --git a/zh/primitives/nft/nft.mdx b/zh/primitives/nft/nft.mdx new file mode 100644 index 00000000000..fd2b3fc122d --- /dev/null +++ b/zh/primitives/nft/nft.mdx @@ -0,0 +1,534 @@ +--- +title: 使用非同质化代币(NFT) +description: "了解遵循 NEP-171 和 NEP-177 标准的 NEAR 非同质化代币(NFT)——铸造、转移、查询和交易独特数字资产,附带完整示例。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; + +想要在您的 dApp 中使用非同质化代币(NFT)?在这里您可以找到所有入门所需的信息,包括创建代币、注册用户、转移代币以及将其集成到智能合约中。 + +--- + +## 部署 NFT 合约 + +如果您想部署自己的 NFT 合约,可以使用我们的[参考实现](https://github.com/near-examples/NFT)创建一个。 + + + + + ```bash + near deploy --wasmFile contract.wasm --initFunction new + ``` + + + + + + + +### 全局合约 + +您可以使用我们的全局 NFT 合约部署新的非同质化代币——这是一个预先部署的[标准 NFT 合约](https://github.com/near-examples/NFT),可供重复使用。[全局合约](../../smart-contracts/global-contracts)只需部署一次,任何账户均可复用,无需承担高额的存储成本。 + + + + + ```bash + near contract deploy use-global-account-id nft.globals.primitives.testnet \ + with-init-call new \ + json-args '{"owner_id": "", "metadata": {"spec": "nft-1.0.0", "name": "MY_NFT", "symbol": "NFT2000", "icon": "data:image/svg+xml,%3Csvg xmlns='\''http://www.w3.org/2000/svg'\'' viewBox='\''0 0 288 288'\''%3E%3Cg id='\''l'\'' data-name='\''l'\''%3E%3Cpath d='\''M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'\''/%3E%3C/g%3E%3C/svg%3E"}}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain \ + send + ``` + + + + + ```bash + near contract deploy use-global-hash ivu1e9obVRnMJLSvVPRgtYefUYUS1L3f5eYHjS86zL9 \ + with-init-call new \ + json-args '{"owner_id": "", "metadata": {"spec": "nft-1.0.0", "name": "MY_NFT", "symbol": "NFT2000", "icon": "data:image/svg+xml,%3Csvg xmlns='\''http://www.w3.org/2000/svg'\'' viewBox='\''0 0 288 288'\''%3E%3Cg id='\''l'\'' data-name='\''l'\''%3E%3Cpath d='\''M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'\''/%3E%3C/g%3E%3C/svg%3E"}}' \ + prepaid-gas '100.0 Tgas' \ + attached-deposit '0 NEAR' \ + network-config testnet \ + sign-with-keychain \ + send + ``` + + + + + +通过**哈希**部署会创建一个永不更改的不可变合约。通过**账户 ID** 部署则创建一个可更新的合约,当所引用账户的合约更新时,该合约也会随之更新。请根据您是否希望 FT 合约可更新或永久不变来做出选择。 + + +--- + +## 铸造 NFT +要创建一个新的 NFT(即铸造),您需要调用 `nft_mint` 方法,传入定义该 NFT 的元数据作为参数。 + + +使用 [NEAR NFT 铸造工具](https://near.org/near/widget/ComponentDetailsPage?src=near/widget/NFTMint)直接从浏览器铸造您自己的 NFT。 + + + + +以下是如何通过您的应用程序直接与工厂合约交互: + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const CONTRACT_ADDRESS = 'nft.primitives.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: CONTRACT_ADDRESS, + method: 'nft_mint', + args: { + token_id: '1', + receiver_id: 'bob.near', + token_metadata: { + title: 'NFT Primitive Token', + description: 'Awesome NFT Primitive Token', + media: 'string', // URL to associated media, preferably to decentralized, content-addressed storage + }, + }, + deposit: 10000000000000000000000, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call nft.primitives.near nft_mint '{"token_id": "1", "receiver_id": "bob.near", "token_metadata": {"title": "NFT Primitive Token", "description": "Awesome NFT Primitive Token", "media": "string"}}' --depositYocto 10000000000000000000000, --useAccount bob.near + ``` + + + + + + + ```rust + // Validator interface, for cross-contract calls + #[ext_contract(ext_nft_contract)] + trait ExternalNftContract { + fn nft_mint(&self, token_series_id: String, receiver_id: AccountId) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + #[payable] + pub fn nft_mint(&mut self, token_series_id: String, receiver_id: AccountId) -> Promise { + let promise = ext_nft_contract::ext(self.nft_contract.clone()) + .with_static_gas(Gas(30*TGAS)) + .with_attached_deposit(env::attached_deposit()) + .nft_mint(token_series_id, receiver_id); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(Gas(30*TGAS)) + .nft_mint_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn nft_mint_callback(&self, #[callback_result] call_result: Result) -> Option { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting NFT contract"); + return None; + } + + // Return the token data + let token_id: TokenId = call_result.unwrap(); + return Some(token_id); + } + } + ``` + + + + + +`TokenMetadata` 参数的完整列表请参见[元数据标准](https://github.com/near/NEPs/tree/master/neps/nep-0177.md)。 + + + + + +gas 和存款的值可能因您调用的 NFT 合约而异。 + + + + + +
+ +### 铸造合集 + +人们通常希望创建多份相同的 NFT 副本(这称为合集)。在这种情况下,您实际上需要铸造 100 个具有相同元数据(但不同 `token-id`)的不同 NFT。 + + + +请注意,[在 Mintbase 上铸造](#minting-a-nft)允许您传递 `num_to_mint` 参数。 + + + +
+ +### 版税 +您可能已经注意到其中一个参数是一个名为版税的结构。版税允许您创建一个用户列表,当代币在市场上出售时,这些用户应该获得报酬。例如,如果 `anna` 有 `5%` 的版税,每次 NFT 出售时,`anna` 应该获得售价的 5%。 + +--- + +## 查询 NFT 数据 +您可以通过调用 `nft_token` 查询 NFT 的信息和元数据。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const CONTRACT_ADDRESS = 'nft.primitives.near'; + + const { viewFunction } = useNearWallet(); + + const response = await viewFunction({ + contractId: CONTRACT_ADDRESS, + method: 'nft_token', + args: { + token_id: '1', + }, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + ```json + { + "token_id": "1", + "owner_id": "bob.near", + "metadata": { + "title": "string", // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + "description": "string", // free-form description + "media": "string", // URL to associated media, preferably to decentralized, content-addressed storage + "media_hash": "string", // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + "copies": 1, // number of copies of this set of metadata in existence when token was minted. + "issued_at": 1642053411068358156, // When token was issued or minted, Unix epoch in milliseconds + "expires_at": 1642053411168358156, // When token expires, Unix epoch in milliseconds + "starts_at": 1642053411068358156, // When token starts being valid, Unix epoch in milliseconds + "updated_at": 1642053411068358156, // When token was last updated, Unix epoch in milliseconds + "extra": "string", // anything extra the NFT wants to store on-chain. Can be stringified JSON. + "reference": "string", // URL to an off-chain JSON file with more info. + "reference_hash": "string" // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. + } + } + ``` + + + + + + ```bash + near view nft.primitives.near nft_token '{"token_id": "1"}' + ``` + + + + ```json + { + "token_id": "1", + "owner_id": "bob.near", + "metadata": { + "title": "string", // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + "description": "string", // free-form description + "media": "string", // URL to associated media, preferably to decentralized, content-addressed storage + "media_hash": "string", // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + "copies": 1, // number of copies of this set of metadata in existence when token was minted. + "issued_at": 1642053411068358156, // When token was issued or minted, Unix epoch in milliseconds + "expires_at": 1642053411168358156, // When token expires, Unix epoch in milliseconds + "starts_at": 1642053411068358156, // When token starts being valid, Unix epoch in milliseconds + "updated_at": 1642053411068358156, // When token was last updated, Unix epoch in milliseconds + "extra": "string", // anything extra the NFT wants to store on-chain. Can be stringified JSON. + "reference": "string", // URL to an off-chain JSON file with more info. + "reference_hash": "string" // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. + } + } + ``` + + + + + + + + + + ```json + { + "token_id": "1", + "owner_id": "bob.near", + "metadata": { + "title": "string", // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + "description": "string", // free-form description + "media": "string", // URL to associated media, preferably to decentralized, content-addressed storage + "media_hash": "string", // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + "copies": 1, // number of copies of this set of metadata in existence when token was minted. + "issued_at": 1642053411068358156, // When token was issued or minted, Unix epoch in milliseconds + "expires_at": 1642053411168358156, // When token expires, Unix epoch in milliseconds + "starts_at": 1642053411068358156, // When token starts being valid, Unix epoch in milliseconds + "updated_at": 1642053411068358156, // When token was last updated, Unix epoch in milliseconds + "extra": "string", // anything extra the NFT wants to store on-chain. Can be stringified JSON. + "reference": "string", // URL to an off-chain JSON file with more info. + "reference_hash": "string" // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. + } + } + ``` + + + + + ```rust + // Validator interface, for cross-contract calls + #[ext_contract(ext_nft_contract)] + trait ExternalNftContract { + fn nft_token(&self, token_id: TokenId) -> Promise; + } + + // Implement the contract structure + #[near] + impl Contract { + pub fn nft_token(&self, token_id: TokenId) -> Promise { + let promise = ext_nft_contract::ext(self.nft_contract.clone()) + .nft_token(token_id); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .nft_token_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn nft_token_callback(&self, #[callback_result] call_result: Result) -> Option { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting NFT contract"); + return None; + } + + // Return the token data + let token_data: Token = call_result.unwrap(); + return Some(token_data); + } + } + ``` + + + +--- + +## 转移 NFT +转移 NFT 有两种情景:(1)您请求转移 NFT,(2)[已授权账户](#approving-users)请求转移 NFT。 + +在这两种情况下,都需要调用 `nft_transfer` 方法,指定代币 ID、接收者,以及(可选地)一个 [approval_id](https://github.com/near/NEPs/tree/master/neps/nep-0178.md)。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const CONTRACT_ADDRESS = 'nft.primitives.near'; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: CONTRACT_ADDRESS, + method: 'nft_transfer', + args: { + token_id: '1', + receiver_id: 'bob.near', + }, + deposit: 1, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call nft.primitives.near nft_transfer '{"token_id": "1", "receiver_id": "bob.near"}' --useAccount bob.near --deposit 0.000000000000000000000001 + ``` + + + + + + + + 请注意,合约只能转移它所拥有的 NFT,或被授权转移的 NFT。 + + + ```rust + const YOCTO_NEAR: u128 = 1; + + #[ext_contract(ext_nft_contract)] + trait ExternalNftContract { + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> Promise; + } + + impl Contract { + #[payable] + pub fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId) -> Promise { + let promise = ext_nft_contract::ext(self.nft_contract.clone()) + .with_attached_deposit(YOCTO_NEAR) + .nft_transfer(receiver_id, token_id); + + return promise.then( // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .nft_transfer_callback() + ) + } + + #[private] // Public - but only callable by env::current_account_id() + pub fn nft_transfer_callback(&self, #[callback_result] call_result: Result<(), PromiseError>) { + // Check if the promise succeeded + if call_result.is_err() { + log!("There was an error contacting NFT contract"); + } + } + } + ``` + + + + +--- + +## 将 NFT 附加到调用 +原生情况下,只有 NEAR 代币(Ⓝ)可以附加到函数调用中。然而,NFT 标准允许通过以 NFT 合约为中介的方式,在调用中附加非同质化代币。这意味着您不是直接将代币附加到调用中,而是请求 NFT 合约代表您同时执行转账和函数调用。 + + + + +```bash +near call nft_transfer_call '{"receiver_id": "", "token_id": "", "msg": ""}' --useAccount --depositYocto 1 +``` + + + + + + + + + +可选地,可以传递 [`memo` 参数](https://github.com/near/NEPs/tree/master/neps/nep-0171.md#nft-interface)以向合约提供更多信息。 + + + +
+ +### 工作原理 +假设您想将一个 NFT(🎫)附加到接收合约上的调用中。工作流程如下: +1. 您调用 NFT 合约的 `nft_transfer_call`,传入接收者、消息和 🎫 的代币 ID。 +2. NFT 合约将 🎫 转移给接收者。 +3. NFT 合约调用 **`receiver.nft_on_transfer(sender, token-owner, token-id, msg)`**。 +4. NFT 合约在 `nft_resolve_transfer` 回调中处理错误。 +5. 如果成功,NFT 合约返回 `true`。 + +#### nft_on_transfer 方法 +从上述工作流程可以看出,我们要调用的接收合约需要实现 `nft_on_transfer` 方法。执行时,该方法将获知: +- 谁在发送 NFT,因为它是参数。 +- 当前所有者是谁,因为它是参数。 +- 哪个 NFT 被转移了,因为它是参数。 +- 是否有任何编码为消息的参数。 + +`nft_on_transfer` **必须返回 true** 如果 NFT 需要**返还给发送者**。 + +--- + +## 授权用户 + +您可以授权其他用户转移您拥有的 NFT。这在您想将 NFT 挂牌到市场时非常有用。在这种情况下,您**信任**市场只会在收到一定金额的代币作为交换后才会转移 NFT。 + + + + + ```bash + near call nft_approve '{ + "token_id": "", + "account_id": "", + "msg": "" + }' --useAccount --depositYocto 1 + ``` + + + + + + + + +如果包含 `msg` 参数,则将对 `.nft_on_approve(msg)` 进行跨合约调用。这将反过来对您的 NFT 合约中的 `nft_resolve_transfer` 进行回调。 + + + +--- + +## 销毁代币 + +虽然 NFT 标准没有定义 `burn` 方法,但您可以直接将代币转入无人控制的账户,例如 [`0000000000000000000000000000000000000000000000000000000000000000`](https://nearblocks.io/es/address/0000000000000000000000000000000000000000000000000000000000000000)(64 个零)。 + + + + + ```js + import { useNearWallet } from "near-connect-hooks"; + + const { callFunction } = useNearWallet(); + + await callFunction({ + contractId: 'nft.primitives.near', + method: 'nft_transfer', + args: { + token_id: '1', + receiver_id: '0000000000000000000000000000000000000000000000000000000000000000', + }, + deposit: 1, + }); + ``` + + 了解更多关于在应用程序中添加 [Near Connect](../../web3-apps/tutorials/wallet-login) 的信息 + + + + + ```bash + near call nft.primitives.near nft_transfer '{"token_id": "1", "receiver_id": "0000000000000000000000000000000000000000000000000000000000000000"}' --useAccount bob.near --deposit 0.000000000000000000000001 + ``` + + + +--- + +## 教程 + +- [NFT 教程](/smart-contracts/tutorials/zero-to-hero/nfts-js) _从零到英雄_(JavaScript SDK)- 一套涵盖如何使用 JavaScript 创建 NFT 合约的教程。 +- [NFT 教程](/smart-contracts/tutorials/zero-to-hero/nfts) _从零到英雄_(Rust SDK)- 一套涵盖如何使用 Rust 创建 NFT 合约的教程。 diff --git a/zh/primitives/nft/sdk-contract-tools.mdx b/zh/primitives/nft/sdk-contract-tools.mdx new file mode 100644 index 00000000000..d05b62e8e8d --- /dev/null +++ b/zh/primitives/nft/sdk-contract-tools.mdx @@ -0,0 +1,128 @@ +--- +title: 使用 Contract Tools 创建 NFT +description: "了解如何使用 Contract Tools 包创建非同质化代币(NFT)" +--- + +import { Github } from '/snippets/github.jsx'; + +在本教程中,您将使用 [NEAR SDK Contract Tools](https://github.com/near/near-sdk-contract-tools) 包创建一个非同质化代币(NFT)。该包是一套通用工具和模式的集合,旨在简化智能合约开发,包括: + +- 存储费用管理 +- 托管模式和派生宏 +- 所有者模式和派生宏 +- 暂停模式和派生宏 +- 基于角色的访问控制 +- 用于 [NEP 标准](./standard)的派生宏 + - [NEP-141](https://github.com/near/NEPs/blob/master/neps/nep-0141.md)(同质化代币),扩展 [NEP-148](https://github.com/near/NEPs/blob/master/neps/nep-0148.md) + - [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md)(存储管理),以及与同质化代币和非同质化代币标准的集成 + - [NEP-171](https://github.com/near/NEPs/blob/master/neps/nep-0171.md)(非同质化代币),扩展 [NEP-177](https://github.com/near/NEPs/blob/master/neps/nep-0177.md)、[NEP-178](https://github.com/near/NEPs/blob/master/neps/nep-0178.md)、[NEP-181](https://github.com/near/NEPs/blob/master/neps/nep-0181.md) + - [NEP-297](https://github.com/near/NEPs/blob/master/neps/nep-0297.md)(事件) + +--- + +## 简介 + +虽然可以仅使用 `near-sdk` 和 `near_contract_standards` 从头开始创建非同质化代币(NFT)合约(例如 [NFT 合约](https://github.com/near-examples/NFT)),但使用 `near-sdk-contract-tools` 是更简便的方式。 + +`near-sdk-contract-tools` 允许您通过在合约 `struct` 上派生宏来实现铸造/销毁逻辑、访问控制和其他 NFT 标准,就像 `OpenZeppelin` 为以太坊合约所做的那样。 + +--- + +## 基本 NFT 方法 + +要为智能合约派生基本 NFT 方法,您需要在合约 `struct` 上派生 `NonFungibleToken` 宏: + + + +这将为合约引入所有基本 NFT 方法: +- `new` +- `contract_source_metadata` +- `nft_is_approved` +- `nft_metadata` +- `nft_supply_for_owner` +- `nft_token` +- `nft_tokens` +- `nft_tokens_for_owner` +- `nft_total_supply` +- `nft_approve` +- `nft_resolve_transfer` +- `nft_revoke` +- `nft_revoke_all` +- `nft_transfer` +- `nft_transfer_call` +- `storage_balance_bounds` +- `storage_balance_of` +- `storage_deposit` +- `storage_unregister` +- `storage_withdraw` + +要为合约引入基本所有者方法,还需要派生 `Owner` 宏,它将添加以下方法: +- `own_get_owner` +- `own_get_proposed_owner` +- `own_accept_owner` +- `own_propose_owner` +- `own_renounce_owner` + +--- + +## 初始化 + +要使用自定义所有者、元数据和存储限制初始化基本 NFT 合约,请实现 `new` 方法: + + + +--- + +## 转账钩子 + +如果您想自定义代币转账的工作方式(即修改 `nft_transfer` 方法),您需要实现一个钩子。钩子是一种**包装(在组件函数前后注入代码)**的方式: + + + +然后将其派生到合约结构体: + + + +--- + +## 铸造 + +默认情况下,NFT 标准不包含铸造方法。但是,您可以通过实现 `nft_mint` 方法来轻松为所有者铸造代币: + + + + + +您可以根据需要修改此方法,例如,仅在合约未暂停时允许铸造(需要派生 [`Pausable`](https://github.com/near/near-sdk-contract-tools/tree/develop?tab=readme-ov-file#macro-combinations) 钩子),或仅允许具有特定角色的账户或白名单账户在自定义限制下进行铸造。 + + + +--- + +## 销毁 + +与铸造类似,销毁也不包含在 NFT 标准中。但是,您同样可以轻松实现它。 + +要允许用户销毁其代币,您可以添加一个 `burn` 方法: + + + +--- + +## 结论 + +使用 `near-sdk-contract-tools` 是一种简单灵活的方式,能以最少的样板代码创建 NFT 合约,让您专注于业务逻辑。 + +您可以通过从该包中派生相应的宏,进一步扩展此合约,添加暂停、基于角色的访问控制、托管模式等更多功能。 diff --git a/zh/primitives/nft/standard.mdx b/zh/primitives/nft/standard.mdx new file mode 100644 index 00000000000..b73d3b4bb01 --- /dev/null +++ b/zh/primitives/nft/standard.mdx @@ -0,0 +1,180 @@ +--- +title: 标准规范 +description: "了解 NEAR 上非同质化代币(NFT)的定义" +--- + +与同质化代币不同,非同质化代币(NFT)是唯一的,因此不可互换。这使得 NFT 非常适合代表资产所有权,例如一段数字内容或一张活动门票。 + +与同质化代币一样,NFT **并不存储**在用户的钱包中,而是每个 NFT 存储在一个 **NFT 合约**里。NFT 合约充当账本,负责创建、存储和转移 NFT。 + + +**NFT 与市场** + +请注意不要将 NFT 与 NFT 市场混淆。NFT 仅存储信息(元数据),而 NFT 市场是可以挂牌和以一定价格交换 NFT 的合约。 + + + +--- + +## NEP-171(NFT 接口) + +[NEP-171](https://github.com/near/NEPs/tree/master/neps/nep-0171.md) 是 NEAR 上所有非同质化代币的蓝图。它定义了合约必须实现的**一套通用规则**和**函数**,才能被视为非同质化代币合约。 + + + +请注意,NEP-171 定义了非同质化代币合约的**接口**和**预期行为**,但不规定内部逻辑的实现方式。 + +不同的 NFT 合约可以有不同的内部实现,同时仍然遵守 NEP-171 标准。 + + + +### 接口 + +#### `nft_token`(*只读*) + +返回给定 `token_id` 的代币信息 + +```ts +nft_token(token_id: string): { token_id: string, owner_id: string } | null +``` + +
+ +#### `nft_transfer` +将 `token_id` 从调用者转移到 `receiver_id`,可选地可以包含 `memo` 字段以向合约提供附加信息 + +调用者必须是代币的**当前所有者**或被**授权代表所有者转移**代币的**账户**,例如市场合约。授权机制在 [NEP-178 标准](https://github.com/near/NEPs/blob/master/neps/nep-0178.md)中定义。 + +> *要求:调用者必须附加[恰好 1 yoctoNEAR](../../smart-contracts/security/one_yocto)* + +```ts +nft_transfer(receiver_id: string, token_id: string, approval_id?: number, memo: string?): void +``` + +
+ +#### `nft_transfer_call` + +该函数将 `token_id` 转移到 `receiver_id`,**并在 `receiver_id` 上调用 `nft_on_transfer(sender_id, previous_owner_id, token_id, msg)` 方法**。 + +可选地,该函数可以为 NFT 合约提供 `memo`,以及一个 `msg` 字段,该字段将被发送到接收合约。 + +> 📖 此函数可用于在单个交易中将 NFT 转移到合约并触发接收方的某些操作,从而实现**将 NFT 附加到函数调用**的效果 + +```ts +nft_transfer_call(receiver_id: string, token_id: string, approval_id?: number, memo?: string, msg: string): Promise {} +``` + + + + 期望**接收**非同质化代币的智能合约**必须**实现此方法。 + + 该方法**必须**返回一个布尔值,指示代币是否应返还给发送者(`true`)或不返还(`false`)。 + + ```ts + nft_on_transfer(sender_id: string, previous_owner_id: string, token_id: string, msg: string): boolean + ``` + + ⚠️ 注意,此方法不需要由 NFT 合约本身实现,而是由任何期望接收非同质化代币的合约实现。 + + + +
+ +#### `nft_resolve_transfer` + +此方法用作[回调](../../smart-contracts/anatomy/crosscontract#callback-function)来解析 `nft_transfer_call` 交易,必要时处理退款。 + +如果代币成功转移到 `receiver_id`,则返回 `true`;如果代币返还给 `owner_id`,则返回 `false`。 + +```js +nft_resolve_transfer(owner_id: string, receiver_id: string, token_id: string, approved_account_ids?: Record): boolean +``` + +--- + +## NEP-177(NFT 元数据) + +[NEP-177](https://github.com/near/NEPs/blob/master/neps/nep-0177.md) 是 NEP-171 标准的扩展,定义了非同质化代币和非同质化代币合约的**元数据**。 + +元数据提供了**关键信息**,例如合约的**名称**或 NFT 的**标题**。特别地,智能合约和代币的元数据中必须包含以下字段: + +```ts +type NFTContractMetadata = { + spec: string, // 必填,本质上是版本号,如 "nft-1.0.0" + name: string, // 必填,例如 "Mochi Rising — Digital Edition" 或 "Metaverse 3" + symbol: string, // 必填,例如 "MOCHI" + icon: string|null, // 数据 URL + base_uri: string|null, // 已知能可靠访问 `reference` 或 `media` URL 引用的去中心化存储资产的集中式网关 + reference: string|null, // 包含更多信息的 JSON 文件的 URL + reference_hash: string|null, // reference 字段中 JSON 的 Base64 编码 sha256 哈希值。如果包含 `reference` 则必填。 +} + +type TokenMetadata = { + title: string|null, // 例如 "Arch Nemesis: Mail Carrier" 或 "Parcel #5055" + description: string|null, // 自由格式描述 + media: string|null, // 关联媒体的 URL,优先选择去中心化、内容寻址的存储 + media_hash: string|null, // `media` 字段引用内容的 Base64 编码 sha256 哈希值。如果包含 `media` 则必填。 + copies: number|null, // 铸造代币时,此组元数据存在的副本数量 + issued_at: number|null, // 代币发行或铸造时间,Unix 纪元毫秒数 + expires_at: number|null, // 代币过期时间,Unix 纪元毫秒数 + starts_at: number|null, // 代币开始生效时间,Unix 纪元毫秒数 + updated_at: number|null, // 代币最后更新时间,Unix 纪元毫秒数 + extra: string|null, // NFT 希望在链上存储的任何额外信息。可以是字符串化的 JSON。 + reference: string|null, // 包含更多信息的链下 JSON 文件的 URL。 + reference_hash: string|null // reference 字段中 JSON 的 Base64 编码 sha256 哈希值。如果包含 `reference` 则必填。 +} +``` + +--- + +## NEP-178(NFT 授权管理) + +[NEP-178](https://github.com/near/NEPs/blob/master/neps/nep-0178.md) 是 NEP-171 标准的扩展,定义了非同质化代币的**授权管理**。 + +授权机制允许 NFT 所有者授权另一个账户(例如市场合约)代表他们转移代币。 + +### 接口 + +#### `nft_is_approved`(*只读*) + +返回 `approved_account_id` 是否确实被授权代表代币所有者转移 `token_id`。如果提供了 `approval_id`,还将检查授权 ID 是否匹配。 + +```ts +nft_is_approved(token_id: string, approved_account_id: string, approval_id?: number): boolean +``` + +
+ +#### `nft_approve` + +授予 `account_id` 代表代币所有者转移 `token_id` 的权限。可选地,该函数可以包含 `msg` 字段以向合约提供附加信息。 + +> *要求:调用者必须附加至少 `1 yoctoNEAR` 加上添加新授权的存储成本* + +```ts +nft_approve(token_id: string, account_id: string, msg?: string): void +``` + +
+ +#### `nft_revoke` +撤销 `account_id` 代表代币所有者转移 `token_id` 的权限。 + +> *要求:调用者必须附加[恰好 1 yoctoNEAR](../../smart-contracts/security/one_yocto)* + +```ts +nft_revoke(token_id: string, account_id: string): void +``` + +
+ +#### `nft_revoke_all` + +撤销 `token_id` 的所有授权。 + +> *要求:调用者必须附加[恰好 1 yoctoNEAR](../../smart-contracts/security/one_yocto)* + +```ts +nft_revoke_all(token_id: string): void +``` diff --git a/zh/primitives/oracles.mdx b/zh/primitives/oracles.mdx new file mode 100644 index 00000000000..dc18f3a0d50 --- /dev/null +++ b/zh/primitives/oracles.mdx @@ -0,0 +1,171 @@ +--- +title: 预言机 +description: "了解 NEAR 协议上的区块链预言机,包括价格数据源、数据集成,以及使用 Outlayer 价格预言机和 Pyth Network 等预言机服务。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; +import { Github } from '/snippets/github.jsx'; + +[区块链预言机](https://en.wikipedia.org/wiki/Blockchain_oracle)作为中间件,将智能合约与外部(链下)数据连接起来。 + +`示例:` + +- **价格数据源:** 加密货币、股票或大宗商品的实时价格。 +- **事件信息:** 体育赛事结果或天气状况等现实世界事件的更新。 +- **API 访问:** 与外部 Web 服务和系统的连接。 + + +预言机作为外部第三方服务,需要仔细考量其可靠性、安全性和去中心化程度,以避免数据错误、操纵或单点故障等风险。 + + +--- + +## NEAR 上已部署的预言机 + +以下是 NEAR 区块链上已部署的第三方预言机服务目录: + +| 名称 | 创建者 | 描述 | +| -------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- | +| [Outlayer 价格预言机](#price-oracle-by-outlayer) | [Outlayer](https://github.com/zavodil/oracle-ark) | 基于 Outlayer 的 TEE 安全价格预言机 | +| [Pyth Network 预言机](#pyth-network-oracle) | [Pyth Network](https://pyth.network/) | 高频、低延迟的价格数据预言机 | +| **[[您的项目在此](https://github.com/near/docs/edit/master/primitives/oracles.mdx)]** | - | - | + +--- + +## Outlayer 价格预言机 + +- **创建者:** [Outlayer](https://github.com/fastnear/near-outlayer) +- **官方文档:** [Price Oracle Outlayer](https://price-oracle.outlayer.ai/docs/) +- **代码仓库:** [zavodil/oracle-ark](https://github.com/zavodil/oracle-ark) +- **已部署地址:** + - **主网:** [price-oracle.near](https://nearblocks.io/address/price-oracle.near) + - **测试网:** [price-oracle.testnet](https://testnet.nearblocks.io/address/price-oracle.testnet) + +### 使用/集成方法 + +`price-oracle.near` 原生实现了 Pyth 兼容的查看方法(详见下方 [Pyth Network 预言机](#pyth-network-oracle))。使用 `pyth-oracle.near` 的 DeFi 合约只需更改一个合约地址即可迁移,无需修改任何代码。 + +--- + +## Pyth Network 预言机 + +- **创建者:** [Pyth Network](https://pyth.network) +- **官方文档:** [Pyth NEAR 文档](https://docs.pyth.network/price-feeds/use-real-time-data/near) +- **代码仓库:** [pyth-network/pyth-crosschain](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/near) +- **已部署地址:** + - **主网:** [pyth-oracle.near](https://nearblocks.io/address/pyth-oracle.near) + - **测试网:** [pyth-oracle.testnet](https://testnet.nearblocks.io/address/pyth-oracle.testnet) + +--- + +## 使用 Pyth Network 预言机 + +> Pyth Network 的 NEAR 智能合约提供两个核心方法,用于更新和获取您所需的价格数据源。 + +1. [`update_price_feeds`](#update_price_feeds) + _(使用您提供的价格数据源更新 Pyth 智能合约)_ + - 参数:`data` + - 类型:`object` + - 示例:`{ "data": "504e41...' }` +2. [`get_price`](#get_price)(获取合约中存储的最新价格)\_ + - 参数:`price_identifier` + - 类型:`object` + - 示例:`{ price_identifier: 'f9c0172ba10dfa8...' }` + + +完整的可交互接口列表请参见 [Pyth 的 `receiver` 合约](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/near/receiver/src/ext.rs)。 + + +### 入门 + +开始使用 Pyth 预言机,您需要收集以下信息,这些信息因网络而异: + +- 价格 ID +- Hermes API 端点 +- 智能合约地址 + +| 网络 | 价格数据源 ID | Hermes API 地址 | 合约地址 | +| --------- | ------------------------------------------------------------------------------------------------ | -------------------------- | -------------------------------------------------------------------------------- | +| `testnet` | [NEAR `testnet` 价格数据源 ID](https://www.pyth.network/developers/price-feed-ids#near-testnet) | `hermes-beta.pyth.network` | [pyth-oracle.testnet](https://testnet.nearblocks.io/address/pyth-oracle.testnet) | +| `mainnet` | [NEAR `mainnet` 价格数据源 ID](https://www.pyth.network/developers/price-feed-ids#near-mainnet) | `hermes.pyth.network` | [pyth-oracle.near](https://nearblocks.io/address/pyth-oracle.near) | + + + +使用价格数据源 ID 时,需要去掉 `0x` 前缀。 + +`价格数据源 ID 示例(测试网):` + + + + + +--- + +### `update_price_feeds` + +> 使用您提供的价格数据源更新 Pyth Oracle 合约数据。 + +- 参数:`data` _(链下十六进制编码的价格数据源)_ +- 类型:`object` +- 示例:`{ "data": "504e41...' }` + +分两步更新 Pyth Oracle 合约的价格数据源: + +1. [获取链下价格数据源](#1-fetching-off-chain-price-feed) +2. [用链下价格数据源更新 Pyth Oracle 合约](#2-update-pyth-oracle-contract-price-feed) + +#### 1) 获取链下价格数据源 + +您可以使用 Pyth 的 [Hermes API](https://hermes-beta.pyth.network/docs/) 获取链下价格数据源。 + +使用这些接口时,需要提供价格数据源 ID,并确保指向正确的网络。更多信息请参见[入门](#getting-started)。 + +以下是使用 `/v2/updates/price/latest` 接口获取最新价格数据源的 node.js 示例: + + + +#### 2) 更新 Pyth Oracle 合约价格数据源 + +[获取链下价格数据源](#1-fetching-off-chain-price-feed)后,您可以向 Pyth Oracle 发起合约调用,在链上更新价格数据源。 + +以 `data` 为参数调用 Pyth Oracle 合约上的 `update_price_feeds`。 + +`示例参数:` + +```json +{ + "data": "504e41550100000000a00100000000010070b0ee3a00d1a3c07ee440887eb34a5a35860e6f4b9230fd62f0593fe35c8a3561735a6a37d269c5f166b84ead8918f710dc1be2ee6b51db5b22340ea2c173fc01673d544b00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa7100000000061bc18c014155575600000000000ab0f04600002710f41bc8c224ed983c68dbf5dab7dd34c9129fecfa03005500ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a600000047e2eb4ef0000000000692480ffffffff800000000673d544b00000000673d544b00000048200e66a00000000005e495a60bb9370c458dd50558b34699b5b179f45e56be22f0a1a0feb1db8469adc8c5efeb53988495bac07bf9efed07f5eee43818150c55055882f6872a228e8e9bc78459ed3ea7fe0b86f3048f6bf0aad34befc46063ab7d200beb8bc9fe5839844d2233546f0742bb665f1e610370fcf8ce5be83d0f47e584b685af87cf3ebcb79e714827dcb99dba579e1a03785052ab3c7c7147d3f7bba822b04dbda159670e9a8d29e7ccf68474b2ca85e00224d29bf65b06b09f95e91703313e053b697b48ac1e4d1c57605a71ab77e7ef276bfe8a369c268333b9a37461bf2b7cb7fd4c005500ecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000e2ba8cd280000000001b40517fffffff800000000673d544b00000000673d544b0000000e3ea44c6800000000016aee120b47b853f55949284cb8ba0b63824ff9b48cd1da8417f45421b79ee3195fc8d107540a0bbb95c2445b66065754f135cb842db09a7e7ab33f79c546a48db872bd7197b04e3d7b52fbb55b3b9f51707c5a55fac3707cb563dbcde4aadeecc3649c237454cecf519dc567c0da03d81808523aa4fa71815eab25ce7da61b48647bac645d403208135002aab5fde2d7ab3c7c7147d3f7bba822b04dbda159670e9a8d29e7ccf68474b2ca85e00224d29bf65b06b09f95e91703313e053b697b48ac1e4d1c57605a71ab77e7ef276bfe8a369c268333b9a37461bf2b7cb7fd4c" +} +``` + +使用 `near-js/client` 和 node.js 更新 Pyth Oracle 合约价格数据源的示例: + + + + + + + +虽然未使用的押金会被退还,但您可以通过调用 Pyth 合约的 `get_update_fee_estimate` 方法来估算费用。 + + + +--- + +### `get_price` + +> 获取 Pyth Oracle 合约中存储的最新价格数据源。这是一个查看方法,不需要签名或付款。 + +- 参数:`price_identifier` _(唯一价格数据源标识符)_ +- 类型:`object` +- 示例:`{ price_identifier: 'f9c0172ba10dfa8...' }` + +[更新价格数据源](#update_price_feeds)后,您可以通过调用 Pyth Oracle 合约上的 `get_price` 在链上查看数据源。请注意,这是一个查看方法,不需要签名或押金。 + +`示例:` + + diff --git a/zh/primitives/what-is.mdx b/zh/primitives/what-is.mdx new file mode 100644 index 00000000000..cec03695fca --- /dev/null +++ b/zh/primitives/what-is.mdx @@ -0,0 +1,37 @@ +--- +title: 什么是原语? +description: "了解区块链原语,包括同质化代币(FT)、非同质化代币(NFT)、去中心化自治组织(DAO)和 LinkDrop,它们是构建应用程序的基础模块。" +--- +原语是可以组合在一起以创建完整功能应用程序的基础构建模块。区块链原语包括[同质化代币(FT)](#fungible-tokens-ft)、[非同质化代币(NFT)](#non-fungible-tokens-nft)、[去中心化自治组织(DAO)](#decentralized-autonomous-organizations-dao)、[链接投放(LinkDrop)](#linkdrops)等。 + +![img](/assets/docs/welcome-pages/5.primitives.webp) + +--- + +#### 同质化代币(FT) +[同质化代币](./ft/ft)代表区块链上一种**可互换**的**资产**。除了原生 NEAR 代币之外,用户还可以发行自己的同质化代币,或使用生态系统中已有的代币。 + + +同质化代币非常适合创建**奖励系统**、**公平票务**以及其他任何类型的**代币**。 + + +#### 非同质化代币(NFT) +与同质化代币不同,每个[非同质化代币(NFT)](./nft/nft)都是**唯一的**,因此**不可互换**。用户可以创建自己的非同质化代币,将其转让给其他用户,或在市场上进行交易。 + + +NFT 非常适合代表**资产所有权**,例如**收藏品**、**活动门票**和其他独特资产。 + + +#### 去中心化自治组织(DAO) +[去中心化自治组织(DAO)](./dao)是围绕共同目标形成的**自组织群体**。成员资格、决策制定和资金分配均通过智能合约上的公开投票来**协调**。 + + +DAO 非常适合创建**去中心化治理**、**资金分配**和**决策工具**。 + + +### 链接投放(LinkDrop) +[链接投放(LinkDrop)](./linkdrop/linkdrop)是通过链接**分发数字资产**(NFT、FT)的简便方式。您只需**提供一个链接**,用户即可**领取**您的投放内容。 + + +LinkDrop 非常适合进行**空投**,以及**引导新用户**进入 Web3 应用。 + diff --git a/zh/protocol/accounts-contracts/access-keys.mdx b/zh/protocol/accounts-contracts/access-keys.mdx new file mode 100644 index 00000000000..9a235507659 --- /dev/null +++ b/zh/protocol/accounts-contracts/access-keys.mdx @@ -0,0 +1,96 @@ +--- +title: 访问密钥 +description: "了解 NEAR 的访问密钥系统——全访问密钥可完全控制账户,函数调用密钥则为特定合约提供受限的、可共享的权限。" +--- + +在 NEAR 中,用户通过访问密钥来控制其账户,访问密钥分为全访问密钥和函数调用密钥。全访问密钥允许对账户进行完全控制,而函数调用密钥则将操作限制在特定合约范围内。该系统实现了权限的安全共享,并简化了用户与应用程序的交互。 + + +--- + +## 访问密钥 + +在大多数区块链中,用户通过持有单一的 [`私钥`](https://en.wikipedia.org/wiki/Public-key_cryptography)(只有自己知道的秘密)并用它来签署[交易](/protocol/transactions)来控制其账户。 + +![访问密钥](/assets/docs/welcome-pages/access-keys.png) + +在 NEAR 中,我们区分两种类型的访问密钥: + +1. `全访问密钥`:对账户拥有完全控制权,**永远不应共享** +2. `函数调用密钥`:只能签署对特定合约的调用,**设计用于共享** + +NEAR 中的每个账户可以持有**多个密钥**,密钥可以被添加或移除,从而对账户权限实现精细化管控。 + +--- + +## 函数调用密钥 + +`函数调用`密钥只能签署调用**特定合约**的交易,且**不允许**在调用中**附带 NEAR 代币**。 + +它们由三个属性定义: +1. `receiver_id`:该密钥**唯一允许**调用的合约,无法使用此密钥调用其他合约 +2. `method_names`(可选):密钥允许调用的合约**方法**。若省略,则可调用合约的所有方法 +3. `allowance`(可选):允许用于[gas](/protocol/transactions/gas) 的 **NEAR 数量**。若省略,该密钥可消耗**无限制**的 gas + +`函数调用密钥`的设计用途是与应用程序共享,以便第三方可以以您的名义进行合约调用。这在多种场景下非常有用,如下所述。 + + +`函数调用`密钥可以安全共享,因为它们只允许调用特定合约,并禁止 NEAR 代币转账 + + +--- + +## 全访问密钥 +顾名思义,`全访问`密钥对账户拥有完全控制权,这意味着它可以签署代表您账户执行任何操作的[交易](/protocol/transactions): + +1. 转账 NEAR Ⓝ +2. 删除您的账户或创建其子账户 +3. 添加或删除访问密钥 +4. 在账户中部署智能合约 +5. 调用任意合约的方法 + +您永远不应共享您的`全访问`密钥,否则将**完全失去对账户的控制权**。 + + +[隐式账户](./account-id#implicit-address) 默认已拥有一个`全访问密钥`,而对于[`命名账户`](./account-id#named-address),其第一个`全访问密钥`在创建时添加 + + +--- + +## 受限访问密钥的注意事项 + +### 仅有函数调用密钥的账户 + +如果一个账户**没有全访问密钥**,只有函数调用密钥,那么该账户将受到实质性限制: +- 它**无法**转账 NEAR、删除自身或管理其密钥 +- 它**只能**执行由密钥的 `receiver_id` 和 `method_names` 定义的特定合约调用 + +这对于创建受限子账户(例如用于[链签名](../../chain-abstraction/chain-signatures))很有用,但请注意,该账户无法通过标准交易进行恢复或重新配置。 + + +创建只有单个函数调用密钥的子账户意味着该账户将**永远**无法移除自身、转出 NEAR 或添加新密钥——除非目标合约提供了相应的方法。 + + +### 配额耗尽 + +`allowance` 字段定义了密钥可用于 gas 费的 NEAR 数量: + +- 若设置为特定金额且已完全消耗 → 密钥变为**不可用**,无法签署任何新交易 +- 若设置为 `0` 或省略 → **无限制**配额(密钥无 gas 预算限制) + + +如果一个账户只有函数调用密钥且配额耗尽,该账户将永久无法发起任何交易。请使用无限制配额(`0`)或确保在配额耗尽之前为账户充值 NEAR。 + + +--- + +## 锁定账户 +如果您移除账户的所有密钥,该账户将变为**锁定**状态,这意味着任何外部参与者都无法以该账户名义执行交易。 + +实际上,这意味着只有账户的智能合约才能转移资产、创建子账户或更新其代码。 + +锁定账户在以下情况下非常有用:当您想要部署合约,并让社区确信只有合约控制该账户时。 + + +账户仍可以通过智能合约向自身添加密钥,从而允许合约解锁账户。请注意,这只有在合约在账户被锁定之前部署的情况下才能实现 + diff --git a/zh/protocol/accounts-contracts/account-id.mdx b/zh/protocol/accounts-contracts/account-id.mdx new file mode 100644 index 00000000000..f69e0f411fc --- /dev/null +++ b/zh/protocol/accounts-contracts/account-id.mdx @@ -0,0 +1,134 @@ +--- +title: 地址(账户 ID) +description: "全面了解 NEAR 账户地址" +--- + +NEAR 账户通过唯一地址进行标识,该地址可以有多种形式: + +1. [**隐式地址**](#implicit-address),长度为 64 个字符(例如 `fb9243ce...`) +2. [**命名地址**](#named-address),类似于域名(例如 `alice.near`、`sub.account.testnet`) +3. 以太坊风格的账户(例如 `0x85f17cf997934a597031b2e18a9ab6ebd4b9f6a4`) + + + +在 NEAR 中,账户实际上可以是任何字符串,只要满足以下条件: +- 至少 2 个字符,最多 64 个字符 +- 只能使用小写字母(`a-z`)、数字(`0-9`)和分隔符(`.`、`-`、`_`) + +这意味着 `root`、`some-unique-string`、`something-to-remember-later`、`0x85f17....`、`fb9243ce` 和 `user.name` 都是**有效的账户 ID**。 + +但是,用户只能创建`命名地址`、`隐式地址`或`以太坊风格地址`的账户。 + + + + +**想要创建账户?** + +您有多种方式创建账户:可以创建[网页钱包](https://wallet.meteorwallet.app/wallet)、通过 [Telegram](https://web.telegram.org/k/#@herewalletbot) 创建移动钱包,或从可用的 [NEAR 钱包](https://wallet.near.org/)中选择。 + + + +--- + +## 隐式地址 +隐式账户由 64 个字符的地址表示,对应一个唯一的公钥/私钥对。控制隐式账户[私钥](./access-keys)的人即控制该账户。 + +例如: +- 私钥:`ed25519:4x1xiJ6u3sZF3NgrwPUCnHqup2o...` +- 对应的公钥:`ed25519:CQLP1o1F3Jbdttek3GoRJYhzfT...` +- 控制账户:`a96ad3cb539b653e4b869bd7cf26590690e8971...` + +隐式账户始终*存在*,因此无需创建。但要使用该账户,您仍需要向其充值 NEAR 代币(或通过中继者支付交易的 [gas](../transactions/gas))。 + + + +在 NEAR 中,您可以删除隐式账户的私钥,这将有效锁定该账户,防止任何人控制它。 + + + + + +获取代表账户的公钥/私钥最简单的方式是使用 [NEAR CLI](/tools/cli) + +```bash +near account create-account fund-later use-auto-generation save-to-folder ~/.near-credentials/implicit + +# 文件 "~/.near-credentials/implicit/8bca86065be487de45e795b2c3154fe834d53ffa07e0a44f29e76a2a5f075df8.json" 已成功保存 + +cat ~/.near-credentials/implicit/8bca86065be487de45e795b2c3154fe834d53ffa07e0a44f29e76a2a5f075df8.json +``` + +或使用 `near-seed-phrase` 库: + +```js +import { generateSeedPhrase } from "near-seed-phrase"; +const { seedPhrase, publicKey, secretKey } = generateSeedPhrase(); +``` + + + + + +您可以通过以下方式从公钥推导隐式账户地址:移除 `ed25519:` 前缀,将结果 Base58 字符串解码为字节,然后将这些字节转换为十六进制字符串。 + +```js +// 原生 js +import { decode } from 'bs58'; +Buffer.from(decode(publicKey.replace('ed25519:', ''))).toString('hex') + +// 或使用 near-api-js +import { utils } from 'near-api-js'; +utils.keyToImplicitAddress(publicKey); +``` + + + +--- + +## 命名地址 +用户可以注册**命名账户**(例如 `bob.near`),这类账户易于记忆和共享。 + +命名账户的一个强大特性是它们可以创建自身的**子账户**,有效地充当域名: + +1. [`registrar`](https://nearblocks.io/address/registrar) 账户可以创建顶级账户(例如 `near`、`sweat`、`kaiching`)。 +2. `near` 账户可以创建子账户,如 `bob.near` 或 `alice.near` +3. `bob.near` 可以创建其自身的子账户,如 `app.bob.near` +4. 账户不能创建其他账户的子账户 + - `near` **不能**创建 `app.bob.near` + - `account.near` **不能**创建 `sub.another-account.near` +5. 账户对其子账户**没有控制权**,它们是不同的实体 + +任何人都可以创建 `.near` 或 `.testnet` 账户,只需调用对应顶级账户的 `create_account` 方法——测试网上是 `testnet`,主网上是 `near`。 + + + +在 NEAR 中,命名账户是使用 [`CreateAccount`](../transactions/transaction-anatomy#actions) 操作创建的。`testnet` 和 `near` 账户都暴露了 `create_account` 函数,该函数触发 `CreateAccount` 来创建其子账户。 + +例如,如果我们想用 `0.1 NEAR` 创建账户 `alice.testnet`,我们需要使用现有账户(例如 `funding-account.testnet`)调用 `testnet` 的 `create_account` 方法: + +```bash +near call testnet create_account '{"new_account_id": "alice.testnet", "new_public_key": "ed25519:"}' --useAccount funding-account.testnet --networkId testnet +``` + +如果我们随后想创建 `alice.testnet` 的子账户(我们控制的),我们可以直接触发 `CreateAccount` 操作: + +```bash +near create-account sub-acc.new-acc.testnet --useAccount new-acc.testnet +``` + +> 注意:如果您希望账户有一些初始余额,可以在上述命令中添加 `--deposit <以 NEAR 为单位的金额>` 标志 + + + + +**创建命名账户** + +如果您不通过中介(即 CLI 或钱包),可以创建一个隐式账户,为其充值,然后在 `near` / `testnet` 上调用 `create_account` + + + +--- + +## 以太坊风格的地址 + +NEAR 还支持以太坊风格的账户,这些账户通过十六进制地址标识(例如 `0x85f17cf997934a597031b2e18a9ab6ebd4b9f6a4`)。当用户使用 MetaMask 等以太坊钱包登录 NEAR 应用程序时,这些账户会自动创建。 diff --git a/zh/protocol/accounts-contracts/account-model.mdx b/zh/protocol/accounts-contracts/account-model.mdx new file mode 100644 index 00000000000..15404a1880e --- /dev/null +++ b/zh/protocol/accounts-contracts/account-model.mdx @@ -0,0 +1,59 @@ +--- +title: NEAR 账户 +description: "了解 NEAR 协议的账户模型,包括命名账户和隐式账户、访问密钥、权限,以及 NEAR 账户与其他区块链平台的区别。" +--- + +用户通过其 NEAR 账户参与 NEAR 生态系统。这些账户通过[唯一地址](./account-id)进行标识,可以选择性地持有[智能合约](/smart-contracts/what-is),并通过[访问密钥](./access-keys)进行控制。 + +通过使用账户签署[交易](../transactions),用户可以: + +1. 发送和接收**数字资产**(如代币或收藏品) +2. 创建和交互称为**智能合约**的链上应用程序 +3. 控制**其他链**(如以太坊或比特币)上的账户 ✨ +4. 通过**承担交易费用**(gas 费)帮助新用户上手 + + +**想要创建账户?** + +查看我们的[创建 NEAR 账户](/getting-started/create-account)教程开始使用! + + +--- + +## 账户模型概述 + +让我们仔细了解构成 NEAR 账户模型的各个元素。 + +![账户模型](/assets/docs/welcome-pages/accounts.png) + +### [账户 ID](/protocol/accounts-contracts/account-id) +NEAR **原生**支持多种账户类型,包括: +1. **命名账户**,如 `alice.near`,易于记忆和共享 +2. **隐式账户**,如 `fb9243ce...`,由私钥派生 +2. **以太坊风格账户**,与以太坊钱包兼容 + +
+ +### [多密钥](/protocol/accounts-contracts/access-keys) +NEAR 账户可以拥有多个[密钥](/protocol/accounts-contracts/access-keys),每个密钥有其自己的权限集: +- 若某个密钥被泄露,可以轻松更换 +- 可以将密钥用作第三方应用程序的授权令牌 + +
+ +### [智能合约](/smart-contracts/what-is) +NEAR 账户可以选择性地持有一个应用程序——称为[智能合约](/smart-contracts/what-is)——可以用 Javascript 或 Rust 编写。 + +--- + +## 与以太坊的比较 + +如果您熟悉以太坊开发,值得快速了解一下账户的不同之处。下表总结了一些关键差异: + +| | 以太坊账户 | NEAR 账户 | +|-----------------|--------------------------|----------------------------------------------------------------------------------------| +| 账户 ID | 公钥(`0x123...`) | - 原生命名账户(`alice.near`)
- 隐式账户(`0x123...`) | +| 私密密钥 | 私钥(`0x456...`) | 具有权限的多个密钥对:
- `FullAccess` 密钥
- `FunctionCall` 密钥 | +| 智能合约 | 同步执行 | 异步执行 | +| Gas 费 | 以美元为数量级 | 以美分的十分之一为数量级 | +| 出块时间 | 约 12 秒 | 约 600 毫秒 | diff --git a/zh/protocol/data-flow/near-data-flow.mdx b/zh/protocol/data-flow/near-data-flow.mdx new file mode 100644 index 00000000000..9711ccfc289 --- /dev/null +++ b/zh/protocol/data-flow/near-data-flow.mdx @@ -0,0 +1,72 @@ +--- +title: NEAR 数据流 +description: "了解 NEAR 协议中的数据流转,包括交易、收据、分片和跨分片通信。" +--- + +NEAR 协议区块链的数据流乍看可能有些复杂,但它实际上相当直观,遵循明确定义的规则。在本文中,我们将深入了解 NEAR 协议区块链中数据是如何流转的。 + + + + +**数据流** + +在这个视频中,我们简要概述了数据在 NEAR 协议区块链中流转的核心概念。 + + + +实际上,任何区块链的流转都可以表示为一个有起点但无终点的无限时间线。 + +![区块时间线](/assets/docs/protocol/data-flow/01-timeline.png) + +区块以固定间隔出现在该时间线上。每个区块都持有前一个区块的信息,从而形成*区块链*。 + +NEAR 协议具有分片特性,这意味着在任何时刻都可能有多个并行网络(称为`分片`)在运行。每个分片在给定间隔内生产一个区块的分块。NEAR 区块链中的一个区块是所有分片的区块分块的集合。区块分块在 NEAR 协议文档中简称为`分块(Chunk)`。 + +回到数据流本身。最好的理解方式是想象音频/视频编辑应用中的轨道。每个分片都有自己的一组轨道。顶部轨道用于分块,它们以固定间隔出现,无论区块链上是否发生了任何事情。对于 NEAR 区块链,间隔约为一秒。即使区块链上什么都没发生,分块也会持续生产。 + +![作为轨道的时间线](/assets/docs/protocol/data-flow/02-tracks.png) + +那么,"发生了某些事情"是什么意思呢?我们指的是某些事情触发了区块链上的某些变化。触发变化最广为人知的方式是向区块链发送一个`交易`,其中包含我们想要进行的变化以及请求这些变化的人的指令。 + +交易需要构建、签名并发送到区块链。执行后,我们期望得到一个结果——`执行结果(ExecutionOutcome)`。听起来很简单,但对于 NEAR 区块链而言并非如此简单。 + +![交易执行](/assets/docs/protocol/data-flow/03-tx-outcome-receipt.png) + +最初有一个`交易`,它包含我们希望在区块链上执行的指令。交易被发送到 NEAR 区块链。 + +是的,它会立即在那里被执行,但交易执行的直接结果始终只是*关于它将在链上执行的确认*;这个内部执行请求被称为`收据(Receipt)`。您可以将`收据`视为在消息传递系统内部传递信息的内部交易。 + +让我们回到轨道来看一个例子。 + +假设我们有两个账户 **alice.near** 和 **bob.near**,分别位于不同的`分片`上。**alice.near** 创建一个`交易`向 **bob.near** 发送一些代币。该`交易`立即被执行,`交易`的`执行结果`始终是一个`收据`。 + +但这个`收据`不能在这里执行,因为 **bob.near** 与 **alice.near** 不在同一个分片上,所以**收据必须在接收方的分片上执行**。因此,收据移动到 **bob.near** 所属的分片。 + +在目标分片上,收据被执行,整个过程即告完成。 + + +**此处的解释已被简化** + +请参阅[代币转账](/protocol/data-flow/token-transfer-flow)流程文章 + + + +最终的流程图如下: + +![从一个分片上的账户向另一个分片上的账户发送代币的完整流程](/assets/docs/protocol/data-flow/04-send-nears-flow.png) + +## 总结 + +我们已经学习了 NEAR 协议中数据流转的主要原则。我们了解到`交易`执行后,其`执行结果`始终是一个`收据`。 + +现在我们知道,`收据`是 NEAR 协议区块链的主要内部资产,它能够在`分片`之间传输。我们通过一个简单的示例了解了 NEAR 数据流。当然,在实际场景中,涉及跨合约调用的更复杂交易会产生更多的收据和执行结果。 + +我们希望本文对您有所帮助,掌握 NEAR 协议中数据流转的知识将助您轻松构建 dApps 和索引器。 diff --git a/zh/protocol/data-flow/token-transfer-flow.mdx b/zh/protocol/data-flow/token-transfer-flow.mdx new file mode 100644 index 00000000000..063393bcc96 --- /dev/null +++ b/zh/protocol/data-flow/token-transfer-flow.mdx @@ -0,0 +1,83 @@ +--- +description: "了解代币转账涉及的所有步骤。" +--- + +本节解释了 NEAR 协议中代币转账的流程,详细描述了代币如何在账户之间移动、收据的作用,以及 gas 退款在该过程中的重要性。 + +# 代币转账流程 + +在[上一篇文章](/protocol/data-flow/near-data-flow)中,我们看到了一个位于不同分片上的账户之间代币转账的示例。那个示例经过了简化,省略了流程中的一些步骤。这是有意为之的,目的是保持文章和视频简短而易于理解,以便给您一个理解概念的宏观视角。 + +在本文中,我们将详细了解相同的数据流,并考虑两个额外的场景: + +- 不同分片上账户之间的代币转账 +- 同一分片上账户之间的代币转账 + +您可能会问,上一个解释中遗漏了什么。简短的回答是:**Gas 退款**,或简称**退款**。 + +如果您不了解 **Gas** 是什么,请[先阅读我们文档中关于 Gas 的文章](/protocol/transactions/gas)。 + +关于*退款*,以下是来自 [Gas](/protocol/transactions/gas) 文章的引用: + +> 多附加 gas,获得退款! +> +> ... +> +> - 如果您附加的 gas 超出所需,您将获得退款 +> +> ... +> +> *来自 NEAR 协议文档 [Gas. 多附加 gas,获得退款!](/protocol/transactions/gas)* + + +**退款在数据流方面意味着什么** + +这意味着每一笔交易都包含退款。 + + + +好了,这些介绍已经足够了,现在让我们进入示例。 + +## 不同分片上账户之间的代币转账 + +这基本上是 [NEAR 数据流](/protocol/data-flow/near-data-flow)文章示例的扩展。 + +假设我们有两个账户 **alice.near** 和 **bob.near**,它们属于不同的`分片`。**alice.near** 向 **bob.near** 发送一些代币。 + +由 **alice.near** 签名的`交易`被发送到网络。它立即被执行,`执行结果`是将交易转换为`收据`的输出或结果。 + +![交易执行](/assets/docs/protocol/data-flow/03-tx-outcome-receipt.png) + +在上述过程中,发送方 **alice.near** 被收取了费用(gas)。作为`交易`结果创建的`收据`遵循以下规则: + +1. 它的执行时间不早于下一个`区块` +2. 它**必须**在接收方的`分片`上执行 + +因此,在我们的例子中,接收方是 **bob.near**,该账户属于不同的`分片`,这就是为什么`收据`移动到接收方的分片并被放入执行队列。 + +在我们的示例中,收据在下一个区块中被执行。 + +![收据在下一个区块中被执行](/assets/docs/protocol/data-flow/04-send-nears-flow.png) + +几乎完成了。还记得退款吗?收据的`执行结果`将是另一个退还 Gas 给发送方的收据。**bob.near** 已经收到了来自 **alice.near** 的代币。现在,**alice.near** 成为新的(也是最后一个)收据的接收方(请记住,此收据中的发送方始终是**系统**)。 + +记住规则 #2:收据必须在接收方的分片上执行。因此,此收据移动到 **alice.near** 所属的分片。这是这个过程中的最后一次执行。 + +![不同分片上账户之间代币转账的完整流程](/assets/docs/protocol/flow-token-transfer/01-diff-shards-complete.png) + +就这样。代币已从一个分片上的账户转移到另一个分片上的账户,初始发送方 **alice.near** 收到了 Gas 退款。 + +## 同一分片上账户之间的代币转账 + +让我们来看两个账户都在同一个`分片`上的示例。该过程与前一个示例相同,只是没有收据从一个分片移动到另一个分片。 + +由 **alice.near** 签名的`交易`被发送到网络。它立即被执行,`执行结果`是将交易转换为`收据`的结果。 + +![交易执行](/assets/docs/protocol/data-flow/03-tx-outcome-receipt.png) + +收据已经在接收方的分片上,因此它被放入下一个`区块`的执行队列中。它在下一个区块中被执行,`执行结果`是一个新的收据,包含对初始发送方 **alice.near** 的退款。 +同样的规则适用于此收据,它被放入执行队列并在下一个区块中执行。 + +![同一分片上账户之间代币转账的完整流程](/assets/docs/protocol/flow-token-transfer/02-same-shard-complete.png) + +就这样。您可能会想,为什么同一分片的情况下流程如此复杂。答案是:**相同的规则始终适用**。此外,这种机制允许仅用一套规则构建 NEAR 协议数据流,无论存在多少分片。这样我们避免了大量的"如果"条件,也不必记住不同的边缘情况,因为流程始终遵循相同的规则。 diff --git a/zh/protocol/network/architecture.mdx b/zh/protocol/network/architecture.mdx new file mode 100644 index 00000000000..f758356b5ec --- /dev/null +++ b/zh/protocol/network/architecture.mdx @@ -0,0 +1,71 @@ +--- +title: 架构 +description: "NEAR 协议架构的全面高层概述" +--- + +NEAR 大致由区块链层和[运行时层](/protocol/network/runtime)组成。 +这两层在设计上相互独立:区块链层理论上可以支持以不同方式处理交易、拥有不同虚拟机(例如 RISC-V)、收取不同费用的运行时;另一方面,运行时对交易的来源一无所知。它不知道运行它的区块链是否分片,使用何种共识机制,甚至是否作为区块链的一部分运行。 + +区块链层和运行时层共享以下组件和不变量: + +## 交易与收据 + +[交易](/protocol/transactions)和收据是 Near 协议中的基本概念。交易表示区块链用户请求的操作,例如发送资产、创建账户、执行方法等。收据则是内部结构;可以将收据视为在消息传递系统内部使用的消息。 + +交易由用户在 Near 协议节点之外创建,通过 RPC 或网络通信发送。收据由运行时从交易创建,或作为处理其他收据的结果创建。 + +区块链层不能创建或处理交易和收据,它只能通过传递它们并将其提供给运行时来操作它们。 + +## 基于账户的系统 + +与以太坊类似,Near 协议是一个[基于账户的系统](/protocol/accounts-contracts/account-model)。这意味着每个区块链用户大致与一个或多个账户相关联(但有例外,当用户共享一个账户并通过[访问密钥](/protocol/accounts-contracts/access-keys)加以区分时)。 + +运行时本质上是一套复杂的规则集,根据交易和收据中的信息决定如何处理账户。因此,运行时对账户的概念有着深刻的理解。 + +区块链层则主要通过 [trie(字典树)](#trie) 和[验证者](#validators)来感知账户。在这两者之外,它不直接操作账户。 + +### 假设每个账户属于其自身的分片 + +NEAR 中的每个账户都属于某个分片。 +与该账户相关的所有信息也属于同一分片。这些信息包括: + +- 余额 +- 锁定余额(用于质押) +- 合约代码 +- 合约的键值存储 +- 所有访问密钥 + +运行时假设这是合约执行时唯一可用的信息。 +虽然其他账户可能属于同一分片,但运行时在合约执行期间从不使用或提供这些信息。 +我们可以简单地假设每个账户都属于其自身的分片。因此,没有必要刻意尝试将账户放置在同一分片上。 + +## Trie(字典树) + +Near 协议是一个有状态的区块链——每个账户都有一个关联的状态,用户通过交易执行的操作会改变该状态。状态以 trie 的形式存储,区块链层和运行时层都了解这一技术细节。 + +区块链层直接操作 trie。它在分片之间划分 trie 以分配负载。它在节点之间同步 trie,最终负责通过共识机制和其他博弈论方法维护节点之间 trie 的一致性。 + +运行时层也知道它用于执行操作的存储是 trie。一般来说,它不必了解这一技术细节,理论上我们可以将 trie 抽象为通用的键值存储。但是,我们允许一些特定于 trie 的操作,这些操作暴露给智能合约开发者,以便他们最大效率地使用 Near 协议。 + +## 代币与 gas + +尽管[代币](/protocol/network/tokens)是区块链的基本概念,但它与 [gas](/protocol/transactions/gas)、[费用](/protocol/transactions/gas#understanding-gas-fees)和奖励一起被整洁地封装在运行时层内。 + +区块链层感知代币和 gas 的唯一方式是通过计算汇率和通货膨胀,而这严格基于区块生产机制。 + +## 验证者 + +区块链层和运行时层都了解一组特殊的参与者,他们负责维护 Near 协议的完整性。这些参与者称为[验证者](/protocol/network/validators),与账户关联并获得相应奖励。奖励部分由运行时层负责,而围绕验证者协调的所有其他内容则在区块链层内。 + +## 区块链层概念 + +有趣的是,以下概念仅属于区块链层,运行时层并不知晓: + +- 分片——运行时层不知道它被用于分片区块链,例如它不知道它操作的 trie 只是整体区块链状态的一部分; +- 区块或分块——运行时不知道它处理的收据构成一个分块,以及输出的收据将用于其他分块。从运行时的角度看,它消费和输出批量的交易和收据; +- 共识——运行时不知道状态的一致性是如何维护的; +- 通信——运行时对当前网络拓扑一无所知。收据只有 `receiver_id`(接收方账户),但不知道目标分片,因此将特定收据路由到正确位置是区块链层的责任。 + +## 运行时层概念 + +- [费用与奖励](/protocol/transactions/gas)——费用和奖励被整洁地封装在运行时层中。区块链层则通过计算代币兑 gas 汇率和通货膨胀间接了解它们。 diff --git a/zh/protocol/network/epoch.mdx b/zh/protocol/network/epoch.mdx new file mode 100644 index 00000000000..cf77a4cf7f4 --- /dev/null +++ b/zh/protocol/network/epoch.mdx @@ -0,0 +1,87 @@ +--- +title: 纪元(Epoch) +description: "了解 NEAR 协议中的纪元,包括其持续时间、在验证者选择中的作用,以及对网络运营和数据保留的影响。" +--- + +本节解释了 NEAR 协议中**纪元**的概念,即网络验证者保持不变的时间单位。描述了纪元的衡量方式、持续时间及其在网络运营中的重要性。 + +**纪元**是网络验证者保持不变的时间单位。以区块为单位进行衡量: + +- `testnet` 和 `mainnet` 的纪元持续时长均为 43,200 个区块。理想情况下,由于每秒产生一个区块,纪元大约持续 12 小时(实际上产生时间会略长)。 +- 您可以通过查询 **[`protocol_config`](/api/rpc/protocol#protocol-config)** RPC 端点并搜索 `epoch_length` 来查看此设置。 + +**注意:** 节点在 5 个纪元(约 2.5 天)后会垃圾回收区块,除非它们是[归档节点](https://near-nodes.io/intro/node-types#archival-node)。 + +**示例:** + + + + + +```json +{ + "jsonrpc": "2.0", + "id": "dontcare", + "method": "EXPERIMENTAL_protocol_config", + "params": { + "finality": "final" + } +} +``` + + + + + +```bash +http post https://rpc.testnet.near.org jsonrpc=2.0 id=dontcare method=EXPERIMENTAL_protocol_config \ + params:='{ + "finality": "final" + }' +``` + + + + + +**示例响应:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "protocol_version": 44, + "genesis_time": "2020-07-21T16:55:51.591948Z", + "chain_id": "mainnet", + "genesis_height": 9820210, + "num_block_producer_seats": 100, + "num_block_producer_seats_per_shard": [ + 100 + ], + "avg_hidden_validator_seats_per_shard": [ + 0 + ], + "dynamic_resharding": false, + "protocol_upgrade_stake_threshold": [ + 4, + 5 + ], + "epoch_length": 43200, + "gas_limit": 1000000000000000, + "min_gas_price": "1000000000", + "max_gas_price": "10000000000000000000000", + "block_producer_kickout_threshold": 90, + "chunk_producer_kickout_threshold": 90, + +// ---- snip ---- +} +``` + +您可以在[验证者常见问题解答](https://github.com/near/wiki/blob/master/Archive/validators/faq.md#what-is-an-epoch)中了解更多关于如何使用纪元管理网络验证的信息。 + + +**有问题?** + + 在 StackOverflow 上提问! + + diff --git a/zh/protocol/network/networks.mdx b/zh/protocol/network/networks.mdx new file mode 100644 index 00000000000..8add0de2164 --- /dev/null +++ b/zh/protocol/network/networks.mdx @@ -0,0 +1,46 @@ +--- +title: NEAR 网络 +description: "探索 NEAR 中可用的不同网络" +--- + +NEAR 协议在多个网络上运行,每个网络都有其独立的验证者和独特的状态。这些网络旨在服务于不同目的,从生产就绪的应用程序到测试和开发环境。 + +## NEAR 网络概述 + +- [`mainnet`](/protocol/network/networks#mainnet) +- [`testnet`](/protocol/network/networks#testnet) +- [`localnet`](/protocol/network/networks#localnet) + +## 主网(Mainnet) + +`mainnet` 用于生产就绪的智能合约和真实代币转账。准备好上 `mainnet` 的合约应经过严格测试,必要时还需进行独立的安全审计。`mainnet` 是唯一一个状态保证随时间持久存在的网络 _(受网络验证流程的典型安全保障约束)_。 + +- 状态:`https://rpc.mainnet.near.org/status` +- [ [NearBlocks 浏览器](https://nearblocks.io) ] +- [ [钱包](https://wallet.near.org) ] +- [ [纪元同步](https://near-nodes.io/intro/node-epoch-sync) ] + +## 测试网(Testnet) + +`testnet` 是一个公共网络,也是 `nearcore` 更改在部署到 `mainnet` 之前的最终测试网络。`testnet` 旨在测试 NEAR 平台的所有方面,包括在 `mainnet` 部署之前的账户创建、模拟代币转账、开发工具和智能合约开发,`testnet` 环境与 `mainnet` 行为高度相似。所有 `nearcore` 更改首先作为候选版本部署在测试网上,然后才在 `mainnet` 上发布。众多 `testnet` 验证者验证交易并创建新区块。dApp 开发者在部署到 `mainnet` 之前先在 `testnet` 上部署其应用程序。需要注意的是,`testnet` 有其自己的交易和状态。 + +- 状态:`https://rpc.testnet.near.org/status` +- [ [浏览器](https://testnet.nearblocks.io) ] +- [ [钱包](https://testnet.mynearwallet.com/) ] +- [ [纪元同步](https://near-nodes.io/intro/node-epoch-sync) ] + +## 本地网(Localnet) + +`localnet` 适用于希望独立于公共区块链使用 NEAR 平台的开发者。您需要自行生成节点。`localnet` 为更高级的用例(包括对 `nearcore` 进行更改)提供对账户、经济模型和其他因素的完全控制。对于开发者而言,如果您希望在开发过程中避免泄露工作信息,`localnet` 是正确的选择。 + +更多关于本地开发的信息请参见[此处](https://near-nodes.io/validator/running-a-node) + +--- + + +**有问题?** + + +在 StackOverflow 上提问! + + diff --git a/zh/protocol/network/runtime.mdx b/zh/protocol/network/runtime.mdx new file mode 100644 index 00000000000..c4cb8ede42f --- /dev/null +++ b/zh/protocol/network/runtime.mdx @@ -0,0 +1,45 @@ +--- +title: 运行时(Runtime) +description: "探索 NEAR 协议的运行时系统,包括核心运行时操作、跨合约调用、操作收据和数据收据,以及状态管理。" +--- + +本节包含探索核心运行时、其操作方式以及如何实现跨合约调用的视频内容。 + +## 运行时概述 + +NEAR 运行时的深度代码概述。 + + + +## 运行时操作收据与数据收据 + +NEAR 运行时如何实现跨合约调用的深度代码解析。 + + + +## 运行时状态 + +NEAR 运行时如何操作其状态的深度概述。 + + diff --git a/zh/protocol/network/staking.mdx b/zh/protocol/network/staking.mdx new file mode 100644 index 00000000000..bb5b0227f55 --- /dev/null +++ b/zh/protocol/network/staking.mdx @@ -0,0 +1,254 @@ +--- +title: 验证者质押 +description: "了解如何质押 NEAR、委托给验证者、跟踪奖励以及安全提取已质押的代币。" +--- + +import { TryOutOnLantstool } from "/snippets/try-out-on-lantstool.jsx"; + +NEAR 使用权益证明(PoS),这意味着用户通过将代币委托给活跃节点验证者来选择他们支持的验证者。 + +在本文中,您将找到质押流程的详细说明,包括委托、查看余额和使用 [NEAR CLI](../../tools/cli) 提取。 + + +**合约源码** + +您可以在[此 GitHub 仓库](https://github.com/near/core-contracts/tree/master/staking-pool)中查看质押池智能合约源码。 + + +--- + +## 委托 NEAR 代币 + +在委托之前,您需要选择一个验证者(参与质押的节点)。 + +查看 [NearBlocks](https://nearblocks.io/node-explorer)、[Pikespeak](https://pikespeak.ai/validators/overview) 或 [Near Staking](https://near-staking.com/),寻找拥有良好历史记录、高在线率和合理佣金费率的验证者。 + + + + 如果您愿意,可以使用 [`near-validator`](../../tools/cli#validator-extension) CLI 获取当前验证者列表: + + ```sh + near-validator validators network-config mainnet now + ``` + + + +### 质押代币 + + + + ```sh + near staking delegation deposit-and-stake '100 NEAR' network-config mainnet sign-with-keychain + ``` + + + ```sh + near call deposit_and_stake --useAccount --deposit 100 + ``` + + + + + + + + +下一个纪元后(约 12 小时),您将开始获得质押奖励 + + + +### 已质押余额 +要查看 `` 账户在 `` 池中的已质押余额,请运行以下命令: + + + + ```sh + near staking delegation view-balance network-config mainnet now + ``` + + + ```sh + near view get_account_staked_balance '{"account_id": ""}' + ``` + + + + + + + + + 您可以使用以下 CLI 命令查看质押池的附加信息和余额: + + #### 整个池的总质押余额 + + + ```sh + near view get_total_staked_balance '{}' + ``` + + + + + + + #### 质押池所有者 + + + ```sh + near view get_owner_id '{}' + ``` + + + + + + + #### 当前奖励费率 + + + ```sh + near view get_reward_fee_fraction '{}' + ``` + + + + + + + #### 所有者余额 + + + ```sh + near view get_account_total_balance '{"account_id": "owner"}' + ``` + + + + + + + #### 质押密钥 + + + ```sh + near view get_staking_key '{}' + ``` + + + + + + + + +--- + +## 提取已质押的代币 + +要提取您的已质押代币,您首先需要从验证者处"取消委托"。 + +您的代币将进入 4 个纪元(约 24 小时)的解绑期,之后才能提取。 + +### 取消质押代币 + + + + +```sh +near staking delegation unstake '1 NEAR' network-config mainnet sign-with-keychain +``` + + + +使用 `unstake-all` 命令一次取消质押所有代币: + +```sh +near staking delegation unstake-all network-config mainnet sign-with-keychain +``` + + + + + + + ```sh + near call unstake '{"amount": "100000000000000000000000000"}' --useAccount + ``` + + + 调用 `unstake_all` 方法可一次取消质押所有代币 + + + + + + + + +### 查询未质押余额 + + + + +```sh +near staking delegation view-balance network-config mainnet now +``` + + + + +```sh +near view get_account_unstaked_balance '{"account_id": ""}' +``` + + + + + + + +### 提取代币 + +解绑期结束后,您可以提取未质押的代币: + + + + +```sh +near staking delegation withdraw '1 NEAR' network-config mainnet sign-with-keychain +``` + + + +如果您想提取所有可用代币,可以使用 `withdraw-all` 命令: + +```sh +near staking delegation withdraw-all network-config mainnet sign-with-keychain +``` + + + + + + +```sh +near call withdraw '{"amount": "100000000000000000000000000"}' --useAccount +``` + + + + + + + +--- + +## 工具与资源 + +- 支持质押和管理代币的钱包: + - [生态系统钱包](https://wallet.near.org/) +- 探索验证者和质押池: + - [NearBlocks](https://nearblocks.io/) + - [Pikespeak](https://pikespeak.ai/) + - [NEAR Staking](https://near-staking.com/) diff --git a/zh/protocol/network/token-loss.mdx b/zh/protocol/network/token-loss.mdx new file mode 100644 index 00000000000..cc6fe093fa1 --- /dev/null +++ b/zh/protocol/network/token-loss.mdx @@ -0,0 +1,60 @@ +--- +title: 避免代币丢失 +description: "了解 NEAR 协议中可能导致代币丢失的场景以及如何预防,包括密钥管理、账户删除和智能合约故障。" +--- + +本文档概述了可能导致代币丢失的常见错误以及如何避免这些错误。 + + +注意!丢失代币意味着损失金钱! + + +代币丢失可能发生在多种场景下。这些场景可以归纳为几个相关类别: + +1. 密钥管理不当 +2. 退款至已删除的账户 +3. 批量操作中的函数调用失败 + +--- + +## 密钥管理不当 + +密钥管理不当可能导致代币丢失。通过签发备用密钥来允许恢复已丢失或删除密钥的账户,可以缓解此类场景。 + +### 丢失 `FullAccess` 密钥 + +用户可能丢失账户的 `FullAccess` 密钥对的私钥,且该账户没有其他密钥。 +此时无人能恢复资金,资金将永久锁定在账户中。 + +### 丢失 `FunctionCall` 访问密钥 + +账户可能有其唯一的 `FunctionCall` 访问密钥被删除。 +此时无人能恢复资金,资金将永久锁定在账户中。 + +--- +## 退款至已删除的账户 + +当为某个账户签发退款收据时,如果该账户不再存在,资金将按照当前纪元中的质押比例分配给验证者。 + +### 删除账户时指定了不存在的受益人 + +当您删除一个账户时,必须指定受益人。 +账户删除后,会生成一个转账收据并发送给受益人账户。 +如果受益人账户不存在,则会生成退款收据并发回原始账户。 +由于原始账户已被删除,资金将分配给验证者。 + +### 余额为零的账户在收到退款之前被垃圾回收 + +如果账户 `A` 将其所有资金转移到另一个账户 `B`,而账户 `B` 不存在,则会为账户 `A` 生成退款收据。在此往返期间,账户 `A` 面临被网络垃圾回收活动删除的风险。 +如果账户 `A` 在退款收据到达之前被删除,资金将分配给验证者。 + +--- +## 批量操作中的函数调用失败 + + +在设计智能合约时,您应该始终考虑 NEAR 协议的异步特性。 + + +如果合约函数 `f1` 调用了两个(或更多)其他函数 `f2` 和 `f3`,且其中至少一个函数(`f2` 或 `f3`)失败,则失败函数的代币将被退款,而成功函数的代币将被适当记入。 + +如果批量中的单个失败意味着整个批量失败,那么成功调用的代币在您的用例中可能被视为丢失。 diff --git a/zh/protocol/network/tokens.mdx b/zh/protocol/network/tokens.mdx new file mode 100644 index 00000000000..06a9097e002 --- /dev/null +++ b/zh/protocol/network/tokens.mdx @@ -0,0 +1,31 @@ +--- +title: 代币 +description: "了解 NEAR 的原生代币及其在网络中的作用" +--- + +NEAR 协议有一个原生代币 NEAR(Ⓝ),用于生态系统内的多种用途。本文档概述了 NEAR 代币、其使用场景,以及它在 NEAR 协议中的功能。 + +# NEAR 代币 +这是 NEAR 协议中使用的原生代币。 +它有多种使用场景: +- 通过质押保护网络安全 +- 提供账户单位——NEAR 用于处理交易和存储数据 +- 作为交换媒介 + +### 保护网络安全 +NEAR 协议是一个权益证明(PoS)网络,这意味着抵御各种攻击的能力来自于 NEAR 的质押。 +已质押的 NEAR 代表了维护网络并为 NEAR 上的应用程序和用户处理交易的去中心化服务器基础设施。 +提供此服务的奖励以 NEAR 的形式获得。 + +## 提供账户单位 +NEAR 用于对 NEAR 基础设施上的计算和存储进行定价。 +网络以 NEAR 收取交易费用来处理变更和交易。 + +## 交换媒介 +NEAR 在协议层面随时可用,因此可用于在 NEAR 应用程序和账户之间转移价值。 +这意味着应用程序可以使用 NEAR 对各种功能收费,例如访问数据或其他复杂交易。 +各方也可以轻松地相互交换 NEAR,无需可信第三方来清算和结算交易。 + +有关 NEAR 经济学的深度解析,请访问:[https://near.org/blog/near-protocol-economics](https://near.org/blog/near-protocol-economics) + +有关 NEAR 代币的更多信息,请访问 [NEAR 代币供应与分配](https://near.org/blog/near-token-supply-and-distribution/) 或 [Nomicon](https://nomicon.io)。 diff --git a/zh/protocol/network/validators.mdx b/zh/protocol/network/validators.mdx new file mode 100644 index 00000000000..4adf44440f7 --- /dev/null +++ b/zh/protocol/network/validators.mdx @@ -0,0 +1,67 @@ +--- +title: 验证者 +description: "了解 NEAR 协议验证者、他们在网络安全中的角色、共识机制、验证者经济学,以及如何成为验证者。" +--- + +从根本上说,NEAR 协议是一个去中心化的区块链,运行在一个由独立参与者——即验证者——组成的网络上,这些验证者负责处理交易并保护网络安全。 + +这些验证者必须达成共识,这意味着他们需要就哪些交易有效并应添加到区块链上达成一致。这确保了没有人能够窃取资金、双花代币或操纵系统。 + +NEAR 验证者使用[权益证明(PoS)](https://en.wikipedia.org/wiki/Proof_of_stake),这是一种通过经济激励而非能源密集型计算能力来保护网络的共识机制。与比特币的[工作量证明](https://en.wikipedia.org/wiki/Proof_of_work)系统需要大量电力不同,PoS 使网络在保持强大安全性的同时实现环境可持续性。 + +在权益证明中,用户通过称为质押的过程将其 NEAR 代币委托给特定验证者来表达支持。原则很简单:拥有更多委托代币的验证者被社区认为是值得信赖的网络安全守护者。如果这些验证者中的任何一个被发现进行恶意活动,他们将被踢出网络,并且质押在其上的所有代币将被销毁,使不诚实行为在经济上得不偿失。 + +### 保护网络安全 +验证者有两项主要工作。第一项是验证和执行交易,将它们聚合到构成区块链的区块中。第二项工作是监督其他验证者,确保没有人生产无效区块或创建替代链(例如,以实现双花为目的)。 + +如果验证者被发现行为不端,他们将被"惩罚(slashed)",即他们的质押(或其中一部分)将被销毁。 + +在 NEAR 网络中,试图操控链意味着需要同时控制大多数验证者,以使恶意活动不被标记。然而,这需要将巨额资本置于风险之中,因为不成功的攻击意味着您的质押代币将被销毁。 + +### 验证者经济学 +作为为网络提供服务的回报,验证者每个纪元获得目标数量的 NEAR 奖励。目标值的计算方式为:按年化计算,将达到总供应量的 2.5%。 + +每个纪元内收取的所有交易费用(减去作为合约返利分配的部分)均由系统销毁。无论收取或销毁的费用数量如何,通胀奖励均以相同速率支付给验证者。 + +## 验证者简介 + +[验证者](https://pages.near.org/papers/the-official-near-white-paper/#economics)负责生产和验证区块和分块,确保 NEAR 网络的安全性和完整性。 + +运行验证者节点的硬件要求因质押位置而异。详细规格可在此处找到:[硬件要求](https://near-nodes.io/validator/hardware-validator)。 + +您可以在 [NEAR-STAKING](https://near-staking.com/stats) 等平台上查看当前活跃验证者列表。要成为验证者,所需的最低质押量由第 300 大质押提案决定。如果提案超过 300 个,门槛将由第 300 个提案的质押量设定,前提是它超过 25,500 `$NEAR` 的最低门槛。加入活跃验证者集的当前席位价格在 [NEAR BLOCKS](https://nearblocks.io/node-explorer) 上实时更新。任何质押量大于当前席位价格的验证者节点都可以加入活跃验证者集。 + + +**是否有计划支持 GPU 计算,如果某些验证者节点可以提供的话,还是只使用 CPU?** + +我们不需要 GPU 支持,因为我们是 PoS 链,所需的计算能力非常少。 + +您可以在我们的[验证者快速入门](https://github.com/near/wiki/blob/master/Archive/validators/about.md)和[质押常见问题解答](https://github.com/near/wiki/blob/master/Archive/validators/faq.md)中了解更多关于我们共识策略的信息。 + + +## 区块和分块生产者 +前 100 名验证者负责生产和验证区块,以及生产分块。在正常情况下,每个验证者被分配到单一分片,负责为其生产分块。 + +区块和分块生产者保证最低年化奖励 2.5%。如果网络代币的质押率低于 100%,验证者有潜力获得更高的年化奖励。 + +## 分块验证者 + +[注意] 区块和分块生产者也充当分块验证者。 + +非前 100 名验证者承担分块验证者的角色,该角色对硬件和质押要求较低,使其更易于参与。此角色有助于扩大网络的验证者集,增加获得奖励和加强 NEAR 生态系统安全性的机会。 + +分块验证者不跟踪分片。他们的职责仅专注于验证和背书分块。 + +与区块和分块生产者一样,分块验证者保证最低年化奖励 2.5%。如果网络代币的质押率低于 100%,分块验证者可能获得更高的奖励。有关验证者经济学的更多详情,请查看 [NEAR 经济学解析](https://near.org/blog/near-protocol-economics/)。 + +## 专属验证者文档站点 + +如果您希望进一步了解验证者和节点的相关信息,可以访问[专属验证者文档站点](https://near-nodes.io/)。 + + +**如果开发者编写了一个有漏洞或恶意的 dApp,验证者是否隐含地承担了风险?** + +不。我们已经在协议层面处理了对网络的潜在损害。例如,我们有很多限制器,限制您可以传递给函数调用的数据量或在一次函数调用中可以进行的计算量等。 + +话虽如此,智能合约开发者需要为自己的 dApp 负责,因为没有阶段关卡或审批流程。所有漏洞只能损害智能合约本身。幸运的是,在 NEAR 上更新智能合约非常顺畅,漏洞可以以其他区块链无法实现的方式被更新/修补到账户中。 + diff --git a/zh/protocol/storage/decentralized-storage.mdx b/zh/protocol/storage/decentralized-storage.mdx new file mode 100644 index 00000000000..8f5909b360d --- /dev/null +++ b/zh/protocol/storage/decentralized-storage.mdx @@ -0,0 +1,244 @@ +--- +title: 去中心化存储解决方案 +description: "探索 NEAR 协议应用程序的去中心化存储替代方案,包括 Arweave、Crust 和 IPFS 集成,实现经济高效的数据存储。" +--- + +> 在本文中,您将找到可集成到去中心化应用程序(dApps)中的不同去中心化存储解决方案的简要概述。这将允许您使用比原生 NEAR 存储更经济的替代方案来存储大量数据。 + +- [Arweave](#arweave) +- [Crust](#crust) +- [IPFS](#ipfs) + +--- + +## 链上存储限制 + +在链上存储数据时,务必记住以下几点: + +- 您可以存储无限量的文件,但每 100KB 将花费 1Ⓝ +- 单次上传的数据量限制为 4MB + +例如,如果您想将 NFT 纯粹存储在链上(而不是使用 IPFS 或以下提到的其他去中心化存储解决方案),您将拥有几乎无限的存储空间,但每使用 100KB 存储需要支付 1 `$NEAR`(参见[存储质押](/protocol/storage/storage-staking))。 + +由于 `MAX_GAS` 限制,用户每次合约调用上传的数据量限制为 4MB。单次 `functionCall` 可附加的最大 gas 量为 300TGas。 + +## Arweave + +[Arweave](https://www.arweave.org/) 是一种新型存储,通过可持续且永久的捐赠(协议内持有的代币,得益于通货膨胀和长期存储成本下降而增值)来支持数据。这使用户和开发者能够永久存储数据。 +Arweave 充当集体拥有的硬盘,允许用户无限期地保存有价值的信息、应用程序和历史记录。 + +Arweave 协议将类似 BT 种子的激励矿工群体(拥有大量集体硬盘空间)与需要永久存储数据或托管内容的个人和组织相匹配。这在去中心化网络中实现,所有存储数据都由区块挖矿奖励和[可持续捐赠](https://arwiki.wiki/#/en/storage-endowment)支持,确保数据永久可用。 + + +要了解更多关于 Arweave 的信息,请查看其[挖矿机制](https://arwiki.wiki/#/en/arweave-mining)和[带宽共享系统](https://arwiki.wiki/#/en/karma)。 + + +### 示例实现 + +让我们通过运行一个本地 Arweave 网关类服务器来了解如何在 Arweave 上存储文件。 + +### Arlocal 设置 + +[Arlocal](https://github.com/textury/arlocal) 本质上创建了 Arweave 的模拟版本。可以将其视为在您的计算机上运行以存储信息的本地节点。 + +在本示例中,您需要运行**两个终端**。 + +- 打开第一个终端并运行: + +```bash +npx arlocal +``` + +您应该会看到响应:`arlocal started on port 1984`。 + + +您可以通过使用 `npx arlocal ` 来指定端口。 + + +### NEAR-Arweave 前端 + +[NEAR-Arweave 仓库](https://github.com/near-examples/NEAR-Arweave-Tutorial) 提供了一个简单的前端,允许您使用 arlocal 存储 `.png` 文件。 + +- 现在打开第二个终端,通过运行以下命令克隆前端: + +```bash +git clone https://github.com/near-examples/NEAR-Arweave-Tutorial.git +``` + +- 在项目文件夹中运行以下命令安装依赖项: + +```bash +cd NEAR-Arweave-Tutorial +yarn +``` + +- 接下来,运行以下命令启动应用程序: + +```bash +yarn start +``` + +- 现在您可以通过选择 选择文件 按钮来上传图片: + +![Arweave 步骤 1](/assets/docs/protocol/storage/arweave-1.png) + +- 点击 提交 按钮后,您应该会看到交易 ID 窗口被填充: + +![Arweave 步骤 2](/assets/docs/protocol/storage/arweave-2.png) + + +如果遇到错误,请确保您的 arlocal 节点在**单独的终端**中运行。 + + +### 挖掘您的交易 + +在 Arweave 上,您的交易经历两个阶段:待处理阶段和已确认阶段。要使交易完成并能够检索数据,您的交易必须被确认。 + +- 在浏览器中访问 `http://localhost:1984/mine` 向本地节点发送挖矿请求。 + + +您可能会发现即使不执行此步骤也能检索到数据,但这是因为您运行的是本地节点。 +在处理真实的 Arweave 节点时,您必须等到交易被挖掘和确认。 + + +### 检索图片 + +- 现在您可以在前端步骤 5 中复制粘贴任何列出的 Arweave 交易 ID,从本地节点检索您的文件: + +![Arweave 步骤 3](/assets/docs/protocol/storage/arweave-3.png) + + +使用 Arweave 的实时网络需要购买 ar 代币来支付存储费用。您可以在 [arweave.org](https://www.arweave.org/) 了解更多信息。 + + + +[near-api-js](https://github.com/near/near-api-js) 和 [arweave-js](https://github.com/ArweaveTeam/arweave-js) JavaScript 库允许您自动化这些步骤中的大部分。 + + +--- + +## Crust + +[Crust](https://crust.network) 为元宇宙提供 Web3.0 去中心化存储网络。 +它旨在实现去中心化、隐私和保障的核心价值。 +Crust 支持多种存储层协议,如 IPFS,并向用户提供即时可访问的链上存储功能。 +Crust 的技术栈还能够支持数据操作和计算。 + +Crust 协议与 [IPFS](https://ipfs.io) 协议 100% 兼容,它将有闲置硬盘空间的人与需要永久存储数据或托管内容的人相匹配。 +Crust 基于 Polkadot 生态系统,通过其跨链解决方案支持大多数合约平台,包括 NEAR/Solana/Ethereum/Elrond 等。 + + +要了解更多关于 Crust 的信息,请查看其[去中心化存储市场](https://wiki.crust.network/docs/en/DSM)和[保障权益证明](https://wiki.crust.network/docs/en/GPoS)。 +您也可以从 [Build-101](https://wiki.crust.network/docs/en/build101) 开始。 + + +### 集成示例 + +以下是使用 Crust 和 NEAR 存储文件的简单集成示例。 + +#### 1. 将文件上传到 IPFS + +首先,您需要将文件放入 IPFS。 + + +如果您想了解如何将**文件和文件夹**上传到 IPFS,请参阅[此部分](https://wiki.crust.network/docs/en/buildFileStoringWithGWDemo#1-upload-files-to-ipfs-gateway)。 + + +有 2 种方式将文件上传到 IPFS: + +- 使用本地 IPFS 节点 +- 使用远程 [IPFS W3Authed 网关](https://docs.ipfs.io/concepts/ipfs-gateway/#authenticated-gateways) + + +- 您可以在[此链接](https://github.com/crustio/ipfsscan/blob/main/lib/constans.ts#L29)找到有关 `ipfsW3GW` 端点的更多详情。 +- 您还可以在[此链接](https://github.com/crustio/crust-demo/blob/main/near/src/index.ts#L20-L51)找到如何将文件上传到 IPFS 的代码示例。 + + +#### 2. 下达存储订单 + +接下来,您需要在 Crust 链上发送一个名为"下达存储订单"的交易。 +此交易将通过区块链将您的存储需求分发给每个 Crust IPFS 节点。 +然后,IPFS 节点将开始使用 IPFS 协议拉取您的文件。 + + +- 您可以在[此链接](https://github.com/crustio/crust-apps/blob/master/packages/apps-config/src/endpoints/production.ts#L9)找到有关 `crustChainEndpoint` 的更多信息。 +- 您可以按照[这些说明](https://wiki.crust.network/docs/en/crustAccount#create-an-account-1)创建您自己的账户(`seeds`)。 +- 查看[此链接](https://github.com/crustio/crust-demo/blob/main/near/src/index.ts#L82-L112)了解在 Crust 上下达存储订单的代码示例。 + + +#### 3. 查询订单状态 + +然后,您可以通过调用链上状态(`status{replica_count, storage_duration, ...}`)查询存储订单。 +此调用将返回: + +```json +{ + "file_size": 23710, + "spower": 24895, + "expired_at": 2594488, // 存储持续时间 + "calculated_at": 2488, + "amount": "545.3730 nCRU", + "prepaid": 0, + "reported_replica_count": 1, // 副本数量 + "replicas": [{ + "who": "cTHATJrSgZM2haKfn5e47NSP5Y5sqSCCToxrShtVifD2Nfxv5", + "valid_at": 2140, + "anchor": "0xd9aa29dda8ade9718b38681adaf6f84126531246b40a56c02eff8950bb9a78b7c459721ce976c5c0c9cd4c743cae107e25adc3a85ed7f401c8dde509d96dcba0", + "is_reported": true, + "created_at": 2140 + }] // 谁存储了文件 +} +``` + + +在[此链接](https://github.com/crustio/crust-demo/blob/main/near/src/index.ts#L144-L147)找到查询存储状态的代码示例。 + + +#### 4. 添加文件预付款 + +单次交易(订单)的默认存储时间为 6 个月。 +如果您想延长存储时间,Crust 提供了预付款池,允许您自定义文件的存储时间。 +这个池子允许您存入一些代币,并会自动延长文件的存储时间。 + + +请访问[此链接](https://github.com/crustio/crust-demo/blob/main/near/src/index.ts#L114-L142)了解如何向文件添加预付款代币的代码片段。 + + +--- + +## IPFS + +[星际文件系统](https://ipfs.io/)(IPFS)是一种用于在分布式文件系统中存储和共享数据的协议和点对点网络。IPFS 使用内容寻址在连接所有计算设备的全局命名空间中唯一标识每个文件。 + +### 内容标识符 + +当您向 IPFS 添加文件时,它会被分割成经过密码学哈希处理的较小块,然后获得一个称为内容标识符(CID)的唯一指纹。 + + +CID 充当文件在该时间点存在状态的永久记录。 + + +### 查找 + +当节点查找文件时,它会向对等节点请求文件 CID 所引用的内容。当节点查看或下载文件时,它会缓存一份副本,并在缓存清除之前成为另一个提供者。 + +### 已固定的内容 + +在 IPFS 网络中,每个节点只存储它感兴趣的内容。 +节点可以固定内容以永久保留,或丢弃未使用的内容以节省空间。 + +### 文件版本 + +当您向 IPFS 添加文件的新版本时,由于密码学哈希不同,它将获得新的 CID。 +这意味着对文件的任何更改都不会覆盖原始文件,文件之间的公共块可以重复使用以最小化存储成本。 + +### 命名系统 + +IPFS 提供去中心化命名系统,因此您不需要记住一长串 CID。 +IPFS 可以使用 IPNS 去中心化命名系统找到文件的最新版本,您可以使用 DNSLink 将 CID 映射到人类可读的 DNS 名称。 + +### IPFS 提供商 + +- [Web3.Storage](https://web3.storage/):这是一项简化在 IPFS 和 Filecoin 上构建的服务。Web3.Storage 由 Filecoin 支持,通过 IPFS 提供内容,充分利用每个网络的独特特性。 +- [NFT.Storage](https://nft.storage/):此服务专门用于存储链下 NFT 数据。数据在 IPFS 和 Filecoin 上去中心化存储。数据通过内容寻址的 IPFS URI 引用,可在您的智能合约中使用。 +- [Filebase](https://filebase.com/):一个地理冗余的 IPFS 固定提供商,以自动 3 倍冗余跨多个地理位置固定所有 IPFS 文件,以提高性能、冗余性和可靠性。 diff --git a/zh/protocol/storage/storage-staking.mdx b/zh/protocol/storage/storage-staking.mdx new file mode 100644 index 00000000000..42e4fdf9e8a Binary files /dev/null and b/zh/protocol/storage/storage-staking.mdx differ diff --git a/zh/protocol/transactions/gas.mdx b/zh/protocol/transactions/gas.mdx new file mode 100644 index 00000000000..53908e511a4 --- /dev/null +++ b/zh/protocol/transactions/gas.mdx @@ -0,0 +1,187 @@ +--- +title: Gas(执行费用) +description: "了解 NEAR 的 gas 系统——防止垃圾交易的执行费用,通过燃烧 gas 的 30% 激励开发者,并使用确定性 gas 单位和动态定价。" +--- + +import { GasPrice } from '/snippets/gas-price.jsx'; + +本节解释了 NEAR 协议中 gas 的工作原理,包括如何计算、收取,以及如何用来激励开发者。 + +在每笔交易中,NEAR 网络收取一笔称为 **gas** 的微小费用。这个费用是一种简单的机制,允许我们: + +1. **防止**不良行为者用无用交易**垃圾刷**网络 +2. 在每笔交易中**燃烧**极少量的**代币供应** +3. 通过将合约执行时燃烧 gas 的 30% 分配给合约,**激励开发者** +4. 通过将交易上限设为 `300Tgas`(约 `300ms` 的计算时间)来实现**墙钟时间** + +NEAR 中的 gas 以[**gas 单位**](/protocol/transactions/gas#gas-units)计算,并根据网络的[**gas 价格**](/protocol/transactions/gas#gas-price)使用 `$NEAR` 代币收费。 + + +**您知道吗?** +在 NEAR 中,为交易附加更多 gas **不会**使其更快。操作的 gas 成本是固定的,任何多余的 gas 都会直接退还给用户 + + +--- + +## 理解 Gas 费用 + +对于每笔交易,用户需要提前支付一笔小额 NEAR 费用。此费用使用确定性的 **gas 单位**计算,并使用网络的 **gas 价格**转换为 NEAR 成本。 + +### Gas 单位 + +NEAR 中的每个操作都有固定数量的 **gas 单位**,这意味着相同的操作始终消耗**相同数量的 gas 单位**。 + +Gas 单位的设计使其可以转换为计算资源,`1Tgas` 大约对应 `1ms` 的计算时间。 + +交易最多可以使用 `300Tgas`,意味着处理时间应少于 `300ms`,允许网络大约**每秒**产生一个新区块。 + + +Gas 单位不仅封装了计算/CPU 时间,还包括带宽/网络时间和存储/IO 时间 + + +### Gas 价格 + +为了确定实际的 NEAR 费用,交易中所有操作的成本乘以 **gas 价格**。 + +gas 价格**每个区块重新计算**,基于网络需求,最低价格为 `1Tgas = 0.0001Ⓝ`。 + +如果前一个区块**超过一半满**,价格上涨 1%,否则下降 1%(直到达到底价)。 + + + +您可以通过 [`RPC`](/api/rpc/gas#gas-price) 查询 gas 单位以 `yoctoNEAR` 计价的成本(1Ⓝ = `1e24` yocto)。要转换为每 `NEAR` 的 `Tgas`,可以使用以下公式:`gas_price * 1e12 / 1e24`。 + +当前 1 Tgas 的成本: Ⓝ + + + +### 常见操作的成本 + +知道操作的 gas 单位成本是固定的,我们可以在最低 gas 价格 `1Tgas = 0.0001Ⓝ` 下计算常见操作的成本。 + +| 操作 | TGas | 费用(Ⓝ) | +|-----------------------------|----------------|----------| +| 创建账户 | 0.42 | 0.000042 | +| 转账 NEAR | 0.45 | 0.000045 | +| 添加全访问密钥 | 0.42 | 0.000042 | +| 删除密钥 | 0.41 | 0.000041 | +| 函数调用* | ≤ 300 | ≤ 0.03 | +| 部署 `16`kb 合约 | 2.65 | 0.000265 | +| 部署 `X`kb 合约** | 0.58 + 0.13`X` | | + +_注意,费用以 NEAR 为单位,要获得美元成本,请乘以当前 `$NEAR` 价格_ + + +**函数调用\*** +调用函数的成本取决于函数的复杂性,但在同类函数调用之间保持一致。在下方了解更多信息。 + + + +**部署合约\*\*** +注意,这包含了上传字节并将其写入存储的 gas 成本,但**不包含**在存储中持有它们的成本(约 `1Ⓝ ~ 100kb`)。 + + + + +NEAR 使用基础成本进行[配置](https://github.com/near/nearcore/blob/master/core/primitives/res/runtime_configs/parameters.yaml)。示例: + +```json + transfer_cost: { + send_sir: 115123062500, + send_not_sir: 115123062500, + execution: 115123062500 + }, + deploy_contract_cost: 184765750000, + deploy_contract_cost_per_byte: 64572944 +``` + +这里的 "sir" 代表"发送方即接收方"。是的,这些值都相同,但将来可能会改变。 + +当您请求转账资金时,NEAR 会立即从您的账户中扣除相应的 `send` 金额。然后创建一个[_收据_,这是一种内部记账机制](./transaction-execution)。创建收据有其自己的相关成本: + +```json + action_receipt_creation_config: { + send_sir: 108059500000, + send_not_sir: 108059500000, + execution: 108059500000 + } +``` + +您可以通过使用 [`protocol_config`](/api/rpc/protocol#protocol-config) RPC 端点并搜索 `action_receipt_creation_config` 来查询此值。 + +创建此收据的相应金额也会立即从您的账户中扣除。 + +"转账"操作要到下一个区块才会最终确定。此时,这些操作的 `execution` 金额将从您的账户中扣除。 + +尽管 gas 价格可能在购买时间和执行时间之间发生变化,但自协议版本 78 起,每笔交易的 gas 价格是固定的。 + +```text + ( + transfer_cost.send_not_sir + action_receipt_creation_config.send_not_sir + transfer_cost.execution + action_receipt_creation_config.execution + ) * gas_price +``` + +在协议版本 78 之前,每个区块的 gas 价格用于该高度完成的工作。 + +```text + ( + transfer_cost.send_not_sir + action_receipt_creation_config.send_not_sir + ) * gas_price_at_block_1 + + ( + transfer_cost.execution + action_receipt_creation_config.execution + ) * gas_price_at_block_2 +``` + + + +--- + +## 如何购买 Gas? + +您不需要购买 gas,而是在交易[首次被处理时](./transaction-execution#block-1-the-transaction-arrives),根据操作的 gas 成本和网络的 gas 价格,自动从您账户余额中扣除 gas 费。 + +唯一的例外是当您向合约进行函数调用时。在这种情况下,您需要定义要使用的 gas 单位数量,最多 `300Tgas`。此金额将使用网络的 gas 价格转换为 NEAR,并从您的账户余额中扣除。 + +如果交易最终使用的 gas 少于扣除的金额,差额将简单地**退还到您的账户**。但是,网络会扣除至少 1 Tgas 或最多未使用 gas 的 5% 作为 **gas 退款费**。 + + + +自协议版本 78 起,收据执行结束时未使用的 gas 将收取 gas 退款费。在给予项目适应时间期间,该费用仍为 0。计划将其调整为按 `max(1 Tgas, 0.05 * unspent_gas) * gas_price` 计算的费用。gas 价格取购买时的价格。 + +_但为什么要引入这种费用而不是退还所有 gas?_ + +原因是为函数调用附加过多 gas 会降低网络效率。 + +当交易附加的 gas 远超实际使用量时,分片之间的拥塞控制变得棘手。网络限制每个分片每个分块中可以有多少跨合约调用,以避免在某个分片上产生大量传入收据队列。这个限制将附加的 gas 视为收据在接收分片上造成工作量的上限。附加过多 gas 可能导致此限制过于严格,从而不必要地停滞分片。 + +另一个低效之处来自退款收据——在版本 78 之前,几乎每次函数调用都会创建这些收据。虽然每个收据执行成本相对较低,但总体而言,它们占 NEAR 协议全局流量的很大一部分。 + +要了解更多关于引入此费用的决策,请查看 [NEP-536](https://github.com/near/NEPs/pull/536) + + + + +在其他链中,支付更高的 gas 价格可以让您的交易更快处理。在 NEAR 中,**gas 成本是确定性的**,您**无法通过付费获得优先处理**。为交易附加的任何额外 gas 都会被退还,但会扣除附加不必要 gas 的费用。 + + +--- + +## Gas 作为开发者激励 + +在 NEAR 中,执行合约时燃烧的 gas 费的 30% 归合约账户所有。这对开发者创建和维护有用合约是一种强大的激励。 + +例如,在[这笔交易](https://testnet.nearblocks.io/txns/JD8Bg4u8kaYeaSsGBqkvhSDCEPgXhtwJRBBPKicCEPMs)中,用户调用了 `guestbook.near-examples.testnet` 合约中的一个函数。 + +执行该函数调用总共燃烧了约 0.00032Ⓝ,其中 30% 归合约账户所有。这意味着合约账户收到了 0.000096Ⓝ。 + +请注意,费用来自函数执行期间燃烧的 gas,而不是使用的总 gas。 + +--- + +## 估算调用成本 + +如果您正在开发智能合约,您可能希望估算函数调用将消耗多少 gas。这对于估算函数的限制并避免出现 gas 不足错误非常有用。 + +估算 gas 成本最准确的方法之一是在 `testnet` 上运行您的函数。要准确知道函数中特定部分使用了多少 gas,可以使用我们 SDK 中的 `used_gas` 方法。 + +另一种选择是使用`沙盒测试`(在 [Rust](https://github.com/near/workspaces-rs/tree/main/examples/src) 和 [JavaScript](https://github.com/near/workspaces-js) 中都可用),它可以模拟 NEAR 网络。在那里,您可以在每次函数调用后访问已燃烧的 gas。 diff --git a/zh/protocol/transactions/index.mdx b/zh/protocol/transactions/index.mdx new file mode 100644 index 00000000000..1e86bf69005 --- /dev/null +++ b/zh/protocol/transactions/index.mdx @@ -0,0 +1,14 @@ +--- +title: 交易 +description: "了解用户如何通过由操作组成的交易与 NEAR 交互,这些交易使用私钥签名,并由网络以确定性 gas 成本处理。" +--- + +用户通过创建交易与 NEAR 进行交互。具体来说,用户使用其账户的[私钥](../accounts-contracts/access-keys)签署交易,然后将交易广播并由网络处理。 + +![交易](/assets/docs/welcome-pages/data-lake.png) + +一笔交易由一个或多个 [`操作(Actions)`](./transaction-anatomy)组成,每个操作消耗确定数量的 [gas 单位](./gas)。这些 gas 单位被转换为 NEAR 代币的成本,用户必须支付这些费用才能让交易被处理。 + + +您可以使用区块浏览器来检查 NEAR 网络中的交易 + diff --git a/zh/protocol/transactions/meta-tx.mdx b/zh/protocol/transactions/meta-tx.mdx new file mode 100644 index 00000000000..ef1889caa00 --- /dev/null +++ b/zh/protocol/transactions/meta-tx.mdx @@ -0,0 +1,127 @@ +--- +title: 元交易 +description: "了解 NEAR 上的 NEP-366 元交易,允许用户无需拥有 gas 代币即可执行交易,由中继者承担交易费用。" +--- + +[NEP-366](https://github.com/near/NEPs/pull/366) 为 Near 协议引入了元交易的概念。此功能允许用户在 NEAR 上无需拥有任何 gas 或代币即可执行交易。为了实现这一点,用户在链下构建并签署交易。第三方(中继者)用于承担提交和执行交易的费用。 + +--- + +## 概述 + +![元交易流程图](https://raw.githubusercontent.com/near/NEPs/003e589e6aba24fc70dd91c9cf7ef0007ca50735/neps/assets/nep-0366/NEP-DelegateAction.png) +_图表版权归 NEP 作者 Alexander Fadeev 和 Egor Uleyskiy 所有。_ + +该图展示了元交易的一个示例用例。Alice 持有一定数量的同质化代币 `$FT`。她想将一些转给 John。为此,她需要在名为 `FT` 的账户上调用 `ft_transfer("john", 10)`。 + +问题是,Alice 没有 NEAR 代币。她只有一个由他人资助的 NEAR 账户,她持有私钥。她可以创建一笔签名交易来进行 `ft_transfer("john", 10)` 调用。但验证者节点不会接受它,因为她没有足够的 Near 代币余额来购买 gas。 + +通过元交易,Alice 可以创建一个 `DelegateAction`,它与交易非常相似。它也包含要执行的操作列表和这些操作的单一接收方。她签署 `DelegateAction` 并(在链下)将其转发给中继者。中继者将其包装在一个交易中,由中继者签名,因此由中继者支付 gas 成本。如果内部操作有附加的代币余额,也由中继者支付。 + +在链上,交易中的 `SignedDelegateAction` 在中继者的分片上被转换为包含相同 `SignedDelegateAction` 的操作收据。该收据被转发到 `Alice` 所在的账户,该账户将解包 `SignedDelegateAction` 并验证它是否由 Alice 使用有效的 Nonce 等签署。如果所有检查通过,一个包含内部操作作为主体的新操作收据将发送到 `FT`。在那里,`ft_transfer` 调用最终执行。 + +--- + +## 中继者 + +元交易只能与称为`中继者`的链下服务一起使用。将其视为一个服务器,接受 `SignedDelegateAction`,对其进行一些检查,最终将其作为交易转发到网络。 + + + +想要构建中继者?查看[中继者指南](/web3-apps/tutorials/meta-transactions) + + + +中继者可以选择免费提供其服务,但这从长远来看在财务上不可持续。但他们可以轻松地让用户通过 Near 区块链之外的其他方式付款。通过一些技巧,甚至可以使用 Near 上的同质化代币付款。 + +在上面可视化的示例中,付款使用 $FT 完成。Alice 在向 John 转账的同时,还添加了一个向中继者支付 0.1 $FT 的操作。中继者检查 `SignedDelegateAction` 的内容,只有在包含此付款作为第一个操作时才处理它。这样,中继者将在与 John 相同的交易中获得付款。 + + +**请记住** +向中继者的付款仍然没有保证。可能 Alice 没有足够的 `$FT`,导致转账失败。为了降低风险,中继者应该先检查 Alice 的 `$FT` 余额。 + + +不幸的是,这仍然无法保证在元交易执行时余额足够。如果 Alice 在恰当的时机以某种方式减少了她的 $FT 余额,中继者可能会在没有补偿的情况下浪费 NEAR gas。因此,中继者与其用户之间需要一定程度的信任。 + + + +从技术上讲,终端用户(客户端)创建一个包含构建 `Transaction` 所需数据的 `SignedDelegateAction`,使用其密钥签署 `SignedDelegateAction`,并将其发送给中继者服务。 + +当收到请求时,中继者使用其自己的密钥将 `SignedDelegateAction` 中的字段作为输入来签署一个 `Transaction`,创建一个 `SignedTransaction`。 + +然后通过 RPC 调用将 `SignedTransaction` 发送到网络,结果返回给客户端。`Transaction` 的执行方式使中继者支付 GAS 费,但所有操作的执行方式就像用户自己发送了交易一样。 + + + + +## 为什么使用中继者? + +使用中继者有多种原因: +1. 您的用户是 NEAR 新手,没有任何 gas 来支付交易费用 +2. 您的用户在 NEAR 上有账户,但只有同质化代币余额。他们现在可以使用 FT 支付 gas +3. 作为企业或大型初创公司,您希望将现有用户无缝引导到 NEAR,而无需他们担心 gas 成本和助记词 +4. 作为企业或大型初创公司,您有一个用户群,可能产生大量用户活动峰值,这会使网络拥堵。在这种情况下,中继者充当低优先级交易的队列 +5. 作为承担 gas 费成本的交换条件,中继者运营商可以限制用户花费资产的地方,同时允许用户保管和拥有其资产 +6. 资本效率:如果没有中继者,而您的业务有 100 万用户,则必须为他们分配 0.25 NEAR 来覆盖 gas 成本,总计 25 万 NEAR。然而,只有约 10% 的用户会实际使用全部配额,这 25 万 NEAR 中的大部分将闲置。因此,使用中继者,您可以为您的用户分配 5 万 NEAR 作为全局资金池,可以根据需要随时补充。 + + + +--- + +## 限制 + +### 单一接收方 + +与普通交易一样,元交易只能有一个接收方。之后可以链接更多收据。但关键是,没有原子性保证,也没有回滚机制。 + +### 账户必须已初始化 + +任何交易,包括元交易,都必须使用 NONCE 来避免重放攻击。NONCE 必须由 Alice 选择,并与链上存储的 NONCE 进行比较。此 NONCE 存储在创建账户时初始化的访问密钥信息中。 + +--- + +## 对元交易中操作的约束 + +一个交易只允许包含一个单一的委托操作。不允许嵌套委托操作,也不允许同一收据中相邻的委托操作。 + +--- + +## 元交易的 Gas 成本 + +元交易挑战了传统的操作收费方式。 +假设 Alice 使用中继者以 Bob 作为接收方执行操作。 + +1. 中继者购买所有内部操作的 gas,加上包装它们的委托操作的 gas。 +2. 从中继者向 Alice 分片发送内部操作和委托操作的成本将立即燃烧。`relayer == Alice` 的条件决定采用哪种操作 `SEND` 成本(`sir` 或 `not_sir`)。称此为 `SEND(1)`。 +3. 在 Alice 的分片上,委托操作被执行,因此其 `EXEC` gas 成本被燃烧。Alice 将内部操作发送到 Bob 的分片。因此,再次燃烧 `SEND` 费。这次基于 `Alice == Bob` 来确定 `sir` 或 `not_sir`。称此为 `SEND(2)`。 +4. 在 Bob 的分片上,所有内部操作被执行,其 `EXEC` 成本被燃烧。 + +这些步骤中的每一步都应该是合理的,不会太令人惊讶。但结果是,在中继者分片上隐含支付的成本是所有内部操作的 `SEND(1)` + `SEND(2)` + `EXEC` 以及委托操作的 `SEND(1)` + `EXEC`。这可能令人惊讶,但希望通过这个解释现在能明白了! + +--- + +## 元交易中的 Gas 退款 + +Gas 退款收据的工作方式与普通交易完全相同。在每个步骤中,计算悲观 gas 价格与该高度实际 gas 价格之间的差额并退款。在最后一步结束时,额外退还所有剩余 gas,以原始购买价格退款。Gas 退款归原始交易的签名者所有,在本例中为中继者。这是公平的,因为中继者也为此付款。 + +--- + +## 元交易中的余额退款 + +与 gas 退款不同,协议将余额退款发送给收据的前置方(即发送方)。这是合理的,因为我们将附加的余额存入接收方,接收方必须明确将新余额重新附加到其可能产生的新收据中。 + +在元交易的世界中,这一假设也受到了挑战。如果内部操作需要附加余额(例如转账操作),则此余额来自中继者。 + +中继者在提交元交易之前可以看到成本,并同意支付,所以到目前为止没有问题。但如果交易在 Bob 的分片上执行失败会怎样?此时,前置方是 `Alice`,因此她收到了退款的代币余额,而不是中继者。这是中继者实现必须注意的问题,因为 Alice 有经济动机提交附加高余额但在 Bob 的分片上失败的元交易。 + +--- + +## 元交易中的函数调用访问密钥 + +[函数调用访问密钥](../accounts-contracts/access-keys#function-call-keys)仅限于为特定合约上的特定方法签署交易。 + +此外,它们可以有一个配额,限制可用于 GAS 费的代币数量。但是,可以通过使用元交易来绕过此限制。 + +当 `DelegateAction` 在网络中被处理时,将检查发送方的访问密钥是否针对接收方和被调用的方法。如果访问密钥被允许进行调用,则执行该操作。 + +但是,不检查配额,因为所有成本已由中继者承担。因此,即使配额不足以覆盖成本,该操作也将被执行。 diff --git a/zh/protocol/transactions/transaction-anatomy.mdx b/zh/protocol/transactions/transaction-anatomy.mdx new file mode 100644 index 00000000000..2e14b0c5834 --- /dev/null +++ b/zh/protocol/transactions/transaction-anatomy.mdx @@ -0,0 +1,65 @@ +--- +title: 交易解析 +description: "了解 NEAR 协议交易的结构和组成部分,包括签名者、接收方、操作和交易验证字段。" +--- + +本节解释了 NEAR 协议中交易的解析,描述其结构、组成部分以及可以在交易中执行的操作。 + +交易是用户向网络发出的执行一组操作的请求。创建交易时,用户必须指定以下字段: + +- `Signer`(签名者):签署交易的账户 +- `Actions`(操作):要执行的操作集(见下文) +- `Receiver`(接收方):在其上执行操作的账户 + +此外,交易还具有以下字段以确保其完整性和有效性: + +- `PublicKey`(公钥):用于签署交易的公钥(以便网络可以验证签名) +- `Nonce`:由`签名者`发送的每笔交易递增的数字 +- `BlockHash`(区块哈希):最近一个区块的哈希值,用于限制交易的时效性 + +用户创建交易并用其私钥签署。然后,**交易及其签名**一起广播到网络,在那里被验证和处理。 + + +每笔交易只有一个 `Signer` 和一个 `Receiver`,但可以有多个 `Actions`。 + + + +**关于 nonce 值** +- 添加密钥时,`nonce` 会被自动分配——具体来说,其值为 `区块高度 * 10^6`——因此 `ADD_KEY` 操作中的值将被忽略 +- 只有当 `nonce` 值在以下范围内时,交易才被接受: + - `[(current_nonce_of_access_key + 1) .. (block_height * 10^6)]` +- 交易被接受后,访问密钥的 `nonce` 将设置为所包含交易的 `nonce` 值 + + +--- + +## 操作(Actions) + +每笔交易可以有**一个或多个** [`操作`](https://nomicon.io/RuntimeSpec/Actions.html),这些是要在 `Receiver` 账户上执行的实际操作。可以执行的操作类型有多种: + +1. [`FunctionCall`](https://nomicon.io/RuntimeSpec/Actions.html#functioncallaction):调用合约上的函数(可选择附加 NEAR) +2. [`Transfer`](https://nomicon.io/RuntimeSpec/Actions.html#transferaction):向另一个账户转账代币 +3. [`DeployContract`](https://nomicon.io/RuntimeSpec/Actions.html#deploycontractaction):在账户中部署合约 +4. [`DeployGlobalContract`](https://nomicon.io/RuntimeSpec/Actions.html#deployglobalcontractaction):注册一个由其合约哈希或账户 ID 引用的全局合约 +5. [`UseGlobalContract`](https://nomicon.io/RuntimeSpec/Actions.html#useglobalcontractaction):通过合约哈希或账户 ID 使用已注册的全局合约 +6. [`CreateAccount`](https://nomicon.io/RuntimeSpec/Actions.html#createaccountaction):创建一个新的子账户(例如 `ana.near` 可以创建 `sub.ana.near`) +7. [`DeleteAccount`](https://nomicon.io/RuntimeSpec/Actions.html#deleteaccountaction):删除账户(将剩余余额转给受益人) +8. [`AddKey`](https://nomicon.io/RuntimeSpec/Actions.html#addkeyaction):向账户添加新密钥(`FullAccess` 或 `FunctionCall` 访问权限) +9. [`DeleteKey`](https://nomicon.io/RuntimeSpec/Actions.html#deletekeyaction):从账户中删除现有密钥 +10. [`DelegateActions`](https://nomicon.io/RuntimeSpec/Actions.html#delegate-actions):创建元交易 +11. [`Stake`](https://nomicon.io/RuntimeSpec/Actions.html#stakeaction):表达成为网络验证者意愿的特殊操作 +12. [`DeterministicStateInit`](https://nomicon.io/RuntimeSpec/Actions.html#deterministicstateinitaction):创建确定性账户(一种高级隐式账户)的特殊操作 + +例如,`bob.near` 可以在一笔交易中捆绑以下操作: +- 创建账户 `contract.bob.near` +- 向 `contract.bob.near` 转账 5 NEAR +- 在 `contract.bob.near` 中部署合约 +- 调用 `contract.bob.near` 中的 `init` 函数 + +`操作`按**交易中指定的顺序**执行。如果任何**操作失败**,交易将被丢弃,所有操作均不生效。 + + +**单一接收方** + +注意,所有操作都在同一账户上执行。**无法**在单笔交易中对多个账户执行操作,因为交易只能有**一个接收方** + diff --git a/zh/protocol/transactions/transaction-execution.mdx b/zh/protocol/transactions/transaction-execution.mdx new file mode 100644 index 00000000000..2bfe836215f --- /dev/null +++ b/zh/protocol/transactions/transaction-execution.mdx @@ -0,0 +1,148 @@ +--- +title: 交易的生命周期 +description: "了解 NEAR 交易的执行和最终确认过程" +--- + +`交易`由用户构建,用于表达在网络中执行操作的意图。进入网络后,交易被转换为`收据`,收据是网络节点之间交换的消息。 + +在本页中,我们将探讨交易的生命周期,从创建到最终状态。 + + +**推荐阅读** + +要深入了解交易路由,我们建议阅读 [nearcore 文档](https://near.github.io/nearcore/architecture/how/tx_routing.html) + + +--- + +## 收据与最终确认 + +让我们以复杂交易为例,了解网络如何以区块为**时间单位**处理它。 + +#### 区块 #1:交易到达 +交易到达后,网络需要一个区块来验证它,并将其转换为包含所有待执行[操作](./transaction-anatomy)的单个`收据`。 + +在创建`收据`时,`签名者`的账户将被扣除 `$NEAR` 用于**支付 gas** 和**任何附加的 NEAR**。 + +如果`签名者`和`接收方`是同一账户——例如`签名者`在添加密钥——`收据`将在第一个区块中立即处理,交易被视为最终确认。 + +#### 区块 #2:收据被处理 +如果`签名者`和`接收方`不同——例如`签名者`向`接收方`转账 NEAR——`收据`将在第二个区块中处理。 + +在此过程中,`FunctionCall` 可能产生**跨合约调用**,创建一个或多个新的`收据`。 + +#### 区块 #3...:函数调用 +从函数调用创建的每个`收据`需要额外一个区块来处理。注意,如果这些`收据`是`FunctionCall`,它们可能会产生新的`收据`,如此循环。 + +#### 最终区块:Gas 退款 +最终一个`收据`在新区块中处理,退还用户多付的 gas。 + +### 等待交易 + +使用我们的库(或直接联系 RPC)时,您可以使用 `wait_until` 参数指定等待交易处理的时长。 + +提交交易后,首先由 RPC 节点验证。如果交易未通过结构检查,将立即被拒绝。否则,以下两件事独立进行: + +- **执行**:由交易产生的收据在各分片间执行 +- **最终确认**:包含交易及其收据的区块经过共识最终确认 + +这两个维度组合形成以下 `wait_until` 等级: + +| `wait_until` | 收据已执行? | 交易区块已最终确认? | 响应中包含收据结果? | +|---|---|---|---| +| `None` | — | — | 否 | +| `Included` | — | — | 否 | +| `ExecutedOptimistic` | 是(非退款) | 否 | 是 | +| `IncludedFinal` | 不一定 | 是 | 部分 | +| `Executed` | 是(非退款) | 是 | 是 | +| `Final` | 是(全部,包括退款) | 是(所有区块) | 是 | + +注意,`ExecutedOptimistic` 和 `IncludedFinal` 之间没有先后顺序——它们代表不同维度上的进度。`ExecutedOptimistic` 保证所有非退款收据的结果可用,但区块尚未最终确认。`IncludedFinal` 保证交易区块已最终确认,但某些收据可能仍在处理中,因此您可能只能获得部分收据结果。 + +#### 退款收据 + +当收据执行后有剩余 gas 时,NEAR 会生成退款收据,将未使用的付款退还给发送方。这些收据始终成功且不影响应用逻辑,因此 `ExecutedOptimistic` 和 `Executed` 不等待它们——只有 `Final` 等待。 + + +大多数交易只会产生一个处理操作的收据和一个退还 gas 的收据,在 1-3 个区块内(约 1-3 秒)最终确认: + +- [一个区块](https://testnet.nearblocks.io/txns/8MAvH96aMfDxPb3kVDrgj8nvJS7CAXP1GgtiivKAMGkF#execution):如果`签名者`和`接收方`相同——例如添加密钥时 +- [三个区块](https://testnet.nearblocks.io/txns/B7gxJNxav1A9WhWvaNWYLrSTub1Mkfj3tAudoASVM5tG#):如果`签名者`和`接收方`不同,因为第一个区块创建`收据`,最后一个退还 gas + +函数调用可能需要更长时间,因为它们可能产生多个收据。网络拥堵也可能增加处理收据和交易的时间。 + + +--- + +## 交易状态 + +随着`交易`的`收据`被处理,它们会获得一个状态: + +- `成功(Success)`:收据中的操作成功执行 +- `失败(Failed)`:收据中的某个操作失败 +- `未知(Unknown)`:收据不被网络识别 + +如果`收据`中的某个操作失败,该`收据`中的所有操作都将被回滚。注意,这里讨论的是`收据`的状态,而不是`交易`的状态。 + +交易的状态由其第一个收据决定,该收据包含所有操作。如果第一个收据中的任何操作失败,交易将被标记为失败。 + +注意,可能出现交易被标记为成功,但某些收据失败的情况。这发生在 `FunctionCall` 成功产生新收据,但随后的函数调用失败时。在这种情况下,交易被标记为成功,因为原始函数调用是成功的。 + +有关详细信息,请参见以下示例。 + + + +#### 示例:带转账的交易 + +1. `bob.near` 创建一笔交易,向 `alice.near` 转账 10 NEAR +2. 交易被转换为收据 +3. 由于 `bob.near` 余额不足,转换失败 +4. 交易被标记为失败 ⛔ + +#### 示例:部署合约 + +1. `bob.near` 创建一笔交易,内容为: + - 创建账户 `contract.bob.near` + - 向 `contract.bob.near` 转账 5 NEAR + - 在 `contract.bob.near` 中部署合约 +2. 交易被转换为一个收据 +3. 账户被创建,转账完成,合约被部署 +4. 交易被标记为成功 ✅ + +#### 示例:部署合约失败 + +1. `bob.near` 创建一笔交易,内容为: + - 创建账户 `contract.bob.near` + - 向 `contract.bob.near` 转账 5 NEAR + - 在 `contract.bob.near` 中部署合约 +2. 交易被转换为一个收据 +3. 账户被创建,但由于 `bob.near` 余额不足,转账失败 +4. 整个过程被回滚(即不创建任何账户) +5. 交易被标记为失败 ⛔ + +#### 示例:调用函数 +1. `bob.near` 创建一笔交易,调用 `contract.near` 中的 `cross-call` 函数 +2. 交易被转换为一个收据 +3. `cross-call` 函数创建一个 Promise,调用 `external.near` 中的 `external-call` 函数 +4. 该函数成功完成,交易被标记为成功 ✅ +5. 创建一个新收据,调用 `external.near` 中的 `external-call` 函数 +5. `external-call` 函数失败 +6. 原始交易仍然被标记为成功 ✅,因为第一个收据成功了 + + + + +您可以使用 [NearBlocks 浏览器](https://nearblocks.io/)检查交易状态 + + +--- + +## Nonce 值 + +`nonce` 是一个对交易`签名者`发送的每笔交易递增的数字。 +对于有效的交易,`nonce` 值应遵循以下规则: + +- 添加密钥时,`nonce` 会被自动分配——具体来说,其值为 `区块高度 * 10^6`——因此 `ADD_KEY` 操作中的值将被忽略 +- 只有当 `nonce` 值在以下范围内时,交易才被接受: + - `[(current_nonce_of_access_key + 1) .. (block_height * 10^6)]` +- 交易被接受后,访问密钥的 `nonce` 将设置为所包含交易的 `nonce` 值 diff --git a/zh/smart-contracts/anatomy/actions.mdx b/zh/smart-contracts/anatomy/actions.mdx new file mode 100644 index 00000000000..692eb17e757 --- /dev/null +++ b/zh/smart-contracts/anatomy/actions.mdx @@ -0,0 +1,955 @@ +--- +title: 转账与操作 +description: "了解合约如何进行转账、调用其他合约等操作" +--- +本页面描述了智能合约在 NEAR 上可以执行的不同类型操作,例如转移 NEAR、调用其他合约、创建子账户以及部署合约。还介绍了如何向账户添加访问密钥。 + +智能合约可以执行特定的 `Actions`,例如转移 NEAR 或调用其他合约。 + +`Actions` 的一个重要特性是,当它们作用于同一合约时,可以批量处理。**批量操作**作为一个单元执行:它们在同一个 [receipt](/protocol/transactions/transaction-execution#receipts--finality) 中执行,如果**任何一个失败**,则**所有操作都将被回滚**。 + + +`Actions` 只能在它们作用于**同一合约**时批量处理。您可以批量调用合约上的两个方法,但**不能**调用不同合约上的两个方法。 + + +--- + +## 转移 NEAR Ⓝ + +您可以从合约向网络上的任何其他账户发送 `$NEAR`。转移 `$NEAR` 的 Gas 成本是固定的,基于协议的创世配置。目前,费用约为 `~0.45 TGas`。 + + + + +```rust + use near_sdk::{near, AccountId, Promise, NearToken}; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + #[near] + impl Contract { + pub fn transfer(&self, to: AccountId, amount: NearToken){ + Promise::new(to).transfer(amount); + } + } +``` + + + + + +```js + import { NearBindgen, NearPromise, call } from 'near-sdk-js' + import { AccountId } from 'near-sdk-js/lib/types' + + @NearBindgen({}) + class Contract{ + @call({}) + transfer({ to, amount }: { to: AccountId, amount: bigint }) { + return NearPromise.new(to).transfer(amount); + } + } +``` + + + + + +```python +from near_sdk_py import call, Contract +from near_sdk_py.promises import Promise + +class ExampleContract(Contract): + @call + def transfer(self, to, amount): + """向另一个账户转移 NEAR""" + # 创建一个转移 NEAR 的 Promise + return Promise.create_batch(to).transfer(amount) +``` + + + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type TransferTokenInput struct { + To string `json:"to"` + Amount string `json:"amount"` +} + +// @contract:payable min_deposit=1NEAR +func (c *Contract) ExampleTransferToken(input TransferTokenInput) error { + amount, err := types.U128FromString(input.Amount) + if err != nil { + return err + } + + promise.CreateBatch(input.To). + Transfer(amount) + + return nil +} +``` + + + + + + +**为什么没有回调?** +转账失败的唯一情况是接收方账户**不存在**。 + + + +请记住,您的余额用于支付合约的存储费用。发送资金时,请确保始终留有足够的余额来支付未来的存储需求。 + + +--- + +## 函数调用 + +您的智能合约可以调用另一个合约中的方法。在下面的代码片段中,我们调用已部署的 [Hello NEAR](../quickstart) 合约中的方法,并在回调中检查一切是否正常。 + + + + +```rust + use near_sdk::{near, env, log, Promise, Gas, PromiseError}; + use serde_json::json; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + const HELLO_NEAR: &str = "hello-nearverse.testnet"; + const NO_DEPOSIT: u128 = 0; + const CALL_GAS: Gas = Gas(5_000_000_000_000); + + #[near] + impl Contract { + pub fn call_method(&self){ + let args = json!({ "message": "howdy".to_string() }) + .to_string().into_bytes().to_vec(); + + Promise::new(HELLO_NEAR.parse().unwrap()) + .function_call("set_greeting".to_string(), args, NO_DEPOSIT, CALL_GAS) + .then( + Promise::new(env::current_account_id()) + .function_call("callback".to_string(), Vec::new(), NO_DEPOSIT, CALL_GAS) + ); + } + + pub fn callback(&self, #[callback_result] result: Result<(), PromiseError>){ + if result.is_err(){ + log!("Something went wrong") + }else{ + log!("Message changed") + } + } + } +``` + + + + + +```js + import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js' + import { AccountId } from 'near-sdk-js/lib/types' + + const HELLO_NEAR: AccountId = "hello-nearverse.testnet"; + const NO_DEPOSIT: bigint = BigInt(0); + const CALL_GAS: bigint = BigInt("10000000000000"); + + @NearBindgen({}) + class Contract { + @call({}) + call_method({}): NearPromise { + const args = bytes(JSON.stringify({ message: "howdy" })) + + return NearPromise.new(HELLO_NEAR) + .functionCall("set_greeting", args, NO_DEPOSIT, CALL_GAS) + .then( + NearPromise.new(near.currentAccountId()) + .functionCall("callback", bytes(JSON.stringify({})), NO_DEPOSIT, CALL_GAS) + ) + .asReturn() + } + + @call({privateFunction: true}) + callback({}): boolean { + let result, success; + + try{ result = near.promiseResult(0); success = true } + catch{ result = undefined; success = false } + + if (success) { + near.log(`Success!`) + return true + } else { + near.log("Promise failed...") + return false + } + } + } +``` + + + + + +```python +from near_sdk_py import call, callback, Contract +from near_sdk_py.promises import CrossContract, Promise, PromiseResult +from near_sdk_py.constants import ONE_TGAS + +# 常量 +HELLO_NEAR = "hello-nearverse.testnet" +NO_DEPOSIT = 0 +CALL_GAS = 10 * ONE_TGAS + +class ExampleContract(Contract): + @call + def call_method(self): + """调用外部合约方法并附带回调""" + # 创建合约引用 + hello = CrossContract(HELLO_NEAR) + + # 调用外部合约并使用回调处理结果 + return hello.call("set_greeting", {"message": "howdy"}).then("callback") + + @callback + def callback(self, result: PromiseResult): + """处理外部合约调用的结果""" + # @callback 装饰器自动处理成功/失败检查 + if not result.success: + # 远程调用失败 + self.log_error("Promise failed...") + return False + + # 远程调用成功 + self.log_info("Success!") + return True +``` + + + + + +```go +package main + +import ( + "strconv" + + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type MessageInput struct { + Message string `json:"message"` +} + +// @contract:payable min_deposit=0.00001NEAR +func (c *Contract) ExampleFunctionCall() { + gas := uint64(types.ONE_TERA_GAS * 10) + accountId := "hello-nearverse.testnet" + args := map[string]string{ + "message": "howdy", + } + promise.NewCrossContract(accountId). + Gas(gas). + Call("set_greeting", args). + Then("example_function_call_callback", args) +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleFunctionCallCallback(input MessageInput, result promise.PromiseResult) MessageInput { + env.LogString("Executing callback") + env.LogString("Input Message : " + input.Message) + + if result.Success { + env.LogString("Cross-contract call executed successfully") + env.LogString("Promise Result Status --> " + strconv.FormatInt(int64(result.StatusCode), 10)) + if len(result.Data) > 0 { + env.LogString("Batch call data: " + string(result.Data)) + } + } else { + env.LogString("Cross-contract call failed") + } + return input +} + +``` + + + + + +上面显示的代码片段是调用其他方法的低级方式。我们建议按照[跨合约调用章节](/smart-contracts/anatomy/crosscontract)中的说明调用其他合约。 + + +--- + +## 创建子账户 +您的合约可以创建自身的直接子账户,例如,`user.near` 可以创建 `sub.user.near`。 + +账户对其子账户**没有控制权**,因为子账户有自己的密钥。 + +子账户只是用于组织账户(例如 `dao.project.near`、`token.project.near`)。 + + + + +```rust + use near_sdk::{near, env, Promise, NearToken}; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + const MIN_STORAGE: NearToken = NearToken::from_millinear(1); //0.001Ⓝ + + #[near] + impl Contract { + pub fn create(&mut self, prefix: String) { + let account_id = prefix + "." + &env::current_account_id().to_string(); + Promise::new(account_id.parse().unwrap()) + .create_account() + .transfer(MIN_STORAGE); + } + } +``` + + + + + +```js + import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' + + const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ + + @NearBindgen({}) + class Contract { + @call({payableFunction:true}) + create({prefix}:{prefix: String}) { + const account_id = `${prefix}.${near.currentAccountId()}` + + NearPromise.new(account_id) + .createAccount() + .transfer(MIN_STORAGE) + } + } +``` + + + + + +```python +from near_sdk_py import call, Contract +from near_sdk_py.promises import Promise +from near_sdk_py.constants import ONE_NEAR + +# 存储所需的最小金额 +MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ + +class ExampleContract(Contract): + @call + def create(self, prefix): + """创建子账户""" + # 生成新账户 ID + account_id = f"{prefix}.{self.current_account_id}" + + # 创建新账户并转移一些 NEAR 用于存储 + return Promise.create_batch(account_id)\ + .create_account()\ + .transfer(MIN_STORAGE) +``` + + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +// @contract:payable min_deposit=0.001NEAR +func (c *Contract) ExampleCreateSubaccount(prefix string) { + currentAccountId, err := env.GetCurrentAccountId() + if err != nil { + env.PanicStr("Failed to get current account") + } + + subaccountId := prefix + "." + currentAccountId + + amount, err := types.U128FromString("1000000000000000000000") //0.001Ⓝ + if err != nil { + env.PanicStr("Bad amount format") + } + + promise.CreateBatch(subaccountId). + CreateAccount(). + Transfer(amount) +} +``` + + + + + + 请注意,在代码片段中,我们正在向新账户转移一些资金用于存储 + + + + +当您从合约内部创建账户时,默认情况下没有密钥。如果您没有明确向其[添加密钥](#add-keys)或在创建时[部署合约](#deploy-a-contract),则该账户将被[锁定](../../protocol/accounts-contracts/access-keys#locked-accounts)。 + + + +
+ +#### 创建 `.testnet` / `.near` 账户 + +账户只能创建自身的直接子账户。 + +如果您的合约想要创建 `.mainnet` 或 `.testnet` 账户,则需要[调用](#function-call) `near` 或 `testnet` 根合约的 `create_account` 方法。 + + + + +```rust + use near_sdk::{near, Promise, Gas, NearToken }; + use serde_json::json; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + const CALL_GAS: Gas = Gas::from_gas(28_000_000_000_000); + const MIN_STORAGE: NearToken = NearToken::from_yoctonear(1_820_000_000_000_000_000_000); //0.00182Ⓝ + + #[near] + impl Contract { + pub fn create_account(&mut self, account_id: String, public_key: String){ + let args = json!({ + "new_account_id": account_id, + "new_public_key": public_key, + }).to_string().into_bytes().to_vec(); + + // 使用 "near" 创建主网账户 + Promise::new("testnet".parse().unwrap()) + .function_call("create_account".to_string(), args, MIN_STORAGE, CALL_GAS); + } + } +``` + + + + + +```js + import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js' + + const MIN_STORAGE: bigint = BigInt("1820000000000000000000"); //0.00182Ⓝ + const CALL_GAS: bigint = BigInt("28000000000000"); + + @NearBindgen({}) + class Contract { + @call({}) + create_account({account_id, public_key}:{account_id: String, public_key: String}) { + const args = bytes(JSON.stringify({ + "new_account_id": account_id, + "new_public_key": public_key + })) + + NearPromise.new("testnet") + .functionCall("create_account", args, MIN_STORAGE, CALL_GAS); + } + } +``` + + + + + +```python +from near_sdk_py import call, Contract +from near_sdk_py.promises import Promise +from near_sdk_py.constants import ONE_NEAR, ONE_TGAS + +# 常量 +MIN_STORAGE = int(1.82 * ONE_NEAR / 1000) # 0.00182Ⓝ +CALL_GAS = 28 * ONE_TGAS + +class ExampleContract(Contract): + @call + def create_account(self, account_id, public_key): + """通过调用 testnet 合约创建测试网账户""" + # 创建 create_account 方法的参数 + args = { + "new_account_id": account_id, + "new_public_key": public_key + } + + # 调用 testnet 合约创建账户 + return Promise.create_batch("testnet")\ + .function_call( + "create_account", + args, + MIN_STORAGE, + CALL_GAS + ) +``` + + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type CreateAccountInput struct { + AccountId string `json:"account_id"` + PublicKey string `json:"public_key"` +} + +// @contract:payable min_deposit=0.002NEAR +func (c *Contract) ExampleCreateAccount(args CreateAccountInput) { + amount, _ := types.U128FromString("2000000000000000000000") // 0.002 NEAR + gas := uint64(200 * types.ONE_TERA_GAS) + + //publicKey (base58) - 4omJwNS1WbniWtbPkLYBrFwN3YLeffXCkpvriYgeLhst (为测试生成您自己的密钥) + //accountId - nearsdkdocs1.testnet (为测试填写您自己的) + createArgs := map[string]string{ + "new_account_id": args.AccountId, + "new_public_key": args.PublicKey, + } + + promise.CreateBatch("testnet"). + FunctionCall("create_account", createArgs, amount, gas) +} +``` + + + + +--- + +## 部署合约 + +创建账户时,您还可以批量执行将合约部署到该账户的操作。请注意,为此您需要在合约中预加载要部署的字节码。 + + + + +```rust + use near_sdk::{near, env, Promise, NearToken}; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + const MIN_STORAGE: NearToken = NearToken::from_millinear(1100); //1.1Ⓝ + const HELLO_CODE: &[u8] = include_bytes!("./hello.wasm"); + + #[near] + impl Contract { + pub fn create_hello(&self, prefix: String){ + let account_id = prefix + "." + &env::current_account_id().to_string(); + Promise::new(account_id.parse().unwrap()) + .create_account() + .transfer(MIN_STORAGE) + .deploy_contract(HELLO_CODE.to_vec()); + } + } +``` + + + + + +```python +from near_sdk_py import call, Context, Contract +from near_sdk_py.promises import Promise +from near_sdk_py.constants import ONE_NEAR + +class ExampleContract(Contract): + @call + def deploy_contract(self, prefix): + """创建账户并在其上部署合约""" + # 这需要在 Python 中加载合约字节码 + # 仅用于示例目的加载合约字节码 + # 在实际实现中,您需要从存储中读取或包含它 + contract_bytes = b'...' # 这应该是实际的 WASM 字节码 + + MIN_STORAGE = 1.1 * ONE_NEAR # 1.1Ⓝ + + # 生成新账户 ID + account_id = f"{prefix}.{self.current_account_id}" + + # 批量操作 + return Promise.create_batch(account_id)\ + .create_account()\ + .transfer(MIN_STORAGE)\ + .deploy_contract(contract_bytes) +``` + + + + +```go +package main + +import ( + _ "embed" + + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +//go:embed status_message_go.wasm +var contractWasm []byte + +// @contract:state +type Contract struct{} + +// @contract:payable min_deposit=1.1NEAR +func (c *Contract) ExampleDeployContract(prefix string) { + currentAccountId, _ := env.GetCurrentAccountId() + subaccountId := prefix + "." + currentAccountId + amount, _ := types.U128FromString("1100000000000000000000000") // 1.1Ⓝ + + promise.CreateBatch(subaccountId). + CreateAccount(). + Transfer(amount). + DeployContract(contractWasm) +} +``` + + + + + +如果部署了合约的账户**没有**任何访问密钥,这被称为锁定合约。当账户被锁定时,它无法签署交易,因此操作**只能**从**合约代码内部**执行。 + + +--- + +## 添加密钥 + +当您使用操作创建新账户时,创建的账户默认没有任何[访问密钥](../../protocol/accounts-contracts/access-keys),这意味着它**无法签署交易**(例如更新其合约、删除自身、转移资金)。 + +有两个选项用于向账户添加密钥: +1. `add_access_key`:添加只能调用指定合约上特定方法的密钥。 +2. `add_full_access_key`:添加对账户具有完全访问权限的密钥。 + +
+ + + + +```rust + use near_sdk::{near, env, Promise, NearToken, PublicKey}; + + #[near(serializers = [json, borsh])] + #[derive(Default)] + pub struct Contract { } + + const MIN_STORAGE: NearToken = NearToken::from_millinear(1100); //1.1Ⓝ + const HELLO_CODE: &[u8] = include_bytes!("./hello.wasm"); + + #[near] + impl Contract { + pub fn create_hello(&self, prefix: String, public_key: PublicKey){ + let account_id = prefix + "." + &env::current_account_id().to_string(); + Promise::new(account_id.parse().unwrap()) + .create_account() + .transfer(MIN_STORAGE) + .deploy_contract(HELLO_CODE.to_vec()) + .add_full_access_key(public_key); + } + } +``` + + + + + +```js + import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' + import { PublicKey } from 'near-sdk-js/lib/types' + + const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ + + @NearBindgen({}) + class Contract { + @call({}) + create_hello({prefix, public_key}:{prefix: String, public_key: PublicKey}) { + const account_id = `${prefix}.${near.currentAccountId()}` + + NearPromise.new(account_id) + .createAccount() + .transfer(MIN_STORAGE) + .addFullAccessKey(public_key) + } + } +``` + + + + + +```python +from near_sdk_py import call, Contract +from near_sdk_py.promises import Promise +from near_sdk_py.constants import ONE_NEAR + +# 存储所需的最小金额 +MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ + +class ExampleContract(Contract): + @call + def create_with_key(self, prefix, public_key): + """创建具有完全访问密钥的子账户""" + # 生成新账户 ID + account_id = f"{prefix}.{self.current_account_id}" + + # 创建新账户、转移一些 NEAR 并添加密钥 + return Promise.create_batch(account_id)\ + .create_account()\ + .transfer(MIN_STORAGE)\ + .add_full_access_key(public_key) +``` + + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type AddKeysInput struct { + Prefix string `json:"prefix"` + PublicKey string `json:"public_key"` +} + +// @contract:payable min_deposit=0.001NEAR +func (c *Contract) ExampleAddKeys(input AddKeysInput) { + currentAccountId, _ := env.GetCurrentAccountId() + subaccountId := input.Prefix + "." + currentAccountId + amount, _ := types.U128FromString("1000000000000000000000") // 0.001Ⓝ + + promise.CreateBatch(subaccountId). + CreateAccount(). + Transfer(amount). + AddFullAccessKey([]byte(input.PublicKey), 0) +} +``` + + + +请注意,您实际上添加的是"公钥"。拥有其对应私钥的人将能够使用新的访问密钥。 + + +如果部署了合约的账户**没有**任何访问密钥,这被称为锁定合约。当账户被锁定时,它无法签署交易,因此操作**只能**从**合约代码内部**执行。 + + +--- + +## 删除账户 + +使用 `delete_account` 操作有两种场景: +1. 作为批量操作链中的**最后一个**操作。 +2. 使智能合约删除其自身账户。 + + + + +```rust + use near_sdk::{near, env, Promise, NearToken, AccountId}; + + #[near(contract_state)] + #[derive(Default)] + pub struct Contract { } + + const MIN_STORAGE: NearToken = NearToken::from_millinear(1); //0.001Ⓝ + + #[near] + impl Contract { + pub fn create_delete(&self, prefix: String, beneficiary: AccountId){ + let account_id = prefix + "." + &env::current_account_id().to_string(); + Promise::new(account_id.parse().unwrap()) + .create_account() + .transfer(MIN_STORAGE) + .delete_account(beneficiary); + } + + pub fn self_delete(beneficiary: AccountId){ + Promise::new(env::current_account_id()) + .delete_account(beneficiary); + } + } +``` + + + + + +```js + import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' + import { AccountId } from 'near-sdk-js/lib/types' + + const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ + + @NearBindgen({}) + class Contract { + @call({}) + create_delete({prefix, beneficiary}:{prefix: String, beneficiary: AccountId}) { + const account_id = `${prefix}.${near.currentAccountId()}` + + NearPromise.new(account_id) + .createAccount() + .transfer(MIN_STORAGE) + .deleteAccount(beneficiary) + } + + @call({}) + self_delete({beneficiary}:{beneficiary: AccountId}) { + NearPromise.new(near.currentAccountId()) + .deleteAccount(beneficiary) + } + } +``` + + + + + +```python +from near_sdk_py import call, Contract +from near_sdk_py.promises import Promise +from near_sdk_py.constants import ONE_NEAR + +# 存储所需的最小金额 +MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ + +class ExampleContract(Contract): + @call + def create_delete(self, prefix, beneficiary): + """创建账户并立即删除它,将资金发送给受益人""" + # 生成新账户 ID + account_id = f"{prefix}.{self.current_account_id}" + + # 创建账户、转移资金然后删除它 + return Promise.create_batch(account_id)\ + .create_account()\ + .transfer(MIN_STORAGE)\ + .delete_account(beneficiary) + + @call + def self_delete(self, beneficiary): + """删除此合约的账户""" + # 删除账户并将剩余资金发送给受益人 + return Promise.create_batch(self.current_account_id)\ + .delete_account(beneficiary) +``` + + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type DeleteAccountInput struct { + Prefix string `json:"prefix"` + Beneficiary string `json:"beneficiary"` +} + +type SelfDeleteInput struct { + Beneficiary string `json:"beneficiary"` +} + +// @contract:payable min_deposit=0.001NEAR +func (c *Contract) ExampleCreateDeleteAccount(input DeleteAccountInput) { + currentAccountId, _ := env.GetCurrentAccountId() + subaccountId := input.Prefix + "." + currentAccountId + amount, _ := types.U128FromString("1000000000000000000000") // 0.001Ⓝ + + promise.CreateBatch(subaccountId). + CreateAccount(). + Transfer(amount). + DeleteAccount(input.Beneficiary) +} + +// @contract:mutating +func (c *Contract) ExampleSelfDeleteAccount(input SelfDeleteInput) { + currentAccountId, _ := env.GetCurrentAccountId() + + promise.CreateBatch(currentAccountId). + DeleteAccount(input.Beneficiary) +} +``` + + + + +**代币损失** +如果受益人账户不存在,资金将被[**分配给验证者**](../../protocol/network/token-loss)。 + + + +**代币损失** +**不要**使用 `delete` 尝试为新账户提供资金。由于账户不存在,代币将会丢失。 + diff --git a/zh/smart-contracts/anatomy/anatomy.mdx b/zh/smart-contracts/anatomy/anatomy.mdx new file mode 100644 index 00000000000..80bdb74da3b --- /dev/null +++ b/zh/smart-contracts/anatomy/anatomy.mdx @@ -0,0 +1,161 @@ +--- +title: 基本结构 +hide_table_of_contents: true +description: "了解所有智能合约的基本结构。" +mode: wide +--- +import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' + +让我们通过一个简单的 "Hello World" 合约来说明智能合约的基本结构。本页面的代码来自我们在 GitHub 上的 [Hello NEAR 仓库](https://github.com/near-examples/hello-near-examples)。 + + + + + + ### 导入 SDK + 所有合约都将导入 **NEAR SDK**,使其能够[访问执行环境](./environment)、[调用其他合约](./crosscontract)、[转移代币](./actions)等。 + + 您也可以使用第三方库,但由于合约运行时的限制,某些库可能无法正常工作。 + + + + + + ### 合约主结构 + 合约通过结构体来描述: + - 属性定义了合约存储的数据 + - 函数定义了其公共(和私有)接口 + + + + + + ### 注释指令 + + 与具有内置装饰器的语言不同,Near Go SDK 使用**注释指令**来控制代码如何被编译成智能合约。 + + 生成器(内置于 `near-go` CLI)会扫描您的注释,寻找以 `@contract:` 开头的特定标签。 + + > **注意:** 这些指令依赖于 `near-go` 构建过程,如果您使用原始的 `tinygo` 编译则无法工作。 + + 1. **`@contract:state`**:放在 `struct` 上方。它标识智能合约的主状态。**每个项目只允许一个状态结构体。** CLI 自动生成 `getState` 和 `setState` 辅助方法。 + 2. **`@contract:init`**:标记初始化方法(构造函数)。它在运行前检查状态是否为空。**每个项目只允许一个 init 方法。** + 3. **`@contract:view`**:将方法标记为只读。它将方法暴露给外部,但不保存状态变更。与 `@contract:promise_callback` 兼容。 + 4. **`@contract:mutating`**:标记修改状态的方法。CLI 在执行后自动将更新后的状态保存回存储。需要修改数据的回调函数必须使用此标记。 + 5. **`@contract:payable`**:允许方法接受附加的 NEAR 代币。您可以选择性地指定要求,如 `min_deposit=1NEAR`。与 `mutating` 和 `init` 兼容。 + 6. **`@contract:promise_callback`**:通过将 `promise.PromiseResult` 注入参数来处理跨合约调用的结果。**必须与** `@contract:view`(用于只读逻辑)或 `@contract:mutating`(用于保存状态变更)**组合使用**。 + + + **注意:** 导出到 WASM 的方法将自动转换为 `snake_case`(例如,`SetGreeting` 变为 `set_greeting`)。 + + + + + + ### 合约类装饰器 + + 请注意,合约类使用 `@NearBindgen` 装饰。此装饰器告诉 SDK 哪个类定义了合约,因此它知道: + 1. 合约加载时从存储中获取什么 + 2. 合约执行完成时存储什么 + 3. 哪些方法暴露给外部 + 4. 合约是否需要初始化(我们稍后会介绍) + + **注意:** 只有一个类可以使用 `@NearBindgen` 装饰器 + + + + + + ### Python 类结构 + + 在 Python 中,我们使用类来定义合约。与 JavaScript 或 Rust 不同,类本身没有特定的装饰器。相反,应该暴露给区块链的每个方法都使用适当的装饰器(`@view`、`@call` 或 `@init`)进行装饰。 + + 合约的状态通过实例变量管理,可以使用 Storage API 或集合进行持久化。 + + + + + + ### 合约结构体宏 + + 请注意,合约的结构体定义和实现都使用宏装饰 + + `#[near(contract_state)]` 宏告诉 SDK 此结构体定义了合约的状态,因此它知道: + 1. 合约加载时从存储中获取什么 + 2. 合约执行完成时存储什么 + + `#[near]` 宏告诉 SDK 哪些函数暴露给外部。 + + **注意:** 只有一个结构体可以使用 `#[near(contract_state)]` 宏装饰。 + + + + + + + + 当 `near` 为 wasm32 目标构建时,它会生成外部 NEAR 合约绑定。为此,它实际上生成了另一个具有签名 `pub extern "C" fn function_name()` 的函数,该函数首先从 NEAR 存储反序列化合约结构体,然后调用 `contract.function_name(parameter1, parameter2, ...)`。 + + 如果您用任何属性类宏注释了函数,这些宏将被执行_两次_。特别是,如果属性类宏对函数签名进行任何修改,或插入任何依赖于合约结构体(以 `&self`、`&mut self` 或 `self` 的形式)的代码,这将在第二次调用时失败,因为外部暴露的函数没有此结构体的概念。 + + 可以通过检查您正在构建的目标来检测这一点,并将宏的执行限制为仅在第一次传递时操作。 + + + + + + + + ### 存储(状态) + 我们将合约中存储的数据称为[合约的状态](./storage)。 + + 在我们的 Hello World 示例中,合约存储一个字符串(`greeting`),状态以默认值 `"Hello"` 初始化。 + + **注意:** 我们将在[状态章节](./storage)中进一步介绍合约状态。 + + + + + + JavaScript 合约还需要包含一个 `schema` 对象,用于定义合约的状态及其类型。SDK 使用此对象正确序列化和反序列化合约状态。 + + + + + + ### 方法装饰器 + + 在 Python 中,合约方法使用 `@view`、`@call` 或 `@init` 装饰器来定义它们的访问方式。 + 这些装饰器自动处理输入解析和返回值序列化。 + + + + + + ### 只读函数 + 合约的函数可以是只读的,意味着它们不修改状态。调用它们对所有人都是免费的,不需要有 NEAR 账户。 + + **注意:** 我们将在[函数章节](./functions)中进一步介绍函数类型。 + + + + + + ### 状态变更函数 + 修改状态或调用其他合约的函数被认为是状态变更函数。必须有 NEAR 账户才能调用它们,因为需要向网络发送交易。 + + **注意:** 我们将在[函数章节](./functions)中进一步介绍函数类型。 + + + + + + + + + + diff --git a/zh/smart-contracts/anatomy/best-practices.mdx b/zh/smart-contracts/anatomy/best-practices.mdx new file mode 100644 index 00000000000..9a7250f3d4c --- /dev/null +++ b/zh/smart-contracts/anatomy/best-practices.mdx @@ -0,0 +1,318 @@ +--- +title: 最佳实践 +description: "NEAR 智能合约编写最佳实践集合。" +--- +本页面提供了在 NEAR 上编写智能合约的最佳实践集合。这些实践旨在帮助您编写安全、高效且可维护的代码。 + +# 最佳实践 + + + + +以下是在 NEAR 上编写智能合约的一些最佳实践: + +- [高效存储账户 ID](#store-account-ids-efficiently) +- [启用溢出检查](#enable-overflow-checks) +- [尽早使用 `require!`](#use-require-early) +- [使用 `log!`](#use-log) +- [返回 `Promise`](#return-promise) +- [复用 `near-sdk` 中的 crate](#reuse-crates-from-near-sdk) +- [`std::panic!` 与 `env::panic` 的区别](#stdpanic-vs-envpanic) +- [使用 workspaces](#use-workspaces) + +--- + +## 高效存储账户 ID + +如果使用 NEAR 账户 ID,可以通过 base32 编码来节省智能合约存储空间。由于账户 ID 由 `[a-z.-_]` 字符组成,最大长度为 64 个字符,可以用每个字符 5 位加终止符 `\0` 进行编码。存储大小从原来的 (64 + 4) * 8 = 544 位缩减到 65 * 5 = 325 位,节省了 40% 的存储成本。 + +## 启用溢出检查 + +通常在整数溢出时触发 panic 会很有用。要启用此功能,请在 `Cargo.toml` 文件中添加以下内容: + +```toml +[profile.release] +overflow-checks = true +``` + +## 尽早使用 `require!` + +在执行任何操作之前,尝试使用 `require!` 验证输入、上下文、状态和访问权限。越早触发 panic,为调用者节省的 [gas](/protocol/transactions/gas) 就越多。 + +```rust +#[near] +impl Contract { + pub fn set_fee(&mut self, new_fee: Fee) { + require!(env::predecessor_account_id() == self.owner_id, "Owner's method"); + new_fee.assert_valid(); + self.internal_set_fee(new_fee); + } +} +``` + +**注意**:如果您需要 panic 消息中包含调试信息,或者使用的 SDK 版本早于 `4.0.0-pre.2`,可以使用 Rust 的 `assert!` 宏代替 `require!`。 + +```rust +#[near] +impl Contract { + pub fn set_fee(&mut self, new_fee: Fee) { + assert_eq!(env::predecessor_account_id(), self.owner_id, "Owner's method"); + new_fee.assert_valid(); + self.internal_set_fee(new_fee); + } +} +``` + +## 使用 `log!` + +使用日志记录进行调试和通知用户。 + +当您需要格式化消息时,可以使用以下宏: + +```rust +log!("Transferred {} tokens from {} to {}", amount, sender_id, receiver_id); +``` + +它等效于以下消息: + +```rust +env::log_str(format!("Transferred {} tokens from {} to {}", amount, sender_id, receiver_id).as_ref()); +``` + +## 返回 `Promise` + +如果您的方法进行了跨合约调用,您可能希望返回新创建的 `Promise`。 +这允许调用者(例如 near-cli 或 near-api-js 调用)等待 Promise 的结果,而不是立即返回。 +此外,如果 Promise 由于某些原因失败,返回它将让调用者了解到失败情况,并使 NEAR Explorer 和其他工具能够将整个交易链标记为失败。 +这可以防止链中第一个或前几个交易成功但后续交易失败时出现的假阳性情况。 + +例如: + +```rust +#[near] +impl Contract { + pub fn withdraw_100(&mut self, receiver_id: AccountId) -> Promise { + Promise::new(receiver_id).transfer(100) + } +} +``` + +## 复用 `near-sdk` 中的 crate + +`near-sdk` 重新导出了以下 crate: + +- `borsh` +- `base64` +- `bs58` +- `serde` +- `serde_json` + +最常用的 crate 包括 `borsh`(用于内部 STATE 序列化)和 `serde`(用于外部 JSON 序列化)。 + +当使用 `serde::Serialize` 标记结构体时,需要使用 `#[serde(crate = "near_sdk::serde")]` 来指向正确的基础 crate。 + +```rust +/// 使用 Borsh 序列化的主合约结构体 +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + pub pair: Pair, +} + +/// 同时实现 `serde` 和 `borsh` 序列化。 +/// `serde` 通常在以 JSON 格式向前端返回结构体时很有用。 +#[near(serializers = [json, borsh])] +pub struct Pair { + pub a: u32, + pub b: u32, +} + +#[near] +impl Contract { + #[init] + pub fn new(pair: Pair) -> Self { + Self { + pair, + } + } + + pub fn get_pair(self) -> Pair { + self.pair + } +} +``` + +## `std::panic!` 与 `env::panic` + +- `std::panic!` 会使当前线程 panic。它内部使用 `format!`,因此可以接受参数。 + SDK 设置了一个 panic hook,将 `panic!` 生成的 `PanicInfo` 转换为字符串,并在内部使用 `env::panic` 向运行时报告。 + 这可能会提供额外的调试信息,例如发生 panic 的源代码行号。 + +- `env::panic` 直接调用宿主方法使合约 panic。 + 除了传入的消息外,它不提供任何额外的调试信息。 + +## 使用 workspaces + +Workspaces 允许您自动化工作流程,并在沙盒或测试网环境中为多个合约和跨合约调用运行测试。 +了解更多:[workspaces-rs](https://github.com/near/workspaces-rs) 或 [workspaces-js](https://github.com/near/workspaces-js)。 + + + + +以下是在 NEAR 上使用 Python 编写智能合约的一些最佳实践: + +- [尽早验证](#validate-early) +- [使用适当的日志记录](#use-proper-logging) +- [返回 Promise](#return-promises) +- [高效处理存储](#handle-storage-efficiently) +- [使用类型提示](#use-type-hints) +- [遵循安全模式](#follow-security-patterns) + +--- + +## 尽早验证 + +在函数中尽早验证输入、上下文和状态。这可以通过在执行昂贵操作之前快速失败来节省 gas。 + +```python +from near_sdk_py import call, Context + +class Contract: + def __init__(self): + self.owner_id = Context.current_account_id() + + @call + def set_config(self, config_data): + # 尽早验证权限 + if Context.predecessor_account_id() != self.owner_id: + raise Exception("Only owner can modify config") + + # 尽早验证输入 + if "parameter" not in config_data or not isinstance(config_data["parameter"], int): + raise Exception("Invalid config: missing or invalid parameter") + + # 验证后再继续 + self._update_config(config_data) +``` + +## 使用适当的日志记录 + +使用 `Log` 工具进行结构化日志记录。这使外部服务能够轻松解析您合约中的事件。 + +```python +from near_sdk_py import call, Log + +@call +def transfer(self, receiver_id, amount): + # 业务逻辑... + + # 使用信息日志进行常规更新 + Log.info(f"Transferred {amount} tokens to {receiver_id}") + + # 使用结构化事件日志记录可索引事件 + Log.event("transfer", { + "sender": Context.predecessor_account_id(), + "receiver": receiver_id, + "amount": amount + }) +``` + +## 返回 Promise + +进行跨合约调用时,返回 Promise 对象以让调用者跟踪结果。这对于需要监控的交易尤为重要。 + +```python +from near_sdk_py import call +from near_sdk_py.promises import Contract + +@call +def withdraw(self, amount, receiver_id): + # 执行验证和业务逻辑... + + # 返回 Promise 以改善调用者体验 + return Contract(receiver_id).call( + "deposit", + amount=amount, + sender=Context.predecessor_account_id() + ) +``` + +## 高效处理存储 + +使用 SDK 集合进行高效的存储处理,尤其是对于不断增长的数据集。 + +```python +from near_sdk_py.collections import UnorderedMap, Vector + +class TokenContract: + def __init__(self): + # 使用 SDK 集合进行高效存储 + self.tokens = Vector("t") # 高效存储有序项 + self.balances = UnorderedMap("b") # 高效存储键值对 + + @call + def mint(self, token_id): + # 无需加载所有 token 即可添加到向量 + self.tokens.append(token_id) + + # 无需加载所有余额即可更新余额 + current = self.balances.get(Context.predecessor_account_id(), 0) + self.balances[Context.predecessor_account_id()] = current + 1 +``` + +## 使用类型提示 + +Python 的类型提示提高了代码可读性,并可以帮助在开发过程中发现错误。 + +```python +from typing import Dict, List, Optional +from near_sdk_py import view, call + +class Contract: + def __init__(self): + self.data: Dict[str, int] = {} + + @view + def get_value(self, key: str) -> Optional[int]: + return self.data.get(key) + + @call + def set_values(self, updates: Dict[str, int]) -> List[str]: + updated_keys = [] + for key, value in updates.items(): + self.data[key] = value + updated_keys.append(key) + return updated_keys +``` + +## 遵循安全模式 + +应用安全最佳实践以保护您的合约免受常见漏洞的影响。 + +```python +from near_sdk_py import call, Context +from near_sdk_py.constants import ONE_NEAR + +class Contract: + def __init__(self): + self.owner = Context.current_account_id() + self.minimum_deposit = ONE_NEAR // 100 # 0.01 NEAR + + @call + def deposit(self): + # 检查足够的存款以防止垃圾攻击 + deposit = Context.attached_deposit() + if deposit < self.minimum_deposit: + raise Exception(f"Minimum deposit is {self.minimum_deposit}") + + # 重入保护模式 + current_balance = self.balances.get(Context.predecessor_account_id(), 0) + # 在外部调用之前更新状态 + self.balances[Context.predecessor_account_id()] = current_balance + deposit + + # 只有在状态更新后,才执行任何外部调用 + # ... +``` + + + diff --git a/zh/smart-contracts/anatomy/collections.mdx b/zh/smart-contracts/anatomy/collections.mdx new file mode 100644 index 00000000000..e192e4a582a --- /dev/null +++ b/zh/smart-contracts/anatomy/collections.mdx @@ -0,0 +1,759 @@ +--- +title: 集合 +description: "在智能合约中高效存储、访问和管理数据。" +--- + +import { Github } from '/snippets/github.jsx' + +在选择数据结构时,了解各种结构的权衡非常重要。选择错误的结构可能会随着应用规模的扩大而产生瓶颈,而将状态迁移到新数据结构也需要付出一定代价。 + +您可以在以下两种类型的集合中进行选择: + +1. 原生集合(例如 `Array`、`Map`、`Set`),由语言本身提供 +2. SDK 集合(例如 `IterableMap`、`Vector`),由 NEAR SDK 提供 + + +**原生集合与 SDK 集合** + +对于需要整体访问的少量数据,使用原生集合;对于不需要整体访问的大量数据,使用 SDK 集合。 + +如果您的集合最多有 100 个条目,使用原生集合是可以接受的。对于更大的集合,建议使用 SDK 集合。有关比较,请参考[此基准测试](https://www.github.com/volodymyr-matselyukh/near-benchmarking)。 + + + +--- + +## 存储管理 + +每次执行合约时,它首先会读取值并将其[反序列化](./serialization)到内存中,函数执行完成后,它会[序列化](./serialization)并将值写回数据库。 + +对于原生集合,合约会在任何方法执行之前将整个集合完全加载到内存中。即使您调用的方法不使用该集合,这种情况也会发生。请注意,这会影响您在合约方法上花费的 GAS。 + + + +您的合约需要锁定与其在区块链上存储数据量成比例的余额。这意味着: + +- 如果添加了更多数据,**存储增加 ↑**,合约的**余额减少 ↓**。 +- 如果删除了数据,**存储减少 ↓**,合约的**余额增加 ↑**。 + +目前,存储 **100kb** 数据的成本约为 **1 Ⓝ**。 + + + + + +在链上存储数据时,请务必注意以下几点: + +- 一次上传最多 4mb 的限制 + +举例来说,如果有人想将 NFT 完全存储在链上(而不是 IPFS 或其他去中心化存储解决方案),存储空间几乎是无限的,但需要为每 100kb 存储支付 1 `$NEAR`。 + +由于 MAX_GAS 限制,用户每次合约调用上传限制为 4MB。一次函数调用最多可附加 300TGas。 + + + + + +如果您尝试存储数据但没有足够的 NEAR 来支付存储成本,合约将会 panic + + + + + +注意潜在的[小额存款攻击](../security/storage) + + + +--- + +## 原生集合 + +原生集合是语言提供的集合,例如 Javascript 中的 `Array`、`Map`、`Set`,或 Rust 中的 `Vec`、`HashMap`、`HashSet`。 + +原生集合中的所有条目都**序列化为单个值**并**一起存储**到状态中。这意味着每次函数执行时,SDK 都会读取并**反序列化原生集合中的所有条目**。 + + + +数组 `[1,2,3,4]` 在 Javascript 中会被序列化为 JSON 字符串 `"[1,2,3,4]"`,在 Rust 中会被序列化为 Borsh 字节流 `[0,0,0,4,1,2,3,4]` 后再存储。 + + + + +**何时使用它们** + +当您计划存储需要整体访问的少量数据时,原生集合很有用。 + + + + +**保持原生集合小巧** + +随着原生集合的增长,从内存中反序列化它的成本会越来越高。如果集合增长过大,您的合约可能会在尝试读取状态时耗尽所有 gas,导致每次函数调用都失败。 + + + +--- + +## SDK 集合 + +NEAR SDK 提供了针对大量数据随机访问进行优化的集合。SDK 集合使用"前缀"实例化,该前缀用作索引,将数据分成多个块。这样,SDK 集合可以将读写延迟到需要时才进行。 + + + +前缀为 `"p"` 的 SDK 数组 `[1,2,3,4]` 将在合约的属性中存储字符串 `"p"`,并在合约存储中创建四个条目:`p-0:1`、`p-1:2`... + + + + + +| 类型 | 可迭代 | 清除所有值 | 保留插入顺序 | 范围选择 | +|----------------|:------:|:----------:|:------------:|:--------:| +| `Vector` | ✅ | ✅ | ✅ | ✅ | +| `LookupSet` | | | | | +| `UnorderedSet` | ✅ | ✅ | | ✅ | +| `IterableSet` | ✅ | ✅ | | ✅ | +| `LookupMap` | | | | | +| `UnorderedMap` | ✅ | ✅ | | ✅ | +| `IterableMap` | ✅ | ✅ | | ✅ | +| `TreeMap` | ✅ | ✅ | ✅ | ✅ | + + + + + +| 类型 | 访问 | 插入 | 删除 | 搜索 | 遍历 | 清除 | +|----------------|:------:|:--------:|:--------:|:--------:|:--------:|:------:| +| `Vector` | O(1) | O(1)\* | O(1)\*\* | O(n) | O(n) | O(n) | +| `LookupSet` | O(1) | O(1) | O(1) | O(1) | N/A | N/A | +| `UnorderedSet` | O(1) | O(1) | O(1) | O(1) | O(n) | O(n) | +| `IterableSet` | O(1) | O(1) | O(1) | O(1) | O(n) | O(n) | +| `LookupMap` | O(1) | O(1) | O(1) | O(1) | N/A | N/A | +| `IterableMap` | O(1) | O(1) | O(1) | O(1) | O(n) | O(n) | +| `TreeMap` | O(1) | O(log n) | O(log n) | O(log n) | O(n) | O(n) | + +_\* - 使用 `push_back` 在向量末尾插入(或使用 `push_front` 在双端队列前端插入)_ +_\*\* - 使用 `pop` 从向量末尾删除(或使用 `pop_front` 从双端队列前端删除),或使用 `swap_remove` 将元素与向量最后一个元素交换后删除。_ + + + +这些集合的接口设计与原生集合相似。 + + +**何时使用它们** + +当您计划存储不需要整体访问的大量数据时,SDK 集合很有用。 + + + +### 实例化 + +所有结构体都需要使用唯一的 **`prefix`** 进行初始化,该前缀将用于在账户状态中索引集合的值。 + + + + + + + + 请注意我们如何使用 `enums` 确保所有集合具有不同的前缀。使用 `enums` 的另一个优点是它们被序列化为单个 `byte` 前缀。 + + + + + + + + + + + +不要忘记使用 `schema` 来定义合约状态的结构 + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import Vector, LookupMap, UnorderedMap, LookupSet, UnorderedSet + +class MyContract: + @init + def new(self): # 使用前缀 "v" 创建 Vector + self.my_vector = Vector("v") + + # 使用前缀 "m" 创建 LookupMap + self.my_lookup_map = LookupMap("m") + + # 使用前缀 "um" 创建 UnorderedMap + self.my_unordered_map = UnorderedMap("um") + + # 使用前缀 "s" 创建 LookupSet + self.my_lookup_set = LookupSet("s") + + # 使用前缀 "us" 创建 UnorderedSet + self.my_unordered_set = UnorderedSet("us") + + # 对于嵌套集合,使用不同的前缀 + self.nested_maps = UnorderedMap("nested") + + return True + + @call + def create_nested_map(self, key: str): + # 创建一个将存储在另一个映射中的新映射 + inner_map = UnorderedMap(f"inner_{key}") + self.nested_maps[key] = inner_map + return {"success": True} + +```` + + + + + + +注意不要在两个集合中使用相同的前缀,否则它们的存储空间会发生冲突,在向一个集合写入时可能会覆盖另一个集合的信息。 + + + +
+ +### Vector + +实现了在合约存储中持久化的[向量/数组](https://en.wikipedia.org/wiki/Array_data_structure)。请参考 Rust 和 JS SDK 文档获取完整的接口参考。 + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import Vector + +class VectorExample: + @init + def new(self): + # 使用前缀 "v" 创建 Vector + self.my_vector = Vector("v") + + @call + def add_number(self, number): + # 向向量添加一个值 + self.my_vector.append(number) + return len(self.my_vector) + + @view + def get_number(self, index): + # 获取特定索引处的值 + try: + return self.my_vector[index] + except Exception: + return None + + @view + def get_all_numbers(self): + # 将整个向量转换为列表 + return [num for num in self.my_vector] +``` + + + +
+ +### LookupMap + +实现了在合约存储中持久化的[映射/字典](https://en.wikipedia.org/wiki/Associative_array)。请参考 Rust 和 JS SDK 文档获取完整的接口参考。 + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import LookupMap + +class LookupMapExample: + @init + def new(self): + # 使用前缀 "m" 创建 LookupMap + self.balances = LookupMap("m") + + @call + def set_balance(self, account_id, amount): + # 为键设置一个值 + self.balances[account_id] = amount + return True + + @view + def get_balance(self, account_id): + # 获取键的值,带有默认值 + return self.balances.get(account_id, 0) + + @call + def remove_balance(self, account_id): + # 删除一个键 + if account_id in self.balances: + del self.balances[account_id] + return True + return False +``` + + + +
+ +### UnorderedMap / IterableMap + +实现了在合约存储中持久化的[映射/字典](https://en.wikipedia.org/wiki/Associative_array)。请参考 Rust 和 JS SDK 文档获取完整的接口参考。 + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import UnorderedMap + +class UnorderedMapExample: + @init + def new(self): + # 使用前缀 "um" 创建 UnorderedMap + self.user_data = UnorderedMap("um") + + @call + def set_user_data(self, account_id, data): + # 为键设置一个值 + self.user_data[account_id] = data + return True + + @view + def get_user_data(self, account_id): + # 获取键的值 + try: + return self.user_data[account_id] + except Exception: + return None + + @view + def list_all_users(self): + # 遍历键和值 + return { + "keys": self.user_data.keys(), + "values": self.user_data.values(), + "items": self.user_data.items() + } +``` + + + +
+ +### LookupSet + +实现了在合约存储中持久化的[集合](https://en.wikipedia.org/wiki/Set_(abstract_data_type))。请参考 Rust 和 JS SDK 文档获取完整的接口参考。 + + + + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import LookupSet + +class LookupSetExample: + @init + def new(self): + # 使用前缀 "s" 创建 LookupSet + self.whitelist = LookupSet("s") + + @call + def add_to_whitelist(self, account_id): + # 向集合添加一个值 + self.whitelist.add(account_id) + return True + + @view + def is_whitelisted(self, account_id): + # 检查集合中是否存在某个值 + return account_id in self.whitelist + + @call + def remove_from_whitelist(self, account_id): + # 从集合中删除一个值 + try: + self.whitelist.remove(account_id) + return True + except Exception: + return False +``` + + + + +
+ +### UnorderedSet / IterableSet + +实现了在合约存储中持久化的[映射/字典](https://en.wikipedia.org/wiki/Associative_array)。请参考 Rust 和 JS SDK 文档获取完整的接口参考。 + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import UnorderedSet + +class UnorderedSetExample: + @init + def new(self): + # 使用前缀 "us" 创建 UnorderedSet + self.owners = UnorderedSet("us") + + @call + def register_owner(self, account_id): + # 向集合添加一个值 + self.owners.add(account_id) + return True + + @view + def is_owner(self, account_id): + # 检查集合中是否存在某个值 + return account_id in self.owners + + @view + def list_all_owners(self): + # 以列表形式获取所有值 + return self.owners.values() + + @call + def remove_owner(self, account_id): + # 尝试删除存在的值 + self.owners.discard(account_id) + return True +``` + + + +
+ +### Tree + +Map 的有序等价物。底层实现基于 [AVL](https://en.wikipedia.org/wiki/AVL_tree) 树。当您需要保持一致的顺序或访问最小/最大键时,应使用此结构。 + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import TreeMap + +class TreeMapExample: + @init + def new(self): + # 使用前缀 "tm" 创建 TreeMap + self.scores = TreeMap("tm") + + @call + def add_score(self, user_id, score): + # 为用户设置分数 + self.scores[user_id] = score + return True + + @view + def get_top_scores(self, limit=10): + # 使用有序键获取最高分 + # 这将按降序返回最高分 + top_users = [] + max_key = self.scores.max_key() + current_key = max_key + count = 0 + + while current_key is not None and count < limit: + top_users.append({ + "user": current_key, + "score": self.scores[current_key] + }) + current_key = self.scores.floor_key(current_key - 1) # 获取下一个最高键 + count += 1 + + return top_users +``` + + + +--- + +## 嵌套集合 + +嵌套 SDK 集合时,请注意为所有集合(包括嵌套集合)**使用不同的前缀**。 + + + + + + + + + 请注意我们如何使用接受 `String` 参数的 `enums` 来确保所有集合具有不同的前缀。 + + + + + + + + + + + + +```python +from near_sdk_py import view, call, init +from near_sdk_py.collections import UnorderedMap, Vector +from near_sdk_py.collections import create_prefix_guard + +class NestedCollectionsExample: + @init + def new(self): + # 用户到其资产的主映射 + self.user_assets = UnorderedMap("users") + + @call + def add_asset(self, user_id, asset_id, metadata): + # 使用唯一前缀获取或创建用户的资产向量 + if user_id not in self.user_assets: + # 创建前缀保护以确保此用户的前缀唯一 + prefix = f"assets:{user_id}" + # 为此用户的资产创建新向量 + self.user_assets[user_id] = Vector(prefix) + + # 将资产添加到用户的资产向量中 + user_assets = self.user_assets[user_id] + user_assets.append({ + "asset_id": asset_id, + "metadata": metadata + }) + + # 在映射中更新向量 + self.user_assets[user_id] = user_assets + return True + + @view + def get_user_assets(self, user_id): + if user_id not in self.user_assets: + return [] + + # 获取用户的所有资产 + user_assets = self.user_assets[user_id] + return [asset for asset in user_assets] +```` + + + +在 Python 中,我们通过在前缀字符串中包含父级的标识符来为嵌套集合创建唯一前缀。SDK 还提供了 `create_prefix_guard` 工具来帮助管理前缀。 + + + + + + +--- + +## 容易出错的模式 + +由于值不保存在内存中,而是从存储中延迟加载,因此如果替换或删除集合,请务必确保存储被清除。此外,如果修改了集合,集合本身必须在状态中更新,因为大多数集合会存储一些元数据。 + +一些无法在类型级别限制的容易出错的模式需要避免: + + + + +```rust +use near_sdk::store::UnorderedMap; + +let mut m = UnorderedMap::::new(b"m"); +m.insert(1, "test".to_string()); +assert_eq!(m.len(), 1); +assert_eq!(m.get(&1), Some(&"test".to_string())); + +// Bug 1: 不应该在不清除状态的情况下替换任何集合,这将重置任何元数据, +// 例如元素数量,从而导致错误。如果您用具有不同前缀的内容替换集合, +// 它将是功能性的,但您会丢失任何先前的数据,而旧值也不会从存储中删除。 +m = UnorderedMap::new(b"m"); +assert!(m.is_empty()); +assert_eq!(m.get(&1), Some(&"test".to_string())); + +// Bug 2: 不应该使用与另一个集合相同的前缀,否则会产生意外的副作用。 +let m2 = UnorderedMap::::new(b"m"); +assert!(m2.is_empty()); +assert_eq!(m2.get(&1), Some(&"test".to_string())); + +// Bug 3: 忘记将集合保存到存储中。当集合附加到合约状态 +//(`#[near]` 中的 `self`)时,这将自动完成,但如果手动与存储交互 +// 或使用嵌套集合时,这一点就变得重要了。 +use near_sdk::store::Vector; + +// 模拟初始化状态的函数调用过程。 +{ + let v = Vector::::new(b"v"); + near_sdk::env::state_write(&v); +} + +// 模拟仅修改集合但不存储集合本身的函数调用过程。 +{ + let mut v: Vector = near_sdk::env::state_read().unwrap(); + v.push(1); + // 这里的 bug 是集合本身没有被写回 +} + +let v: Vector = near_sdk::env::state_read().unwrap(); +// 这将报告集合为空,即使元素存在 +assert!(v.get(0).is_none()); +assert!( + near_sdk::env::storage_read(&[b"v".as_slice(), &0u32.to_le_bytes()].concat()).is_some() +); + +// Bug 4(仅与 `near_sdk::store` 相关):这些集合将缓存写入和读取, +// 写入在 [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) 时执行, +// 因此如果集合保存在静态内存中或使用了 `std::mem::forget`,更改将不会被持久化。 +use near_sdk::store::IterableSet; + +let mut m: IterableSet = IterableSet::new(b"l"); +m.insert(1); +assert!(m.contains(&1)); + +// 修复方法是手动将中间更改刷新到存储中。 +// m.flush(); +std::mem::forget(m); + +m = IterableSet::new(b"l"); +assert!(!m.contains(&1)); +``` + + + + +--- + +## 分页 + +持久化集合(如 `IterableMap/UnorderedMap`、`IterableSet/UnorderedSet` 和 `Vector`)可能包含的元素数量超过了可用于全部读取的 gas 量。 +为了通过视图调用暴露所有元素,我们可以使用分页。 + + + + 在 Rust 中,可以使用带有 [`Skip`](https://doc.rust-lang.org/std/iter/struct.Skip.html) 和 [`Take`](https://doc.rust-lang.org/std/iter/struct.Take.html) 的迭代器来实现。这将只从存储中加载范围内的元素。 + + ```rust + #[near(contract_state)] + #[derive(PanicOnDefault)] + pub struct Contract { + pub status_updates: IterableMap, + } + + #[near] + impl Contract { + /// 从 `IterableMap` 中检索多个元素。 + /// - `from_index` 是起始索引。 + /// - `limit` 是要返回的最大元素数量。 + pub fn get_updates(&self, from_index: usize, limit: usize) -> Vec<(AccountId, String)> { + self.status_updates + .iter() + .skip(from_index) + .take(limit) + .collect() + } + } + ``` + + + + + 在 JavaScript 中,可以使用带有 [`toArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/toArray) 和 [`slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 的迭代器来实现。 + + ```ts + /// 从 `UnorderedMap` 中返回多个元素。 + /// - `from_index` 是起始索引。 + /// - `limit` 是要返回的最大元素数量。 + @view({}) + get_updates({ from_index, limit }: { from_index: number, limit:number }) { + return this.status_updates.toArray().slice(from_index, limit); + } + ``` + + + + ```python + # 在 Python 中,可以使用标准列表切片来实现。 + + @view + def get_updates(self, from_index: int = 0, limit: int = 50) -> list: + """返回集合中带分页的多个元素。 + + 参数: + from_index:起始索引 + limit:要返回的最大元素数量 + + 返回: + 集合中的元素列表 + """ + # 从集合中获取所有值 + all_items = self.status_updates.values() + + # 使用列表切片应用分页 + start = min(from_index, len(all_items)) + end = min(start + limit, len(all_items)) + + return all_items[start:end] + ``` + + diff --git a/zh/smart-contracts/anatomy/crosscontract.mdx b/zh/smart-contracts/anatomy/crosscontract.mdx new file mode 100644 index 00000000000..ab0ede77d74 --- /dev/null +++ b/zh/smart-contracts/anatomy/crosscontract.mdx @@ -0,0 +1,850 @@ +--- +title: 跨合约调用 +description: "合约可以与网络上的其他合约进行交互" +--- + +import {Github} from '/snippets/github.jsx'; + +NEAR 合约可以与其他已部署的合约进行交互,通过跨合约调用查询信息并在其上执行函数。 + +由于 NEAR 是一个分片区块链,其跨合约调用的行为与其他链不同。在 NEAR 中,跨合约调用是**异步**且**独立**的。 + + +**异步** +**调用函数**和**回调函数**在**不同的区块**中执行(通常相差 1-2 个区块)。在此期间,合约保持活跃状态,可以接收其他调用。 + + + +**独立** + +每个函数——发起调用的函数、外部函数和回调函数——都在各自的上下文中执行。如果外部调用失败,调用函数已经成功完成;不会自动回滚。您必须在回调中明确处理失败情况。 + + + +--- + +## 代码片段:查询信息 + +在编写合约时,您可能需要从另一个合约查询信息。以下是一个基本示例,我们从 [Hello NEAR](../quickstart) 示例中查询问候语消息。 + + + + + + + + + + 高级 API 使用了 [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) 中定义的接口 + + + + + + + + + +```python +from near_sdk_py import call, view, Contract, callback, PromiseResult, CrossContract, init + +class CrossContractExample(Contract): + # Contract we want to interact with + hello_contract = "hello-near.testnet" + + @init + def new(self): + """Initialize the contract""" + # Any initialization logic goes here + pass + + @view + def query_greeting_info(self): + """View function showing how to make a cross-contract call""" + # Create a reference to the Hello NEAR contract + # This is a simple call that will execute in the current transaction + hello = CrossContract(self.hello_contract) + return hello.call("get_greeting").value() + + @call + def query_greeting(self): + """Calls Hello NEAR contract to get the greeting with a callback""" + # Create a reference to the Hello NEAR contract + hello = CrossContract(self.hello_contract) + + # Call get_greeting and chain a callback + # The Promise API handles serialization and callback chaining + promise = hello.call("get_greeting").then("query_greeting_callback") + + return promise.value() + + @callback + def query_greeting_callback(self, result: PromiseResult): + """Processes the greeting result from Hello NEAR contract""" + # The @callback decorator automatically parses the promise result + # result will have a data property and a success boolean + if not result.success: + return {"success": False, "message": "Failed to get greeting"} + + return { + "success": True, + "greeting": result.data, + "message": f"Successfully got greeting: {result.data}" + } +``` + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +// @contract:payable min_deposit=0.001NEAR +func (c *Contract) ExampleQueryingGreetingInfo() { + helloAccount := "hello-nearverse.testnet" + gas := uint64(10 * types.ONE_TERA_GAS) + + promise.NewCrossContract(helloAccount). + Gas(gas). + Call("get_greeting", map[string]string{}). + Value() +} + +// @contract:payable min_deposit=0.001NEAR +func (c *Contract) ExampleQueryingInformation() { + helloAccount := "hello-nearverse.testnet" + gas := uint64(10 * types.ONE_TERA_GAS) + + promise.NewCrossContract(helloAccount). + Gas(gas). + Call("get_greeting", map[string]string{}). + Then("example_querying_information_response", map[string]string{}) +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleQueryingInformationResponse(result promise.PromiseResult) { + + if result.Success { + env.LogString("State change/Query completed successfully") + } else { + env.LogString("State change/Query failed") + } + + env.LogString("Promise result status: " + types.IntToString(result.StatusCode)) + if len(result.Data) > 0 { + env.LogString("Returned data: " + string(result.Data)) + } else { + env.LogString("No return data") + } +} +``` + + + +--- + +## 代码片段:发送信息 +调用另一个合约并传递信息也是常见场景。以下是一个与 [Hello NEAR](../quickstart) 示例交互以更改其问候语消息的函数。 + + + + + + + + + + + 高级 API 使用了 [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) 中定义的接口 + + + + + + + + + +```python +from near_sdk_py import call, Contract, callback, PromiseResult, CrossContract + +class CrossContractExample(Contract): + # Contract we want to interact with + hello_contract = "hello-near.testnet" + + @call + def change_greeting(self, new_greeting): + """Changes the greeting on the Hello NEAR contract""" + # Create a reference to the Hello NEAR contract + hello = CrossContract(self.hello_contract) + + # Create a promise to call set_greeting with the new greeting + # Pass context data to the callback directly as kwargs + promise = hello.call( + "set_greeting", + message=new_greeting + ).then( + "change_greeting_callback", + original_greeting=new_greeting # Additional context passed to callback + ) + + return promise.value() + + @callback + def change_greeting_callback(self, result: PromiseResult, original_greeting): + """Processes the result of set_greeting""" + # The original_greeting parameter is passed from the change_greeting method + if not result.success: + return { + "success": False, + "message": f"Failed to set greeting to '{original_greeting}'" + } + + return { + "success": True, + "message": f"Successfully set greeting to '{original_greeting}'", + "result": result.data + } +``` + + + +```go +package main + +import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +// @contract:payable min_deposit=0.00001NEAR +func (c *Contract) ExampleSendingInformation() { + helloAccount := "hello-nearverse.testnet" + gas := uint64(30 * types.ONE_TERA_GAS) + + args := map[string]string{ + "message": "New Greeting", + } + + promise.NewCrossContract(helloAccount). + Gas(gas). + Call("set_greeting", args). + Then("example_change_greeting_callback", map[string]string{}) +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleChangeGreetingCallback(result promise.PromiseResult) { + if result.Success { + env.LogString("State change completed successfully") + } else { + env.LogString("State change failed") + } + + env.LogString("Promise result status: " + types.IntToString(int(result.StatusCode))) + if len(result.Data) > 0 { + env.LogString("Returned data: " + string(result.Data)) + } else { + env.LogString("No return data from state change") + } +} +``` + + + +--- + +## Promise +跨合约调用通过在网络中创建两个 Promise 来工作: +1. 在外部合约中执行代码的 Promise - `Promise.create` +2. **可选**:调用另一个函数并传递结果的 Promise - `Promise.then` + +两个 Promise 都包含以下信息: + +- 您要交互的合约的**地址** +- 您要执行的**函数** +- 传递给函数的参数 +- 要使用的 **GAS** 数量(从**附加的 Gas** 中扣除) +- 附加的 NEAR **存款**(从**您合约的余额**中扣除) + + + +回调可以发送给**任何**合约,这意味着结果可能由另一个合约处理。 + + + +--- + +## 创建跨合约调用 + +要创建带回调的跨合约调用,需要创建两个 Promise 并使用 `.then` 方法链接它们: + + + + + + + ```rust + #[ext_contract(external_trait)] + trait Contract { + fn function_name(&self, param1: T, param2: T) -> T; + } + + external_trait::ext("external_address") + .with_attached_deposit(DEPOSIT) + .with_static_gas(GAS) + .function_name(arguments) + .then( + // 这是回调 + Self::ext(env::current_account_id()) + .with_attached_deposit(DEPOSIT) + .with_static_gas(GAS) + .callback_name(arguments) + ); + + ``` + + + + + ```rust + let arguments = json!({ "foo": "bar" }) + .to_string() + .into_bytes(); + + let promise = Promise::new("external_address").function_call( + "function_name".to_owned(), + arguments, + DEPOSIT, + GAS + ); + + promise.then( + // 创建一个 Promise 回调 query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(GAS) + .callback_name(), + ); + ``` + + + + + + ```ts + NearPromise.new("external_address").functionCall("function_name", JSON.stringify(arguments), DEPOSIT, GAS) + .then( + // 这个函数是回调 + NearPromise.new(near.currentAccountId()).functionCall("callback_name", JSON.stringify(arguments), DEPOSIT, GAS) + ); + ``` + + + + + ```go + package main + + import ( + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" + ) + + // @contract:state + type Contract struct{} + + type PromiseCallbackInputData struct { + Data string `json:"data"` + } + + // @contract:payable min_deposit=0.00001NEAR + func (c *Contract) ExampleCrossContractCall() { + externalAccount := "hello-nearverse.testnet" + gas := uint64(5 * types.ONE_TERA_GAS) + + args := map[string]string{ + "message": "New Greeting", + } + callback_args := map[string]string{ + "data": "saved_for_callback", + } + promise.NewCrossContract(externalAccount). + Gas(gas). + Call("set_greeting", args). + Then("example_cross_contract_callback", callback_args). + Value() + } + + // @contract:view + // @contract:promise_callback + func (c *Contract) ExampleCrossContractCallback(input PromiseCallbackInputData, result promise.PromiseResult) { + env.LogString("Executing callback") + + env.LogString("Input CrossContractCallback : " + input.Data) + + if result.Success { + env.LogString("Cross-contract call executed successfully") + } else { + env.LogString("Cross-contract call failed") + } + } + ``` + + + + + + +✅ 您可以连接 Promise:`P1.then(P2).then(P3)`:`P1` 执行,然后 `P2` 以 `P1` 的结果执行,然后 `P3` 以 `P2` 的结果执行 + +✅ 您可以合并 Promise:`(P1.and(P2)).then(P3)`:`P1` 和 `P2` 并行执行,完成后 `P3` 将执行并可以访问**两者的结果** + +⛔ 不能在没有回调的情况下**返回**一个合并的 Promise:`return P1.and(P2)` 是无效的,您需要添加 `.then()` + +⛔ 不能在 `then` 中合并 Promise:`P1.then(P2.join([P3]))` 是无效的 + +⛔ 不能在 `then` 中使用 `then`:`P1.then(P2.then(P3))` 是无效的 + + + + + +如果函数返回一个 Promise,那么它将委托返回值和交易执行状态;但如果您返回一个值或什么都不返回,那么 `Promise` 结果将不会影响交易状态。 + + + + + +您创建的 Promise 不会**立即执行**。事实上,它们将在网络中排队: +- 跨合约调用将在您的函数**正确完成**后 1 或 2 个区块后执行。 + + + +--- + +## 回调函数 +如果您的函数正确完成,那么最终您的回调函数将会执行。无论**外部合约是否失败**,这都会发生。 + +在回调函数中,您将可以访问结果,其中包含外部函数的状态(是否成功)以及成功时的返回值。 + + + + + + + + + + + +```python +from near_sdk_py import callback, PromiseResult, Contract + +class CrossContractExample(Contract): + @callback + def query_greeting_callback(self, result: PromiseResult, additional_context=None): + """ + Process the result of a cross-contract call. + The @callback decorator automatically: + 1. Reads the promise result data + 2. Handles serialization/deserialization + 3. Provides proper error handling + + Parameters: + - result: The PromiseResult object with status and data + - additional_context: Optional context passed from the calling function + """ + if not result.success: + # This means the external call failed or returned nothing + return { + "success": False, + "message": "Failed to get greeting", + "context": additional_context + } + + # Process successful result + return { + "success": True, + "greeting": result.data, + "message": f"Successfully got greeting: {result.data}", + "context": additional_context + } +``` + + + +```go +type PromiseCallbackInputData struct { + Data string `json:"data"` +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleCrossContractCallback(input PromiseCallbackInputData, result promise.PromiseResult) { + env.LogString("Executing callback") + + env.LogString("Input CrossContractCallback : " + input.Data) + + if result.Success { + env.LogString("Cross-contract call executed successfully") + } else { + env.LogString("Cross-contract call failed") + } +} +``` + + + + +**回调始终会执行** + +再次强调,如果您的函数正确完成,那么您的回调将**始终执行**。无论外部函数是否正确完成,这都会发生。 + + + + + +始终确保为您的回调函数有足够的 Gas 来执行。 + + + + + +记得使用宏/装饰器将您的回调函数标记为私有,这样它只能由合约本身调用。 + + + +### 如果我调用的函数失败会怎样? +如果外部函数失败(即 panic),那么您的回调**仍然会执行**。在这里,您需要**手动回滚**在原始调用期间对合约所做的任何更改。特别是: + +1. **如有需要,退款给前驱账户**:如果合约向调用附加了 NEAR,这些资金现在回到了**合约账户中**。 +2. **恢复任何状态更改**:如果原始函数做了任何状态更改(即更改或存储了数据),您需要手动回滚它们。**它们不会自动恢复**。 + + +如果您的原始函数正确完成,那么即使**外部函数 panic**,回调也会执行。您的状态**不会**自动回滚,`$NEAR` **不会**自动返回给签名者。务必在回调中检查外部函数是否失败,并在必要时手动回滚任何操作。 + + +--- + +## 在同一合约上调用多个函数 + +您可以在同一个外部合约上调用多个函数,这被称为**批量调用**。 + +批量调用的一个重要特性是它们**作为一个单元**运行:它们在同一个 [receipt](/protocol/transactions/transaction-execution#receipts--finality) 中执行,如果**任何函数失败**,那么它们**全部都会回滚**。 + + + + + + + + + + + + + + + +```python +from near_sdk_py import call, Context, Contract, callback, PromiseResult, ONE_TGAS, CrossContract, init + +class BatchCallsExample(Contract): + # Contract we want to interact with + hello_contract = "hello-near.testnet" + + @init + def new(self): + """Initialize the contract""" + pass + + @call + def call_multiple_methods(self, greeting1, greeting2): + """Call multiple methods on the same contract in a batch""" + # Create a contract instance + hello = CrossContract(self.hello_contract) + + # Create a batch for the hello contract + batch = hello.batch() + + # Add function calls to the batch + batch.function_call("set_greeting", {"message": greeting1}) + batch.function_call("another_method", {"arg1": greeting2}) + + # Add a callback to process the result + promise = batch.then(Context.current_account_id()).function_call( + "batch_callback", + {"original_data": [greeting1, greeting2]}, + gas=10 * ONE_TGAS + ) + + return promise.value() + + @callback + def batch_callback(self, result: PromiseResult, original_data=None): + """Process batch result - only gets the result of the last operation""" + return { + "success": result.success, + "result": result.data, + "original_data": original_data + } +``` + + + + +```go +package main + +import ( + "strconv" + + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type PromiseCallbackInputData struct { + Data string `json:"data"` +} + +// @contract:payable min_deposit=0.00001NEAR +func (c *Contract) ExampleBatchCallsSameContract() { + helloAccount := "hello-nearverse.testnet" + gas := uint64(10 * types.ONE_TERA_GAS) + amount, _ := types.U128FromString("0") + callback_args := map[string]string{ + "data": "[Greeting One, Greeting Two]", + } + + promise.NewCrossContract(helloAccount). + Batch(). + Gas(gas). + FunctionCall("set_greeting", map[string]string{ + "message": "Greeting One", + }, amount, gas). + FunctionCall("another_method", map[string]string{ + "arg1": "val1", + }, amount, gas). + Then(helloAccount). + FunctionCall("example_batch_calls_callback", callback_args, amount, gas) + + env.LogString("Batch call created successfully") +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleBatchCallsCallback(input PromiseCallbackInputData, result promise.PromiseResult) { + env.LogString("Processing batch call results") + env.LogString("Input CrossContractCallback : " + input.Data) + + env.LogString("Batch call success: " + strconv.FormatBool(result.Success)) + if len(result.Data) > 0 { + env.LogString("Batch call data: " + string(result.Data)) + } +} +``` + + + + + + +回调只能访问批量调用中**最后一个函数**的结果。 + + + +--- + +## 在不同合约上调用多个函数 + +您也可以在**不同的合约**上调用多个函数。这些函数将并行执行,且互不影响。这意味着,如果一个失败,其他的**将会执行,且不会回滚**。 + + + + + + + + + + + +```python +from near_sdk_py import call, Contract, multi_callback, PromiseResult, CrossContract, init + +class MultiContractExample(Contract): + # Contract addresses we want to interact with + contract_a = "contract-a.testnet" + contract_b = "contract-b.testnet" + + @init + def new(self): + """Initialize the contract""" + pass + + @call + def call_multiple_contracts(self): + """Calls multiple different contracts in parallel""" + # Create promises for each contract + contract_a = CrossContract(self.contract_a) + promise_a = contract_a.call("method_a") + + contract_b = CrossContract(self.contract_b) + promise_b = contract_b.call("method_b") + + # Join the promises and add a callback + # The first promise's join method can combine multiple promises + combined_promise = promise_a.join( + [promise_b], + "multi_contract_callback", + contract_ids=[self.contract_a, self.contract_b] # Context data + ) + + return combined_promise.value() + + @multi_callback + def multi_contract_callback(self, results, contract_ids=None): + """Process results from multiple contracts""" + # results is an array containing all promise results in order + return { + "contract_a": { + "id": contract_ids[0], + "result": results[0].data, + "success": results[0].success + }, + "contract_b": { + "id": contract_ids[1], + "result": results[1].data, + "success": results[1].success + }, + "success": all(result.success for result in results) + } +``` + + + + +```go +package main + +import ( + "strconv" + + "github.com/vlmoon99/near-sdk-go/env" + "github.com/vlmoon99/near-sdk-go/promise" + "github.com/vlmoon99/near-sdk-go/types" +) + +// @contract:state +type Contract struct{} + +type PromiseCallbackInputData struct { + Data string `json:"data"` +} + +// @contract:payable min_deposit=0.00001NEAR +func (c *Contract) ExampleParallelCallsDifferentContracts() { + contractA := "hello-nearverse.testnet" + contractB := "child.neargopromises1.testnet" + + promiseA := promise.NewCrossContract(contractA). + Call("get_greeting", map[string]string{}) + + promiseB := promise.NewCrossContract(contractB). + Call("SetStatus", map[string]string{"message": "Hello, World!"}) + + promiseA.Join([]*promise.Promise{promiseB}, "example_parallel_contracts_callback", map[string]string{ + "data": contractA + "," + contractB, + }).Value() + + env.LogString("Parallel contract calls initialized") +} + +// @contract:view +// @contract:promise_callback +func (c *Contract) ExampleParallelContractsCallback(input PromiseCallbackInputData, results []promise.PromiseResult) { + env.LogString("Processing results from multiple contracts") + env.LogString("Input CrossContractCallback : " + input.Data) + + for i, result := range results { + env.LogString("Processing result " + types.IntToString(i)) + env.LogString("Success: " + strconv.FormatBool(result.Success)) + if len(result.Data) > 0 { + env.LogString("Data: " + string(result.Data)) + } + } + + env.LogString("Processed " + types.IntToString(len(results)) + " contract responses") +} +``` + + + + + + +回调可以访问并行调用中**所有函数**的结果。 + + + +--- + +## 安全注意事项 + +在编写跨合约调用时,有一个重要方面需要牢记:所有调用都是**独立**且**异步**的。换句话说: + +- 进行调用的函数和回调函数是**独立**的。 +- 调用和回调之间存在**延迟**,在此期间人们仍然可以与合约交互。 + +这对您如何处理回调有重要影响。特别是: + +1. 确保在调用和回调之间不要让合约处于可被利用的状态。 +2. 如果外部调用失败,在回调中手动回滚对状态的任何更改。 + +我们有一个专门针对这些特定错误的[安全部分](../security/callbacks),请务必查阅。 + + +不遵循这些基本安全准则可能会让您的合约面临漏洞利用风险。请查看[安全部分](../security/callbacks),如果仍有疑问,[请在 Discord 加入我们](https://near.chat)。 + diff --git a/zh/smart-contracts/anatomy/environment.mdx b/zh/smart-contracts/anatomy/environment.mdx new file mode 100644 index 00000000000..34d4f32d64c --- /dev/null +++ b/zh/smart-contracts/anatomy/environment.mdx @@ -0,0 +1,304 @@ +--- +title: 环境 +description: "了解调用您的账户、附加了多少 gas 和代币等信息。" +--- + +环境是一组在智能合约执行时可用的变量和函数。它提供诸如谁调用了方法、调用附加了多少资金以及有多少计算资源可用等信息。 + +每次方法执行都有一个关联环境,包含以下信息: + +1. 谁调用了该方法 +2. 调用附加了多少资金 +3. 有多少计算资源可用 +4. 当前时间戳 +5. 公钥派生等辅助函数 + +--- + +## 环境变量 + + + + +| 变量名称 | SDK 变量 | 描述 | +|------------------------|----------------------------------|----------------------------------------------------------------------------------------| +| 前驱账户 | `env::predecessor_account_id()` | 调用此方法的账户 ID | +| 当前账户 | `env::current_account_id()` | 此智能合约的账户 ID | +| 签名者 | `env::signer_account_id()` | 签署导致本次执行的交易的账户 ID | +| 附加存款 | `env::attached_deposit()` | 前驱附加给调用的 yoctoNEAR 金额 | +| 账户余额 | `env::account_balance()` | 此智能合约的余额(包括附加存款) | +| 预付 Gas | `env::prepaid_gas()` | 可用于执行的 gas 数量 | +| 时间戳 | `env::block_timestamp()` | 当前时间戳(自 1970 年 1 月 1 日 0:00:00 UTC 起的非闰纳秒数) | +| 当前纪元 | `env::epoch_height()` | 区块链中的当前纪元 | +| 区块索引 | `env::block_index()` | 当前区块索引(即区块高度) | +| 已用存储 | `env::storage_usage()` | 此智能合约当前使用的存储字节数 | +| 存储字节成本 | `env::storage_byte_cost()` | 每字节当前存储成本(以 yoctoNEAR 为单位) | +| 已用 Gas | `env::used_gas()` | 执行已使用的 gas 数量 | +| 签名者公钥 | `env::signer_account_pk()` | 发送者公钥 | +| 账户锁定余额 | `env::account_locked_balance()` | 此智能合约被锁定的余额 | + + + + + +| 变量名称 | SDK 变量 | 描述 | +|------------------------|--------------------------------|----------------------------------------------------------------------------------------| +| 前驱账户 | `near.predecessorAccountId()` | 调用此方法的账户 ID | +| 当前账户 | `near.currentAccountId()` | 此智能合约的账户 ID | +| 签名者 | `near.signerAccountId()` | 签署导致本次执行的交易的账户 ID | +| 附加存款 | `near.attachedDeposit()` | 前驱附加给调用的 yoctoNEAR 金额 | +| 账户余额 | `near.accountBalance()` | 此智能合约的余额(包括附加存款) | +| 预付 Gas | `near.prepaidGas()` | 可用于执行的 gas 数量 | +| 时间戳 | `near.blockTimestamp()` | 当前时间戳(自 1970 年 1 月 1 日 0:00:00 UTC 起的非闰纳秒数) | +| 当前纪元 | `near.epochHeight()` | 区块链中的当前纪元 | +| 区块索引 | `near.blockIndex()` | 当前区块索引(即区块高度) | +| 已用存储 | `near.storageUsage()` | 此智能合约当前使用的存储 | +| 已用 Gas | `near.usedGas()` | 执行已使用的 gas 数量 | +| 签名者公钥 | `near.signerAccountPk()` | 发送者公钥 | +| 账户锁定余额 | `near.accountLockedBalance()` | 此智能合约被锁定的余额 | + + + + + +| 变量名称 | SDK 变量 | 描述 | +|------------------------|-------------------------------------|----------------------------------------------------------------------------------------| +| 前驱账户 | `Context.predecessor_account_id()` | 调用此方法的账户 ID | +| 当前账户 | `Context.current_account_id()` | 此智能合约的账户 ID | +| 签名者 | `Context.signer_account_id()` | 签署导致本次执行的交易的账户 ID | +| 附加存款 | `Context.attached_deposit()` | 前驱附加给调用的 yoctoNEAR 金额 | +| 预付 Gas | `Context.prepaid_gas()` | 可用于执行的 gas 数量 | +| 已用 Gas | `Context.used_gas()` | 执行已使用的 gas 数量 | +| 区块高度 | `Context.block_height()` | 当前区块高度 | +| 时间戳 | `Context.block_timestamp()` | 当前时间戳(自 1970 年 1 月 1 日 0:00:00 UTC 起的非闰纳秒数) | +| 当前纪元 | `Context.epoch_height()` | 区块链中的当前纪元 | + + + + +| 变量名称 | SDK 变量 | 描述 | +|------------------------|--------------------------------------|----------------------------------------------------------------------------------------| +| 前驱账户 | `env.GetPredecessorAccountID()` | 调用此方法的账户 ID | +| 当前账户 | `env.GetCurrentAccountId()` | 此智能合约的账户 ID | +| 签名者 | `env.GetSignerAccountID()` | 签署导致本次执行的交易的账户 ID | +| 附加存款 | `env.GetAttachedDeposit()` | 前驱附加给调用的 yoctoNEAR 金额 | +| 账户余额 | `env.GetAccountBalance()` | 此智能合约的余额(包括附加存款) | +| 预付 Gas | `env.GetPrepaidGas()` | 可用于执行的 gas 数量 | +| 时间戳 | `env.GetBlockTimeMs()` | 当前时间戳(自 1970 年 1 月 1 日 0:00:00 UTC 起的非闰纳秒数) | +| 当前纪元 | `env.GetEpochHeight()` | 区块链中的当前纪元 | +| 区块索引 | `env.GetCurrentBlockHeight()` | 当前区块索引(即区块高度) | +| 已用存储 | `env.GetStorageUsage()` | 此智能合约当前使用的存储字节数 | +| 已用 Gas | `env.GetUsedGas()` | 执行已使用的 gas 数量 | +| 签名者公钥 | `env.GetSignerAccountPK()` | 发送者公钥 | +| 账户锁定余额 | `env.GetAccountLockedBalance()` | 此智能合约被锁定的余额 | + + + + + +--- + +## 谁在调用?我是谁? + +环境让您可以访问 3 个重要用户:`current_account`(当前账户)、`predecessor`(前驱)和 `signer`(签名者)。 + +### 当前账户 + +`current_account` 包含您的合约部署的地址。这对于实现所有权非常有用,例如使公共方法只能由合约本身调用。 + +### 前驱与签名者 + +`predecessor` 是调用合约中方法的账户。而 `signer` 是_签署_初始交易的账户。 + +在简单交易(没有[跨合约调用](../anatomy/crosscontract))中,`predecessor` 与 `signer` 相同。例如,如果 **alice.near** 调用 **contract.near**,从合约的角度来看,**alice.near** 既是 `signer` 也是 `predecessor`。但是,如果 **contract.near** 创建了一个[跨合约调用](../anatomy/crosscontract),那么 `predecessor` 就会沿链发生变化。在下面的示例中,当 **pool.near** 执行时,它会将 **contract.near** 视为 `predecessor`,将 **alice.near** 视为 `signer`。 + +![img](https://miro.medium.com/max/1400/1*LquSNOoRyXpITQF9ugsDpQ.png) +*您可以访问与您的智能合约交互的用户的信息* + + +在大多数情况下,您**只需要了解前驱**。但是,在某些情况下签名者非常有用。例如,在将 [NFT](../../primitives/nft/nft) 添加到[此市场](https://github.com/near-examples/nft-tutorial/blob/7fb267b83899d1f65f1bceb71804430fab62c7a7/market-contract/src/nft_callbacks.rs#L42)时,合约会检查 `signer`(即生成交易链的人)是否是 NFT 所有者。 + + +--- + +## 余额与附加的 NEAR +环境让您可以访问 3 个与代币相关的参数,均以 yoctoNEAR 表示(1 Ⓝ = 1024yⓃ): + +### 附加存款 +`attached_deposit` 表示前驱附加到调用中的 yoctoNEAR 金额。 + +该金额**已存入**您合约的账户,如果您的**方法 panic**,会**自动退还**给 `predecessor`。 + + +如果您进行[跨合约调用](../anatomy/crosscontract)且它 panic,资金会发送回**您的合约**。请参阅[回调部分](../anatomy/crosscontract#what-happens-if-the-function-i-call-fails)了解如何处理此情况。 + + +### 账户余额 + +`account_balance` 表示您合约(`current_account`)的余额。 + +它包含 `attached_deposit`,因为在方法执行开始时已经存入。 + +如果合约有任何锁定的 `$NEAR`,它将出现在 `account_locked_balance` 中。 + +--- + +### 已用存储 + +`storage_used` 表示您的合约当前使用的[存储](../anatomy/storage)量。 + + +如果您想知道某个结构使用了多少存储,在存储之前和之后打印存储量即可。 + + +--- + +## 计时 + +环境提供了三种不同的方式来表示时间流逝,每种方式代表底层区块链的不同维度。 + +### 时间戳 + +`timestamp` 属性表示此调用执行时以**纳秒**为单位的近似 **UNIX 时间戳**。它以人类可理解的方式量化时间流逝,使我们能够检查特定日期是否已过。 + +### 当前纪元 + +NEAR 区块链将区块分组为[纪元](../../protocol/network/epoch)。`current_epoch` 属性衡量到目前为止经过了多少纪元。它对于与其他以纪元衡量时间的合约(如[验证者](../../protocol/network/validators))进行协调非常有用。 + +### 区块索引 + +`block_index` 表示此交易将被添加到区块链的区块的索引。 + +--- + +## Gas + +您的合约在每次调用时有**有限的计算资源**可用。这些资源以 [Gas](/protocol/transactions/gas) 来衡量。 + +Gas 可以理解为墙上时间,1 PetaGas(1_000 TGas)大约相当于 1 秒的计算时间。 + +每条代码指令都消耗一定数量的 Gas,如果耗尽,执行将停止并显示错误消息 `Exceeded the prepaid gas`。 + +环境让您可以访问两个 gas 相关参数:`prepaid_gas` 和 `used_gas`。 + +### 预付 Gas +`prepaid_gas` 表示 `predecessor` 附加到此调用的 Gas 数量。不能超过 300TGas(300 * 1012 Gas)的限制。 + +### 已用 Gas +`used_gas` 包含到目前为止已使用的 Gas 数量。用于估算运行方法的 Gas 成本。 + + +在[跨合约调用](./crosscontract)期间,始终确保回调有足够的 Gas 来完全执行。 + + + + +如果您已经[估算了方法需要的 Gas](../../protocol/transactions/gas#estimating-costs-for-a-call),可以使用 `assert` 确保其永远不会耗尽 Gas。 + + + + +```rust +const REQUIRED_GAS: Gas = Gas(20_000_000_000_000); // 20 TGas +assert!(env::prepaid_gas() >= REQUIRED_GAS, "Please attach at least 20 TGas"); +``` + + + + + +```python +from near_sdk_py import call, Context + +@call +def check_gas(required_gas=20_000_000_000_000): # 20 TGas + if Context.prepaid_gas() < required_gas: + raise Exception(f"Please attach at least {required_gas} gas") + return "Sufficient gas attached" +``` + + + + + + + +--- + +## 环境函数 + +除了环境变量外,SDK 还暴露了一些执行基本密码学操作的函数。 + + + + +| 函数名称 | SDK 方法 | 描述 | +|------------------------|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SHA 256 | `env::sha256(value)` | 使用 sha256 对字节序列进行哈希。 | +| Keccak 256 | `env::keccak256(value)` | 使用 keccak256 对字节序列进行哈希。 | +| Keccak 512 | `env::keccak512(value)` | 使用 keccak512 对字节序列进行哈希。 | +| SHA 256(数组) | `env::sha256_array(value)` | 使用 SHA-256 哈希函数对字节进行哈希。返回 32 字节哈希。 | +| Keccak 256(数组) | `env::keccak256_array(value)` | 使用 Keccak-256 哈希函数对字节进行哈希。返回 32 字节哈希。 | +| Keccak 512(数组) | `env::keccak512_array(value)` | 使用 Keccak-512 哈希函数对字节进行哈希。返回 64 字节哈希。 | +| RIPEMD 160(数组) | `env::ripemd160_array(value)` | 使用 RIPEMD-160 哈希函数对字节进行哈希。返回 20 字节哈希。 | +| EC Recover | `env::ecrecover(hash, signature, v, malleability_flag)` | 从 32 字节消息 `hash` 和相应的 `signature` 以及 `v` 恢复字节中恢复 ECDSA 签名者地址。接受一个额外的标志来检查签名的可延展性,这通常只对交易是理想的。如果恢复成功,返回 64 字节表示公钥。 | +| Panic String | `env::panic_str(message)` | 以 UTF-8 编码的消息终止程序执行。 | +| Log String | `env::log_str(message)` | 记录字符串消息。该消息存储在链上。 | +| 验证者质押 | `env::validator_stake(account_id)` | 返回给定账户的当前质押。如果该账户不是验证者,返回 0。 | +| 验证者总质押 | `env::validator_total_stake()` | 返回当前纪元中验证者的总质押。 | + + + + + +| 函数名称 | SDK 方法 | 描述 | +|------------------------|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SHA 256 | `near.sha256(value)` | 使用 sha256 对字节序列进行哈希。 | +| Keccak 256 | `near.keccak256(value)` | 使用 keccak256 对字节序列进行哈希。 | +| Keccak 512 | `near.keccak512(value)` | 使用 keccak512 对字节序列进行哈希。 | +| RIPEMD 160 | `near.ripemd160(value)` | 使用 RIPEMD-160 哈希函数对字节进行哈希。 | +| EC Recover | `near.ecrecover(hash, sig, v, malleabilityFlag)` | 从 32 字节消息 `hash` 和相应的 `signature` 以及 `v` 恢复字节中恢复 ECDSA 签名者地址。接受一个额外的标志来检查签名的可延展性,这通常只对交易是理想的。如果恢复成功,返回 64 字节表示公钥。 | +| Log String | `near.log(msg)` | 记录字符串消息。该消息存储在链上。 | +| 验证者质押 | `near.validatorStake(accountId)` | 返回给定账户的当前质押。如果该账户不是验证者,返回 0。 | +| 验证者总质押 | `near.validatorTotalStake()` | 返回当前纪元中验证者的总质押。 | + + + + + +| 函数名称 | SDK 方法 | 描述 | +|------------------------|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SHA 256 | `near.sha256(value)` | 使用 sha256 对字节序列进行哈希。 | +| Keccak 256 | `near.keccak256(value)` | 使用 keccak256 对字节序列进行哈希。 | +| Keccak 512 | `near.keccak512(value)` | 使用 keccak512 对字节序列进行哈希。 | +| Log Info | `Log.info(message)` | 记录信息消息。该消息存储在链上。 | +| Log Warning | `Log.warning(message)` | 记录警告消息。该消息存储在链上。 | +| Log Error | `Log.error(message)` | 记录错误消息。该消息存储在链上。 | +| Log Event | `Log.event(event_type, data)` | 记录遵循 NEP 事件标准的结构化事件。这对索引器和前端应用程序很有用。 | +| 抛出异常 | `raise Exception(message)` | 以错误消息终止函数执行。类似于其他语言中的 panic。 | + + + + +| 函数名称 | SDK 方法 | 描述 | +|------------------------|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SHA 256 | `env.Sha256Hash(value)` | 使用 sha256 对字节序列进行哈希。 | +| Keccak 256 | `env.Keccak256Hash(value)` | 使用 keccak256 对字节序列进行哈希。 | +| Keccak 512 | `env.Keccak512Hash(value)` | 使用 keccak512 对字节序列进行哈希。 | +| SHA 256 | `env::Sha256Hash(value)` | 使用 SHA-256 哈希函数对字节进行哈希。返回 32 字节哈希。 | +| RIPEMD 160 | `env::Ripemd160Hash(value)` | 使用 RIPEMD-160 哈希函数对字节进行哈希。返回 20 字节哈希。 | +| EC Recover | `env.EcrecoverPubKey(hash, signature, v, malleability_flag)` | 从 32 字节消息 `hash` 和相应的 `signature` 以及 `v` 恢复字节中恢复 ECDSA 签名者地址。接受一个额外的标志来检查签名的可延展性,这通常只对交易是理想的。如果恢复成功,返回 64 字节表示公钥。 | +| Panic String | `env.PanicStr(message)` | 以 UTF-8 编码的消息终止程序执行。 | +| Log String | `env.LogString(message)` | 记录字符串消息。该消息存储在链上。 | +| 验证者质押 | `env.ValidatorStakeAmount(account_id)` | 返回给定账户的当前质押。如果该账户不是验证者,返回 0。 | +| 验证者总质押 | `env.ValidatorTotalStakeAmount()` | 返回当前纪元中验证者的总质押。 | + + + + + + +在 JS SDK 中,`throw new Error("message")` 模拟了 Rust 的 `env::panic_str("message")` 的行为。 + + +--- diff --git a/zh/smart-contracts/anatomy/functions.mdx b/zh/smart-contracts/anatomy/functions.mdx new file mode 100644 index 00000000000..d07e7f73a74 --- /dev/null +++ b/zh/smart-contracts/anatomy/functions.mdx @@ -0,0 +1,366 @@ +--- +title: 外部接口 +hide_table_of_contents: true +description: "了解如何定义合约的接口。" +mode: wide +--- + +import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' + +智能合约暴露函数以便用户与之交互。有不同类型的函数,包括 `只读`、`私有` 和 `可支付` 函数。 + + + + + +### 合约接口 + +合约中所有**公共**函数都是其**接口**的一部分。任何人都可以调用它们,它们是与合约交互的唯一方式。 + + + + + + + +函数也可以通过 trait 实现来暴露。如果要为合约实现共享接口或标准,这会很有用。代码生成的处理方式与基本 `pub` 函数非常相似,但 `#[near]` 宏只需要附加到 trait 实现上,而不是 trait 本身: + +```rust +pub trait MyTrait { + fn trait_method(&mut self); +} + +#[near] +impl MyTrait for MyContractStructure { + fn trait_method(&mut self) { + // .. method logic here + } +} +``` + + + + + + + +### 初始化函数 + +合约可以选择有一个初始化函数。如果存在,必须在任何其他函数之前调用此函数以[初始化合约](./storage)。 + + + + + +#### `@initialize({ privateFunction: true })` + +初始化函数用 `@initialize` 装饰器标记。 + + + + + +#### `#[init]` + +初始化函数用 `#[init]` 宏标记。 + + + + + +#### `@init` + +在 Python 中,初始化函数用 `@init` 装饰器标记。 + + + + + +### 状态变更函数 + +修改[状态](./storage)或执行[操作](./actions)的函数需要由具有 NEAR 账户的用户调用,因为执行它们需要一个交易。 + + + + + +#### `@call` + +状态变更函数用 `@call` 装饰器标记。 + + + + + +#### `&mut self` + +在 Rust 中,状态变更函数是那些接受对 `self` 的**可变**引用的函数。 + + + + + +#### `@call` + +在 Python 中,状态变更函数用 `@call` 装饰器标记。 + + + + + + + +SDK 提供了[上下文信息](./environment),例如哪个账户在调用函数,或当前时间。 + + + + + + + +### 只读函数 + +合约的函数可以是只读的,意味着它们不修改状态。调用它们对所有人都是免费的,不需要有 NEAR 账户。 + + + + + +#### `@view` + +在 TS/JS 中,只读函数用 `@view` 装饰器标记。 + + + + + +#### `&self` + +在 Rust 中,只读函数是那些接受对 `self` 的**不可变**引用的函数。 + + + + + +#### `@view` + +在 Python 中,只读函数用 `@view` 装饰器标记。 + + + + + +### 私有函数 + +很多时候您会希望有一些函数作为合约接口的一部分**被暴露**,但**不应直接由用户调用**。 + +除初始化函数外,[跨合约调用的回调](./crosscontract)应始终是 `private` 的。 + +这些函数在合约代码中标记为 `private`,只能由合约本身调用。 + + + + + +#### `decorator({privateFunction: true})` + +私有函数通过在 `@call` 或 `@initialize` 装饰器中设置 `privateFunction: true` 来标记。 + + + + + +#### [#private] + +在 Rust 中,私有函数使用 `#[private]` 宏标记。 + + + + + +#### Python 中的私有函数 + +在 Python 中,您可以使用 `@callback` 装饰器创建回调,该装饰器专门用于处理跨合约调用的结果。对于只应由合约本身调用的一般私有函数,您可以在函数内部使用验证来检查调用者是否是合约本身。 + + + + + +### 可支付函数 + +默认情况下,如果用户向调用附加了 NEAR 代币,函数将会 panic。接受 NEAR 代币的函数必须标记为 `payable`。 + +在函数内部,用户可以访问[附加存款](./environment)。 + + + + + +#### `@call({payableFunction: true})` + +可支付函数通过在 `@call` 装饰器中设置 `payableFunction: true` 来标记。 + + + + + +#### [#payable] + +在 Rust 中,可支付函数使用 `#[payable]` 宏标记。 + + + + + +#### 在 Python 中处理支付 + +在 Python 中,您需要使用 Context API 手动检查存款。没有专门的装饰器用于可支付函数,因此您需要在函数代码中验证存款金额。 + + + + + +### 内部函数 + +到目前为止我们涵盖的所有函数都是接口的一部分,意味着它们可以被外部参与者调用。 + +但是,合约也可以有私有内部函数——例如辅助函数或工具函数——这些函数**不对外界暴露**。 + +要在 JS 合约中创建内部私有方法,只需省略 `@view` 和 `@call` 装饰器。 + + + + + +### 内部函数 + +到目前为止我们涵盖的所有函数都是接口的一部分,意味着它们可以被外部参与者调用。 + +但是,合约也可以有私有内部函数——例如辅助函数或工具函数——这些函数**不对外界暴露**。 + +要在 Rust 合约中创建内部私有方法,不要将其声明为公共的(`pub fn`)。 + + + + + +### 内部函数 + +到目前为止我们涵盖的所有函数都是接口的一部分,意味着它们可以被外部参与者调用。 + +但是,合约也可以有私有内部函数——例如辅助函数或工具函数——这些函数**不对外界暴露**。 + +要在 Python 合约中创建内部私有方法,只需定义没有 `@view`、`@call` 或 `@init` 装饰器的普通方法。 + + + + + + + +另一种不导出方法的方式是有一个独立的 `impl Contract` 部分,它没有标记 `#[near]`。 + +```rust +#[near] +impl Contract { + pub fn increment(&mut self) { + self.internal_increment(); + } +} +impl Contract { + /// 此方法仍然不会被导出。 + pub fn internal_increment(&mut self) { + self.counter += 1; + } +} +``` + + + + + + + +### 纯函数 + +纯函数是一种特殊类型的函数,不需要从状态中访问数据。 + +它们对于在合约上返回硬编码值很有用。 + + + + + + + + + + + +```js +@NearBindgen({}) +class Contract { + helper_function(params... ){ + // 此函数无法从外部调用 + } + + @view({}) + interface_view(params...){ + // 此函数可以从外部调用 + } + + @call({privateFunction: true}){ + // 此函数可以从外部调用,但 + // 只能由合约账户调用 + } +} +``` + + + + + +```rs +const SOME_VALUE: u64 = 8; + +#[near] +impl MyContractStructure { + fn internal_helper(&mut self, params... ){ + // 此函数无法从外部调用 + } + + pub fn public_log(/* Parameters here */) { + near_sdk::log!("inside log message"); + } + + pub fn return_static_u64() -> u64 { + SOME_VALUE + } +} +``` + + + + + +```python +class Contract: + def helper_function(self, params...): + # 此函数无法从外部调用 + # 因为它没有装饰器 + pass + + @call + def validate_owner(self): + if Context.predecessor_account_id() != self.owner_id: + raise Exception("Only the owner can call this method") + + @view + def get_static_value(self): + # 此函数返回硬编码值 + return 42 +``` + + + + diff --git a/zh/smart-contracts/anatomy/reduce-size.mdx b/zh/smart-contracts/anatomy/reduce-size.mdx new file mode 100644 index 00000000000..b9e668ff722 --- /dev/null +++ b/zh/smart-contracts/anatomy/reduce-size.mdx @@ -0,0 +1,314 @@ +--- +title: 减小合约大小 +description: "了解减小 NEAR 智能合约大小的策略,以优化部署和性能。" +--- +import {Github} from '/snippets/github.jsx'; + +在本页面中,我们将探讨减小 NEAR 上智能合约大小的策略。这对于希望优化合约部署的开发者特别有用,尤其是在合约大小限制是一个问题的场景中。 + +# 减小合约大小 + + + + +## 建议与示例 + +本页面面向熟悉底层概念并希望显著减小合约大小的开发者,可能以代码可读性为代价。 + +以下是一些这种方法可能有用的常见场景: + +- 打算与账户管理绑定的合约 +- 使用工厂部署的合约 +- 类似 NEAR 上 EVM 的未来进展 + +编译合约时,有一些可能会向合约大小添加不必要字节的因素。其中一些可能更容易换成其他方法,而另一些则需要更多关于系统调用的内部知识。 + +## 小优化 + +### 使用编译标志 + +编译合约时,确保将标志 `-C link-arg=-s` 传递给 Rust 编译器: + +```bash +RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release +``` + +以下是我们在大多数示例中 `Cargo.toml` 中使用的参数: + +```toml +[profile.release] +codegen-units = 1 +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true +``` + +您可能想尝试使用 `opt-level = "z"` 代替 `opt-level = "s"` 来查看是否会生成更小的二进制文件。详见 [The Cargo Book Profiles section](https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level)。您也可以参考此 [Shrinking .wasm Size](https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) 资源。 + +### 从清单文件中删除 `rlib` + +确保您的清单文件(`Cargo.toml`)不包含 `rlib`,除非需要。一些 NEAR 示例中包含了以下内容: + + + +**添加了不必要的膨胀** + +```toml +[lib] +crate-type = ["cdylib", "rlib"] +``` + + + + 当它可以是: + + + +```toml +[lib] +crate-type = ["cdylib"] +``` + + +3. 使用 Rust SDK 时,您可以覆盖默认的 JSON 序列化,改用 [Borsh](https://borsh.io)。有关更多信息和示例,请[参见此页面](./serialization-interface#overriding-serialization-protocol-default)。 +4. 使用断言或守卫时,避免使用标准 `assert` 宏,如 [`assert!`](https://doc.rust-lang.org/std/macro.assert.html)、[`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html) 或 [`assert_ne!`](https://doc.rust-lang.org/std/macro.assert_ne.html),因为这些可能会添加关于错误行号的信息导致膨胀。`unwrap`、`expect` 和 Rust 的 `panic!()` 宏也存在类似问题。 + + 标准断言的示例: + + + + **添加了不必要的膨胀** + + ```rust + assert_eq!(contract_owner, predecessor_account, "ERR_NOT_OWNER"); + ``` + + + + 当它可以是: + + + + ```rust + if contract_owner != predecessor_account { + env::panic(b"ERR_NOT_OWNER"); + } + ``` + + + 删除 `expect` 的示例: + + + + **添加了不必要的膨胀** + + ```rust + let owner_id = self.owner_by_id.get(&token_id).expect("Token not found"); + ``` + + + + 当它可以是: + + + + ```rust + fn expect_token_found(option: Option) -> T { + option.unwrap_or_else(|| env::panic_str("Token not found")) + } + let owner_id = expect_token_found(self.owner_by_id.get(&token_id)); + ``` + + + 更改标准 `panic!()` 的示例: + + + + **添加了不必要的膨胀** + + ```rust + panic!("ERR_MSG_HERE"); + ``` + + + + 当它可以是: + + + + ```rust + env::panic_str("ERR_MSG_HERE"); + ``` + + +## 可直接使用的脚本 +我们准备了一个简单的 `bash` 脚本,可用于缩小 `.wasm` 合约文件。您可以在[此处](https://github.com/near/near-sdk-rs/blob/master/minifier/minify.sh)找到它。 + +当前缩小方法如下: +1. 使用 `wasm-snip` 将标准库中一些已知的大型函数(如浮点格式化和 panic 相关的)替换为 unreachable 指令。 +2. 运行 `wasm-gc` 以消除从被替换函数可达的所有函数。 +3. 使用 `wasm-strip` 删除不需要的部分,例如名称。 +4. 运行 `binaryen wasm-opt`,清理剩余部分。 + +### 运行脚本的要求: +- 使用 Cargo 安装 [wasm-snip](https://docs.rs/wasm-snip/0.4.0/wasm_snip/) 和 [wasm-gc](https://docs.rs/crate/wasm-gc/0.1.6): +```bash +cargo install wasm-snip wasm-gc +``` +- 在系统上安装 [binaryen](https://github.com/WebAssembly/binaryen) 和 [wabt](https://github.com/WebAssembly/wabt)。对于 Ubuntu 和其他基于 Debian 的 Linux 发行版,运行: +```bash +apt install binaryen wabt +``` + + + +缩小可能相当激进,因此缩小后必须测试合约。独立的 NEAR 运行时可能对此有帮助,参见[这里](https://github.com/nearprotocol/nearcore/tree/master/runtime/near-vm-runner)。 + + + +## 底层方法 + +对于最小化合约的 `no_std` 方法,请参考以下示例: + +- [Tiny contract](https://github.com/near/nearcore/tree/1e7c6613f65c23f87adf2c92e3d877f4ffe666ea/runtime/near-test-contracts/tiny-contract-rs) +- [NEAR ETH Gateway](https://github.com/ilblackdragon/near-eth-gateway/blob/master/proxy/src/lib.rs) +- [此 YouTube 视频](https://youtu.be/Hy4VBSCqnsE),Eugene 在其中演示了 `no_std` 模式下的同质化代币。[此示例的代码在此处](https://github.com/near/core-contracts/pull/88)。 +- [使用 `nesdie` 项目的示例](https://github.com/austinabell/nesdie/tree/main/examples)。 +- 请注意,Aurora 已成功使用 [rjson](https://crates.io/crates/rjson) 作为轻量级 JSON 序列化 crate。它比目前与 Rust SDK 打包的 [serde](https://crates.io/crates/serde) 占用更小。请参见 Aurora 仓库中的[此 rjson 示例](https://github.com/aurora-is-near/aurora-engine/blob/65a1d11fcd16192cc1bda886c62005c603189a24/src/json.rs#L254),不过实现细节需要读者自行了解,这里不作展开。[此 nesdie 示例](https://github.com/austinabell/nesdie/blob/bb6beb77e32cd54077ac54bf028f262a9dfb6ad0/examples/multisig/src/utils/json/vector.rs#L26-L30)也使用了 [miniserde crate](https://crates.io/crates/miniserde),这是那些选择避免使用 Rust SDK 的人的另一个选项。 + + +**系统调用信息** + + + + + + + + + + + + +## 优化 Python 合约 + +由于 NEAR 上的 Python 智能合约是解释执行而非直接编译为 WebAssembly,优化策略与 Rust 合约不同。Python 解释器本身已经经过优化,但您仍然可以使合约更高效。 + +### 减小合约大小 + +#### 1. 最小化依赖 + +只导入您需要的模块和函数: + +```python +# 效率较低 - 导入所有内容 +from near_sdk_py import * + +# 效率较高 - 只导入需要的内容 +from near_sdk_py import view, call, Context +``` + +#### 2. 使用高效的数据结构 + +为您的需求选择正确的数据结构: + +```python +# 查找效率较低 +user_list = [] # O(n) 查找时间 +for user in user_list: + if user["id"] == user_id: + return user + +# 查找效率较高 +user_dict = {} # O(1) 查找时间 +if user_id in user_dict: + return user_dict[user_id] +``` + +#### 3. 优化存储使用 + +NEAR SDK 集合针对链上存储进行了优化。对于大型数据集,使用这些集合代替原生 Python 集合: + +```python +# 原生集合 - 完全加载到内存中 +self.users = {} # 每次方法调用时完全加载 + +# SDK 集合 - 延迟加载 +from near_sdk_py.collections import UnorderedMap +self.users = UnorderedMap("u") # 只加载需要的内容 +``` + +#### 4. 减少字符串操作 + +字符串操作可能很昂贵。尽量减少: + +```python +# 效率较低 +result = "" +for i in range(100): + result += str(i) # 创建许多中间字符串 + +# 效率较高 +parts = [] +for i in range(100): + parts.append(str(i)) +result = "".join(parts) # 只创建一次字符串 +``` + +#### 5. 避免递归 + +深度递归可能导致栈溢出问题。考虑使用迭代方法: + +```python +# 递归 - 对深层结构可能有问题 +def process_tree(node): + result = process_node(node) + for child in node.children: + result += process_tree(child) + return result + +# 迭代 - 效率更高 +def process_tree(root): + result = 0 + queue = [root] + while queue: + node = queue.pop(0) + result += process_node(node) + queue.extend(node.children) + return result +``` + +#### 6. 使用 Python 内置函数 + +Python 的内置函数通常经过优化且效率更高: + +```python +# 效率较低 +sum_value = 0 +for num in numbers: + sum_value += num + +# 效率较高 +sum_value = sum(numbers) +``` + +#### 7. 拆分复杂逻辑 + +如果您有一个非常大的合约,考虑将其拆分为多个具有专注职责的合约。这可以提高可维护性并降低单个调用的成本。 + +#### 8. 分析并优化热路径 + +将优化工作集中在最频繁调用的函数或处理大量数据的函数上: + +```python +@call +def frequently_called_method(self, data): + # 仔细优化此代码 + # 考虑使用更高效的算法和数据结构 + pass +``` + + + diff --git a/zh/smart-contracts/anatomy/reproducible-builds.mdx b/zh/smart-contracts/anatomy/reproducible-builds.mdx new file mode 100644 index 00000000000..cfc6fbd9a5b --- /dev/null +++ b/zh/smart-contracts/anatomy/reproducible-builds.mdx @@ -0,0 +1,108 @@ +--- +title: 可重现构建 +description: "在不同开发者环境中创建相同的构建。" +--- +可重现构建允许不同的人构建同一个程序并获得完全相同的输出。它们帮助用户相信已部署的合约是正确构建的,并与源代码对应。为了验证您的合约,用户可以自行构建并检查二进制文件是否相同。 + +## 问题 + + + + +如果您在两台不同的机器上构建合约,很可能会得到两个相似但不完全相同的二进制文件。您的构建产物可能受到语言区域、时区、构建路径以及构建环境中数十亿其他因素的影响。因此,Rust 社区有着解决此问题的悠久历史。 + +为了实现可重现构建,NEAR 利用了多个组件,如 [NEP-330-Source Metadata](https://github.com/near/NEPs/blob/master/neps/nep-0330.md)、[cargo-near](https://github.com/near/cargo-near)、[Docker](https://docker.com) 和 [SourceScan](https://github.com/SourceScan)。 + + +为了使用可重现构建,您需要在机器上安装 [Docker](https://docker.com)。 + + +使用 `cargo near new` 初始化项目时,生成的 `Cargo.toml` 将包含有关构建环境和存储库的信息。 + +```toml +# ... +repository = "https://github.com//" +# ... +[package.metadata.near.reproducible_build] +image = "sourcescan/cargo-near:0.13.5-rust-1.85.1" +image_digest = "sha256:3b0272ecdbb91465f3e7348330d7f2d031d27901f26fb25b4eaf1560a60c20f3" +passed_env = [] +container_build_command = [ + "cargo", + "near", + "build", + "non-reproducible-wasm", + "--locked", +] +# ... +``` + +使用 `cargo near deploy` 部署时,此信息用于克隆存储库并在 Docker 容器中编译合约。 + +编译过程将向合约添加方法 `contract_source_metadata`,**不对合约代码或逻辑做任何更改**,该方法将返回结果元数据。 + +合约部署后,我们可以通过调用方法 `contract_source_metadata` 查看元数据,例如: + +```zsh +near view contract_source_metadata + + INFO --- Result ------------------------- + | { + | "build_info": { + | "build_command": [ + | "cargo", + | "near", + | "build", + | "non-reproducible-wasm", + | "--locked" + | ], + | "build_environment": "sourcescan/cargo-near:0.13.5-rust-1.85.1@sha256:3b0272ecdbb91465f3e7348330d7f2d031d27901f26fb25b4eaf1560a60c20f3", + | "contract_path": "", + | "output_wasm_path": null, + | "source_code_snapshot": "" + | }, + | "link": "", + | "standards": [ + | { + | "standard": "nep330", + | "version": "1.3.0" + | } + | ], + | "version": "0.1.0" + | + | ------------------------------------ +``` + +## 验证与发布 +为了验证和发布您的合约代码,您可以使用 [NearBlocks](https://nearblocks.io) 触发验证过程。导航到合约账户页面,在 **Contract** 选项卡下,您将看到 **Verify and Publish** 按钮。验证过程完成后,合约的源代码以及元数据将在 NearBlocks 的 `Contract -> Contract Code` 选项卡上公开访问。 + +![reproducible-build](/assets/docs/smart-contracts/reproducible-build.png) + +有关如何验证合约的分步指南,请查看[验证指南](https://github.com/SourceScan/verification-guide)。 + +## 其他资源 + +- [验证 NEAR 上的智能合约:分步指南](https://github.com/SourceScan/verification-guide) +- [工具社区活动 #10 - 介绍可重现构建(视频)](https://youtu.be/RBIAcQj7nFs?t=1742) + + + + +对于 Python 智能合约,可重现性问题相对较少,因为 Python SDK 不直接编译为 WebAssembly。相反,Python 代码在嵌入在合约运行时的 Python-to-WASM 解释器中执行。 + +当您部署 Python 智能合约时,您直接部署 Python 源代码(或轻度处理的形式),而不是编译后的二进制文件。这使部署过程更加确定性,因为构建环境因素影响最终产物的机会更少。 + +### 确保 Python 合约可重现 + +为确保您的 Python 合约是可重现的: + +1. **固定您的依赖项**:如果您的合约使用任何第三方库,请确保指定确切版本。 + +2. **使用一致的格式**:使用 Black 或 YAPF 等工具确保代码格式一致。 + +3. **记录 Python 版本**:确保记录您的合约是使用哪个 Python 版本开发的。 + +4. **避免系统特定代码**:不要依赖可能在不同操作系统上表现不同的功能。 + + + diff --git a/zh/smart-contracts/anatomy/serialization-interface.mdx b/zh/smart-contracts/anatomy/serialization-interface.mdx new file mode 100644 index 00000000000..2775d0509d1 --- /dev/null +++ b/zh/smart-contracts/anatomy/serialization-interface.mdx @@ -0,0 +1,151 @@ +--- +title: 序列化协议 +description: "了解智能合约用于序列化数据的协议。" +--- + +import {Github} from '/snippets/github.jsx'; + +SDK 中的序列化格式定义了数据结构如何转换为字节,这些字节用于将数据传递到智能合约的方法中或在状态中存储数据。对于方法参数,SDK 支持 [JSON](https://www.json.org/json-en.html)(默认)和 [Borsh](https://borsh.io/),对于在链上存储数据则使用 Borsh。 + +JSON 和 Borsh 的特性如下: + +JSON: +- 人类可读 +- 自描述格式(不需要知道底层类型) +- 与 JavaScript 的互操作性好 +- 大小和(反)序列化效率较低 + +Borsh: +- 紧凑的二进制格式,对序列化数据大小有效 +- 需要知道数据格式或有 schema 才能反序列化数据 +- 严格且规范的二进制表示 +- 大多数情况下速度快且开销少 + +一般来说,JSON 用于合约调用和跨合约调用以获得更好的开发体验,而 Borsh 可用于通过减小参数序列化大小和降低合约内的反序列化计算来优化 gas 使用。 + +### 覆盖序列化协议默认值 + +结果和参数序列化可以分别选择,但所有参数必须采用相同格式(不能将某些参数序列化为 borsh,其他参数序列化为 JSON)。将结果和参数都切换到 borsh 的示例如下: + +```rust +#[result_serializer(borsh)] +pub fn sum_borsh(#[serializer(borsh)] a: u32, #[serializer(borsh)] b: u32) -> u32 { + a + b +} +``` + +`result_serializer(borsh)` 注解将覆盖默认的结果序列化协议从 JSON 到 borsh,`serializer(borsh)` 注解将覆盖参数序列化。 + +#### 示例 + +从单元测试中获取 [Borsh 序列化](https://borsh.io)的 base64 编码值的简单演示: + + + +以下代码片段显示了一个简单函数,从前端或 CLI 接受此值。注意:此方法没有返回值,因此不需要 `#[result_serializer(borsh)]`。 + + + +请注意,这使用了这个简单结构体: + + + +使用 NEAR CLI 调用时,使用类似以下的命令: + +```bash +near contract call-function as-transaction rust-status-message.demo.testnet set_status_borsh base64-args 'DAAAAEFsb2hhIGhvbnVhIQ==' prepaid-gas '30 TeraGas' attached-deposit '0 NEAR' sign-as demo.testnet network-config testnet sign-with-keychain send +``` + +详情请参见[此 GitHub gist](https://gist.github.com/mfornet/d8a94af333a68d67affd8cb78464c7c0),由 [Marcelo](https://gist.github.com/mfornet) 提供。 + +### JSON 包装类型 + +为了帮助将某些具有意外或低效默认格式的类型序列化为 JSON,[`near_sdk::json_types`](https://docs.rs/near-sdk/3.1.0/near_sdk/json_types/index.html) 中有一些包装类型可供使用。 + +由于 JavaScript 仅支持最大值为 `2^53 - 1` 的整数,如果反序列化超过此范围的 JSON 整数,您将失去精度。为了解决此问题,您可以使用 `I64`、`U64`、`I128` 和 `U128` 代替这些参数或结果的原生类型,以将值序列化为字符串。默认情况下,所有整数类型都将在 JSON 中序列化为整数。 + +您可以使用 `std::convert::Into` 在 `U64` 和 `u64` 之间转换,例如: + +```rust +#[near] +impl Contract { + pub fn mult(&self, a: U64, b: U64) -> U128 { + let a: u64 = a.into(); + let b: u64 = b.into(); + let product = u128::from(a) * u128::from(b); + product.into() + } +} +``` + +您也可以使用 `.0` 访问内部值: + +```diff + #[near] + impl Contract { + pub fn mult(&self, a: U64, b: U64) -> U128 { +- let a: u64 = a.into(); ++ let a = a.0; +- let b: u64 = b.into(); ++ let b = b.0; + let product = u128::from(a) * u128::from(b); + product.into() + } + } +``` + +您可以使用 `U64(...)` 和 `U128(...)` 将小写 `u` 变体转换为大写 `U` 变体: + +```diff + #[near] + impl Contract { + pub fn mult(&self, a: U64, b: U64) -> U128 { + let a = a.0; + let b = b.0; + let product = u128::from(a) * u128::from(b); +- product.into() ++ U128(product) + } + } +``` + +组合在一起: + +```rust +#[near] +impl Contract { + pub fn mult(&self, a: U64, b: U64) -> U128 { + U128(u128::from(a.0) * u128::from(b.0)) + } +} +``` + +虽然 SDK 中包含了这些 JSON 包装类型,但可以使用任何自定义类型,只要它分别实现了 [`serde`](https://serde.rs/) 的序列化和反序列化。所有这些类型只是覆盖 JSON 格式,内部类型的 `borsh` 序列化和反序列化将保持一致。 + +### Base64VecU8 + +另一个您可能想要覆盖默认序列化的类型示例是 `Vec`,它在 Rust 中表示字节。默认情况下,这会序列化为整数数组,既不紧凑也难以使用。有一个包装类型 [`Base64VecU8`](https://docs.rs/near-sdk/3.1.0/near_sdk/json_types/struct.Base64VecU8.html),它序列化和反序列化为 [Base-64](https://en.wikipedia.org/wiki/Base64) 字符串,以实现更紧凑的 JSON 序列化。 + +示例如下: + +```rust +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + // 注意,内部我们存储 `Vec` + pub data: Vec, +} + +#[near] +impl Contract { + #[init] + pub fn new(data: Base64VecU8) -> Self { + Self { + data: data.into(), + } + } + pub fn get_data(self) -> Base64VecU8 { + self.data.into() + } +} +``` diff --git a/zh/smart-contracts/anatomy/serialization.mdx b/zh/smart-contracts/anatomy/serialization.mdx new file mode 100644 index 00000000000..2c0ce89de3f --- /dev/null +++ b/zh/smart-contracts/anatomy/serialization.mdx @@ -0,0 +1,239 @@ +--- +title: 序列化说明 +description: "了解合约如何为函数调用和存储序列化数据。" +--- + +智能合约需要能够以简单的方式**通信复杂数据**,同时也能高效地将这些数据**读取和存储**到状态中。 + +为了实现这种简单的通信和高效的存储,智能合约将数据从复杂表示转换为更简单的表示。 + +这个将**复杂对象转换为更简单的单值**表示的过程称为**序列化**。NEAR 使用两种序列化格式:[JSON](https://www.json.org/json-en.html) 和 [Borsh](https://borsh.io/)。 +1. [JSON](https://www.json.org/json-en.html) 用于在函数调用期间序列化合约的输入/输出 +2. [Borsh](https://borsh.io/) 用于序列化合约的状态 + +--- + +## 序列化格式概述 + +让我们快速概述两种序列化格式,包括它们的优缺点,以及序列化结果的示例。 + +
+ +### [JSON](https://www.json.org/json-en.html):对象转字符串 + +#### 特性 + - 自描述格式 + - 与 JavaScript 的互操作性好 + - 多种实现随时可用 + - 但是...在计算时间和结果大小方面效率不高 + +#### 示例 +```js +Example{ + number: i32 = 2; + arr: Vector = [0, 1]; +} + +// 序列化为 +"{\"number\": 2, \"arr\": [0, 1]}" +``` + +
+ +### [Borsh](https://borsh.io/):对象转字节 + +#### 特性 + - 紧凑的二进制格式,专为高效(反)序列化而构建 + - 严格且规范的二进制表示 + - 更少的开销:不需要存储属性名称 + - 但是...必须知道 schema 才能(反)序列化数据 + +#### 示例 +```js +Example{ + number: i32 = 2; + arr: Vector = [0, 1]; +} + +// 序列化为 +[2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] +``` +--- + +## 序列化输入与输出 +NEAR 合约可以实现接受和返回复杂对象的方法。 +为了以简单的方式处理这些数据,使用 JSON 序列化。 + +使用 JSON 使每个人都更容易与合约通信,因为大多数语言都提供了 JSON(反)序列化器。 + +#### 示例 +让我们看看这个仅出于教育目的而编写的示例: + +```rust +#[near(serializers = [json])] +pub struct A { + pub a_number: i32, + pub b_number: u128 +} + +#[near(serializers = [json])] +pub struct B { + pub success: bool, + pub other_number: i32 +} + +pub fn method(&self, struct_a: A): B { + B { + success: true, + other_number: 0 + } +} +``` + +#### 接收数据 +当用户调用 `method` 时,合约接收编码为 JSON 字符串的参数(例如 `"{\"a_number\":0, \"b_number\":\"100\"}"`),然后将其(反)序列化为正确的对象(`A{0, 100}`)。 + +#### 返回数据 +当返回结果时,合约将自动将对象 `B{true, 0}` 编码为其 JSON 序列化值:`"{\"success\":true, \"other_number\":0}"` 并返回此字符串。 + + +**JSON 限制** +由于 JSON 限制为 `52 字节` 数字,您不能将 `u64`/`u128` 用作输入或输出。JSON 无法序列化它们。相反,您必须使用 `Strings`。 + +`NEAR SDK RS` 目前实现了 `near_sdk::json_types::{U64, I64, U128, I128}`,您可以用于数据的输入/输出。 + + +--- + +## Borsh:状态序列化 + +在底层,智能合约使用简单的**键/值对**存储数据。这意味着合约需要将复杂状态转换为简单的键值对。 + +为此,NEAR 合约使用 [borsh](https://borsh.io),它被优化用于将复杂对象(反)序列化为更小的字节流。 + + +**SDK-JS 仍使用 json** +JavaScript SDK 使用 JSON 序列化状态中的对象,但 borsh 实现应该很快就会到来 + + +#### 示例 +让我们看看这个仅出于教育目的而编写的示例: + +```rust +#[near(serializers = [json, borsh])] +#[derive(PanicOnDefault)] +pub struct Contract { + string: String, + vector: Vector +} + +#[near] +impl Contract { + #[init] + pub fn init(string: String, first_u8: u8) -> Self { + let mut vector: Vector = Vector::new("prefix".as_bytes()); + vector.push(&first_u8); + + Self { string, vector } + } + + pub fn change_state(&mut self, string: String, number: u8) { + self.string = string; + self.vector.push(&number); + } +} +``` + +#### 部署时的空状态 +如果我们将合约部署到新账户并立即请求状态,我们将看到它是空的: + +```bash +near view-state $CONTRACT --finality optimistic + +# Result is: [] +``` + +#### 初始化状态 +如果我们初始化状态,可以看到 Borsh 如何用于序列化状态: + +```bash +# 使用字符串 "hi" 和 0 初始化 +near call $CONTRACT init '{"string":"hi", "first_u8":0}' --useAccount $CONTRACT + +# 检查状态 +near view-state $CONTRACT --utf8 --finality optimistic +``` + + + +```bash +[ + { + key: 'STATE', + value: '\x02\x00\x00\x00hi\x01\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00prefix' + }, + { key: 'prefix\x00\x00\x00\x00\x00\x00\x00\x00', value: '\x00' } +] +``` + + +第一个键值对是: + +```js +key: 'STATE' +value: '\x02\x00\x00\x00hi\x01\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00prefix' +``` + +由于 `Contract` 具有结构 `string, Vector`,值被解释为: + +```bash +[2, 0, 0, 0, "h", "i"] -> `string` 有 2 个元素:"h" 和 "i"。 +[1, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, "prefix"] -> Vector 有 1 个元素,要查看值,请搜索以(6 字节前缀)"prefix" 开头的键 +``` + +然后,第二个键值对显示由 `"prefix"` 字符串标记的 `Vector` 条目: + +```js +key: 'prefix\x00\x00\x00\x00\x00\x00\x00\x00' +value: '\x00' +``` + +#### 修改状态 +如果我们修改存储的字符串并添加新数字,状态会相应变化: + +```bash +near call $CONTRACT change_state '{"string":"bye", "number":1}' --useAccount $CONTRACT +``` + + + +```bash +[ + { + key: 'STATE', + value: '\x03\x00\x00\x00bye\x02\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00prefix' + }, + { key: 'prefix\x00\x00\x00\x00\x00\x00\x00\x00', value: '\x00' }, + { key: 'prefix\x01\x00\x00\x00\x00\x00\x00\x00', value: '\x01' } +] +``` + + +我们可以看到 `STATE` 键发生了变化以反映新字符串(`bye`)的存储,以及向量现在有 2 个元素。 + +同时,添加了一个新的键值对,添加了新的向量条目:我们刚刚添加的 `1u8`。 + +
+ +{/* We should see where to move/replicate this */} + +### 反序列化错误 +当有人调用智能合约方法时,合约的第一步是反序列化自己的状态。 + +在上面使用的示例中,合约将首先读取 `STATE` 键,并尝试将其值反序列化为对象 `Contract{string: String, vector: Vector}`。 + +如果您将具有不同合约结构的合约部署到该账户,那么合约将无法反序列化 `STATE` 键并 panic `Cannot deserialize the contract state`。 + +要解决此问题,您可以: +1. 回滚到之前的合约代码 +2. 实现一个[迁移合约状态](../release/upgrade)的方法 diff --git a/zh/smart-contracts/anatomy/storage.mdx b/zh/smart-contracts/anatomy/storage.mdx new file mode 100644 index 00000000000..9cb79c1603b --- /dev/null +++ b/zh/smart-contracts/anatomy/storage.mdx @@ -0,0 +1,171 @@ +--- +title: 状态 +sidebarTitle: 状态(存储) +hide_table_of_contents: true +description: "探索 NEAR 智能合约如何管理其状态。" +mode: wide +--- +import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' + +智能合约将数据存储在其账户的状态中,该状态在链上是公开的。存储从**空**开始,直到部署合约并初始化状态。 + +重要的是要知道,账户的**代码**和账户的**存储**是**独立的**。[更新代码](../release/upgrade)**不会清除**状态。 + + + + + + ### 定义状态 + 标记为合约的 `class` 的属性定义了将要存储的数据。 + + 合约可以存储所有原生类型(例如 `number`、`string`、`Array`、`Map`)以及复杂对象。 + + 例如,我们的拍卖合约存储拍卖结束时间以及代表最高出价的对象。 + + **注意:** SDK 还提供了[集合](./collections),用于高效存储数据集合。 + + + + + + ### 定义状态 + 标记为合约的 `struct` 的属性定义了将要存储的数据。 + + 合约可以存储所有原生类型(例如 `u8`、`string`、`HashMap`、`Vector`)以及复杂对象。 + + 例如,我们的拍卖合约存储拍卖结束时间以及代表最高出价的对象。 + + **注意:** 将要保存的结构体需要一个特殊的宏,告诉 SDK 以 [Borsh 序列化](./serialization)存储它们。 + + **注意:** SDK 还提供了[集合](./collections),用于高效存储数据集合。 + + + + + + + +合约通过锁定部分余额来支付存储费用。 + +目前,存储 **100KB** 数据的成本约为 **1Ⓝ**。 + + + + + + + + ### 初始化状态 + 合约部署后,其状态为空,需要使用一些初始值进行初始化。 + + 有两种初始化状态的方式: + 1. 创建初始化函数 + 2. 设置默认值 + + + + + + ### 一、初始化函数 + 初始化状态的一种选择是创建一个 `initialization` 函数,在执行任何其他函数之前需要先调用此函数。 + + 在我们的拍卖示例中,合约有一个初始化函数,用于设置拍卖结束时间。注意 `@initialization` 装饰器以及 `NearBindgen` 上的强制初始化。 + + **注意:** 将初始化函数标记为私有是一个好习惯。我们将在[函数部分](./functions)介绍函数类型。 + + + + + + + +在 Python 中,您需要明确管理状态初始化。SDK 不强制要求在其他方法调用之前进行初始化——如果需要,您需要自己添加检查。 + + + + + + + + ### 一、初始化函数 + 初始化状态的一种选择是创建一个 `initialization` 函数,在执行任何其他函数之前需要先调用此函数。 + + 在我们的拍卖示例中,合约有一个初始化函数,用于设置拍卖结束时间。合约派生了 `PanicOnDefault`,这迫使用户调用由 `#[init]` 宏标记的 init 方法。 + + **注意:** 将初始化函数标记为私有是一个好习惯。我们将在[函数部分](./functions)介绍函数类型。 + + + + + + ### 二、默认状态 + 初始化状态的另一种选择是为 class 的属性设置默认值。 + + 我们的 "Hello World" 合约就是这种情况,它存储了一个默认值为 `"Hello"` 的 `greeting`。 + + 第一次调用合约时(某人执行 `get_greeting` 或 `set_greeting`),默认值将存储到状态中,状态将被视为已初始化。 + + **注意:** 状态只能初始化一次。 + + + + + + ### 二、默认状态 + 初始化状态的另一种选择是为合约的 `struct` 创建一个 `Default` 版本。 + + 例如,我们的 "Hello World" 合约有一个默认状态,其中 `greeting` 设置为 `"Hello"`。 + + 合约第一次执行时,`Default` 将存储到状态中,状态将被视为已初始化。 + + **注意:** 状态只能初始化一次。 + + + + + + ### 状态的生命周期 + 当函数被调用时,合约的状态从存储中加载并放入内存。 + + 状态实际上是[序列化后存储](./serialization)的,SDK 需要花一点时间来反序列化它,然后方法才能访问它。 + + 当方法成功执行完成时,所有对状态的更改都会被序列化并保存回存储。 + + + + + + +**状态与代码** + +在 NEAR 中,合约的代码和合约的存储是**独立的**。 + +更新合约的代码**不会清除**状态,实际上可能导致意外行为或错误。 + +如果遇到问题,请务必阅读[更新合约](../release/upgrade)。 + + + + + + + + + + + + + + + + + + diff --git a/zh/smart-contracts/anatomy/types.mdx b/zh/smart-contracts/anatomy/types.mdx new file mode 100644 index 00000000000..0472eac1ee0 --- /dev/null +++ b/zh/smart-contracts/anatomy/types.mdx @@ -0,0 +1,168 @@ +--- +title: SDK 类型 +hide_table_of_contents: true +description: "了解 SDK 提供的一切,以高效存储数据。" +mode: wide +--- +import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' + +让我们讨论智能合约使用哪些类型来输入和输出数据,以及这些数据在合约代码中如何存储和处理。 + + + + + + ### 原生类型 + 智能合约可以使用 JS 原生类型接收、存储和返回数据: + - `string` + - `number` + - `boolean` + - `Array` + - `Map` + - `Object` + - `BigInt` + + + + + + ### 原生类型 + 智能合约可以使用以下 Rust 类型接收、存储和返回数据: + - `string` + - `i8-i32/u8-u32` + - **`u64/128`**:建议使用 SDK 类型 `U64` 和 `U128` + - `bool` + - `HashMap` + - `Vector` + + + + + + ### 原生类型 + 智能合约可以使用以下 Python 类型接收、存储和返回数据: + - `str` + - `int` + - `float` + - `bool` + - `list` + - `dict` + - `set` + - `bytes` + - `None` + + + + + + +**`U64/U128`** + +智能合约可以存储 `u64` 和 `u128`,但这些类型需要转换为 `string` 用于输入/输出,因为 JSON 无法序列化超过 52 位的值。 + +为了简化开发,SDK 提供了 `U64` 和 `U128` 类型,存储时自动转换为 `u64/u128`,用作输入/输出时转换为 `string`。 + + + + + + + + +**Python 大数字** + +Python 的 `int` 类型具有无限精度,因此它可以处理大整数(如 yoctoNEAR 值)而无需任何特殊处理。所有值都会自动正确地序列化和反序列化。 + + + + + + + + ### 复杂对象 + 智能合约可以存储和返回复杂对象 + + **注意:** 对象始终以 JSON 格式接收和返回 + + + + + + #### 序列化器 + 将用作输入或输出的对象需要序列化为 JSON,添加 `#[near(serializer=json)]` 宏 + + 将存储在合约状态中的对象需要序列化为 Borsh,添加 `#[near(serializer=borsh)]` 宏 + + + + + + #### 序列化 + Python 对象使用 JSON 自动序列化和反序列化用于输入/输出,使用 Pickle 用于内部存储。 + + 列表和字典等复杂嵌套对象无需额外配置即可直接使用。 + + + + + + ### 处理代币 + `$NEAR` 代币在 JS 中类型化为 `BigInt`,其值以 `yoctonear` 表示 + + **注意:** 1 NEAR = 10^24 yoctoNEAR + + + + + + ### 处理代币 + `$NEAR` 代币通过 `NearToken` 结构体处理,该结构体提供以 `yoctonear`、`milinear` 和 `near` 表示值的方法 + + **注意:** 1 NEAR = 10^24 yoctonear + + + + + + ### 处理代币 + 在 Python 中,`$NEAR` 代币表示为整数,值以 `yoctoNEAR` 为单位。 + `near_sdk_py.constants` 模块提供了 `ONE_NEAR` 和 `ONE_TGAS` 常量。 + + **注意:** 1 NEAR = 10^24 yoctoNEAR + + + + + + ### 账户 + SDK 提供了一种特殊类型来处理 NEAR 账户,它会自动检查账户地址是否有效 + + + + + + ### 账户 ID + 在 Python 中,NEAR 账户 ID 表示为 `str` 类型。当账户 ID 用于合约调用或跨合约交互时,SDK 会执行验证。 + + + + + + + + + + + + + + + diff --git a/zh/smart-contracts/anatomy/yield-resume.mdx b/zh/smart-contracts/anatomy/yield-resume.mdx new file mode 100644 index 00000000000..8ec3f671ce8 --- /dev/null +++ b/zh/smart-contracts/anatomy/yield-resume.mdx @@ -0,0 +1,352 @@ +--- +title: Yield 与 Resume +description: "等待外部响应并恢复执行" +--- + +import { Github } from '/snippets/github.jsx' + +NEAR 智能合约可以**暂停(yield)**执行,直到**外部**服务**恢复(resume)**它们。实际上,合约向自身发起一个**跨合约调用**并暂停,直到外部服务执行某个函数,然后合约决定恢复执行。 + +这是一个强大的特性,允许合约在继续执行之前等待外部事件,例如来自预言机的响应。 + + + +合约可以等待 200 个区块(约 2 分钟),之后暂停的函数将会执行,并收到"超时错误"作为输入。 + + + +--- + +## 暂停 Promise + +让我们看一个示例,该示例接受用户的提示(例如"2+2 是什么"),并暂停执行,直到外部服务提供响应。 + + + + + + + +```python +from near_sdk_py import call, view, near, Context +from near_sdk_py.collections import UnorderedMap +import json + +class YieldResumeContract: + def __init__(self): + # Store pending requests + self.requests = UnorderedMap("r") + self.request_id = 0 + + @call + def ask_assistant(self, prompt): + """ + Creates a yielded promise that will be resumed when an external service responds + + Args: + prompt: The question to ask the assistant + """ + # Create a new request ID + request_id = self.request_id + self.request_id += 1 + + # Create arguments for the callback function + callback_args = json.dumps({"request_id": request_id}) + + # Create a yielded promise to call return_external_response on this contract + yield_id = near.promise_create( + Context.current_account_id(), + "return_external_response", + callback_args, + 0, # No deposit + 30000000000000 # Gas + ) + + # Store the yield ID and prompt for the external service to find + self.requests[str(request_id)] = { + "yield_id": yield_id, + "prompt": prompt + } + + # Return the yield_id and request_id for tracking + return { + "yield_id": yield_id, + "request_id": request_id + } +``` + + + +#### 创建暂停的 Promise +在上面的示例中,我们创建了一个[`Promise`](./crosscontract#promises)来调用合约的函数 `return_external_response`。 + +注意,我们在 Rust 中使用 `env::promise_yield_create` 或在 Python 中使用 `near.promise_create` 来创建 `Promise`(Python SDK 使用标准 Promise 来实现暂停),这将在 `YIELD_REGISTER` 中为暂停的 Promise 创建一个**标识符**。 + +#### 获取暂停的 Promise ID +我们读取 `YIELD_REGISTER` 来获取暂停 Promise 的 `ID`。我们存储 `yield_id` 和用户的 `prompt`,以便外部服务可以查询它们(合约暴露了一个函数来列出所有请求)。 + +#### 返回 Promise +最后,我们返回 `Promise`,它**不会立即执行**,而是**暂停**,直到外部服务提供响应。 + + + +`self.request_id` 是一个内部唯一标识符,我们用它来跟踪存储的请求。这样,一旦外部服务提供了响应(或等待超时),我们就可以删除该请求。 + +由于我们只是用它来简化请求跟踪过程,如果您有其他方式跟踪请求(例如索引器),可以将其删除。 + + + +--- + +## 发送恢复信号 + +Rust 中的 `env::promise_yield_resume` 函数或 Python 中的 `near.promise_yield_resume` 允许我们指定哪个暂停的 Promise 应该执行,以及要传递给恢复函数的参数。 + + + + + + + +```python +@call +def respond(self, yield_id, response): + """Called by the external service to provide a response to a prompt""" + return near.promise_yield_resume(yield_id, json.dumps({"response": response})) +``` + + + +在上面的示例中,`respond` 函数将由外部服务调用,传入应恢复哪个 Promise(`yield_id`)以及对提示的响应。 + + +**保护恢复信号** + +由于用于发送恢复信号的函数是公开的,开发者必须确保对其进行适当的保护,以避免不必要的调用。这可以通过简单检查函数的调用者来实现。 + + + +--- + +## 恢复执行的函数 + +被恢复的函数将能够访问传递给它的所有参数,包括在创建 yield 时传递的参数,以及外部服务的响应。 + + + + + + + +```python +@call +def return_external_response(self, request_id, response=None): + """ + Function that gets called when the yielded promise resumes + + Args: + request_id: The ID of the request (passed when promise was created) + response: The response from the external service (or None if timed out) + """ + # Remove the request from our tracking + request_data = self.requests.get(str(request_id)) + if request_data: + del self.requests[str(request_id)] + + # Handle timeout case + if response is None: + return {"error": "Timeout waiting for external service", "request_id": request_id} + + # Return the response from the external service + return { + "request_id": request_id, + "response": response, + "success": True + } +``` + + + +在上面的示例中,`return_external_response` 接收以下参数: + +1. `request_id` - 在[创建时](#创建暂停的-promise)传递 - 用于从状态中删除请求 +2. `response` - 在[发送恢复信号时](#发送恢复信号)传递 - 包含外部响应,如果合约等待超时则为 `None` + + +**有足够的时间** + +合约可以等待 200 个区块(约 2 分钟)然后超时。 + + + + + +请注意,在此特定示例中,无论是有响应还是超时,我们都选择返回一个值。 + +不抛出错误的原因是,我们正在更改状态(在第 `#7` 行删除请求),而抛出错误会回滚此状态更改。 + + + +--- + +## 管理状态 + +使用 yield 和 resume 时,务必小心管理合约状态。由于其异步执行特性,合约 yield 和 resume 所在的函数是独立的。 + +如果您在 yield Promise 的函数中(这里是 `request` 函数)更改了合约状态,那么在恢复的函数中(这里是 `return_external_response` 函数),如果 Promise 超时或响应无效,您需要确保回滚状态。 + +最佳实践是在发送恢复信号的函数中(这里是 `respond` 函数)检查响应的有效性,如果响应无效则 panic;外部服务可以在 Promise 超时之前再次尝试响应。不应该在 `return_external_response` 中 panic,因为它只在 Promise 已经解决时调用(已恢复或已超时),这意味着它无法再次被恢复,并且 `request` 中的状态已经确定。您应该优雅地完成函数并回滚状态。 + + + +查看有关[回调安全](../security/callbacks)和[重入攻击](../security/reentrancy)的更多文档,以避免处理异步调用时的常见陷阱。 + + + +--- + +## 完整示例 + +以下是 Python 中 yield-resume 模式的更完整实现: + + + +```python +from near_sdk_py import view, call, near, Context +from near_sdk_py.collections import UnorderedMap +import json + +class AIAssistantContract: + def __init__(self): + # Track all pending requests + self.requests = UnorderedMap("r") + self.request_counter = 0 + + @call + def ask_question(self, question): + """ + Ask a question to the AI assistant + + The execution will yield until an external AI service responds + """ + # Create a unique ID for this request + request_id = self.request_counter + self.request_counter += 1 + + # Create callback args - will be passed to our callback function + callback_args = json.dumps({ + "request_id": request_id + }) + + # Create the promise - this will yield until resumed + promise_id = near.promise_create( + Context.current_account_id(), # Call this contract + "process_ai_response", # Call this method when resumed + callback_args, # Pass these arguments + 0, # No attached deposit + 30000000000000 # Gas for execution (30 TGas) + ) + + # Store the request for the external service to find + self.requests[str(request_id)] = { + "prompt": question, + "promise_id": promise_id, + "user": Context.predecessor_account_id(), + "timestamp": Context.block_timestamp() + } + + return { + "request_id": request_id, + "status": "processing" + } + + @view + def get_pending_requests(self): + """Returns all pending requests for the AI service to process""" + return [ + { + "request_id": int(req_id), + "data": self.requests[req_id] + } + for req_id in self.requests.keys() + ] + + @call + def provide_ai_response(self, request_id, response): + """ + Called by the AI service to provide a response + + Args: + request_id: ID of the request being answered + response: The AI's response to the question + """ + request_id_str = str(request_id) + + # Verify the request exists + if request_id_str not in self.requests: + raise Exception(f"No pending request with ID {request_id}") + + # Get the request data + request = self.requests[request_id_str] + + # Resume the promise with the AI's response + result = near.promise_yield_resume( + request["promise_id"], + json.dumps({"ai_response": response}) + ) + + return {"success": result} + + @call + def process_ai_response(self, request_id, ai_response=None): + """ + Called when a yielded promise resumes + + This is either called by provide_ai_response or by a timeout + + Args: + request_id: ID of the request + ai_response: The AI's response or None if timed out + """ + request_id_str = str(request_id) + + # Cleanup - remove from pending requests + if request_id_str in self.requests: + request = self.requests[request_id_str] + del self.requests[request_id_str] + else: + request = None + + # Handle timeout case + if ai_response is None: + return { + "request_id": request_id, + "status": "timeout", + "message": "The AI service did not respond in time" + } + + # Return the AI's response + return { + "request_id": request_id, + "status": "complete", + "question": request["prompt"] if request else "Unknown", + "answer": ai_response, + "user": request["user"] if request else "Unknown" + } +``` + + + +此示例演示了 AI 助手合约的完整 yield-resume 模式,其中: + +1. 用户通过 `ask_question` 提问 +2. 合约创建一个暂停的 Promise 并存储问题 +3. 外部 AI 服务定期使用 `get_pending_requests` 检查新问题 +4. 当 AI 有答案时,它调用 `provide_ai_response` 来恢复暂停的 Promise +5. `process_ai_response` 函数以 AI 的答案(或超时信息)执行并返回结果 diff --git a/zh/smart-contracts/global-contracts.mdx b/zh/smart-contracts/global-contracts.mdx new file mode 100644 index 00000000000..890f735d709 --- /dev/null +++ b/zh/smart-contracts/global-contracts.mdx @@ -0,0 +1,99 @@ +--- +title: 全局合约 +description: "一次部署合约,跨账户复用。" +--- +全局合约允许智能合约只需部署一次,即可被任何账户使用,而无需承担高昂的存储成本。 +开发者无需重复部署合约或在分片间低效地路由消息,现在可以以模块化和通用的方式思考:一次编写,到处使用。 + +## 概述 + +如果您曾经将相同的合约代码部署到多个账户,您可能已经注意到每次部署都需要再次支付全额存储费用——因为 WASM 文件的大小决定了在账户上锁定多少 `NEAR`。 +借助 [NEP-0591](https://github.com/near/NEPs/blob/master/neps/nep-0591.md)(引入了全局合约),NEAR 提供了一种高度战略性的替代方案,[优雅地解决了这个问题](#solution)。 + +## 主要特性 + +以下是使全局合约与众不同的特性: + +- **全局寻址**:这些合约不绑定到特定账户,而是使用唯一的全局标识符。这使得 NEAR 上的任何合约、用户或应用程序都可以从任何分片即时调用该合约。 + +- **不可变逻辑**:合约代码一旦部署即固定,成为可信的参考点。这确保了一致性和安全性——非常适合系统关键协议。 + +- **共享基础设施**:全局合约可以作为规范库、实用中心或其他合约依赖的标准,简化开发并减少重复。 + +- **跨分片超级能力**:开发者可以构建真正模块化的应用程序,其中堆栈的各个部分位于不同的分片上,但通过共享的全局逻辑进行通信,延迟和重复最小。 + +### 使用场景 + +- **标准库**:用于数学、字符串操作或代币接口的可复用组件。 +- **DeFi 协议**:全局合约可以锚定 DEX、借贷市场、预言机——跨所有应用程序共享。 +- **DAO 框架**:任何 DAO 都可以接入的共享治理模块,确保一致性和可靠性。 +- **身份与凭证**:一个全局合约可以为整条链管理去中心化身份验证和访问管理。 +- **多部分 dApp**:复杂应用程序可以将职责分散在各个分片上,同时访问共同的逻辑核心。 + +## 解决方案 + +全局合约通过允许相同的合约代码在多个账户间共享,从而只需支付一次存储费用,解决了重复部署的低效问题。 + +引用全局合约有两种方式: +- **[按账户](#reference-by-account)**:可升级的合约在特定账户 ID 下全局发布。 +- **[按哈希](#reference-by-hash)**:不可变合约全局部署并通过其代码哈希标识。 + + +- 合约代码分布在 Near Protocol 网络的所有分片上,而非存储在任何特定账户的存储中。 +- 账户部署全局合约的费用是普通部署的 10 倍,费率为每 100KB 10 NEAR。 +- 此金额完全被销毁,之后无法恢复,这与普通部署中 Near 只是被锁定不同。 +- 用户使用全局合约的总费用通常低于 0.001 NEAR,因为账户存储中只需存储几个字节的引用。 + + +### 按账户引用 + +使用**按账户**引用时,合约代码与另一个账户绑定。如果该账户后来部署了新版本的合约,您的账户将自动开始使用更新后的代码,无需重新部署。 + +### 按哈希引用 + +使用**按哈希**引用时,您通过不可变的代码哈希引用全局合约。这确保您始终使用完全相同的版本,除非您明确使用不同的哈希重新部署,否则它永远不会改变。 + +## 何时使用全局合约 + +在决定如何部署合约之前,请自问以下问题: + +- **_您是否在本地环境中工作?_** + + 如果您只是测试或构建原型,[常规部署](/smart-contracts/release/deploy)更简单、更灵活。无需销毁代币或注册全局引用——直接部署并迭代。 + +- **_合约是否应该部署在多个账户上?_** + + 如果相同的合约将在许多独立账户上复用——比如 10 个或更多——全局合约可以显著降低整体成本和复杂性。但如果只涉及少数账户,常规部署仍然是更经济的选择。 + +- **_这些账户是否由您的团队管理?_** + + 如果所有目标账户都在您的基础设施下,您可能更倾向于使用常规部署以获得灵活性和成本回收。 +- **_账户数量是否超过 10 个?_** + + 全局合约在大规模复用时才具有财务效益。如果您要将同一合约部署到超过 10 个账户,可能值得考虑。 + +- **_您是否需要在一步中升级多个账户的合约,即使需要销毁代币?_** + + 如果您希望能够一次性将更新推送到所有已部署实例,请选择按账户 ID 的全局合约,但请记住部署成本是不可退还的。 + +- **_您的用例是否要求合约永久不可变?_** + + 如果合约绝对不能更改(例如出于安全、合规或用户信任的原因),则使用按代码哈希的全局合约可在协议级别确保不可变性。 + +## 部署全局合约 + +全局合约可以通过 2 种方式部署:按[哈希](#reference-by-hash)或按所有者[账户 ID](#reference-by-account)。 +按哈希部署的合约实际上是不可变的,无法更新。 +按账户 ID 部署时,所有者可以重新部署合约,为所有用户更新它。 + +全局合约可以使用 [`NEAR CLI`](./tutorials/factories/global-contracts#deployment) 或通过代码使用 [NEAR APIs](../tools/near-api#deploy-a-global-contract) 部署。请查看[此教程](./tutorials/factories/global-contracts)了解如何使用 CLI、JavaScript 或 Rust 部署和使用全局合约。 + + +部署全局合约会产生较高的存储成本。代币被销毁以补偿链上存储合约的费用,这与常规合约中代币根据合约大小被锁定不同。 + + +### 关于合约更新 + +如果合约预计会不时进行升级,则应使用 `账户 ID` 策略进行部署。使用按 `哈希` 的全局合约适用于预期永远保持不变的不可变合约。 + +请记住,使用按[账户 ID](#reference-by-account) 的全局合约时,负责合约更新的是原始账户,因此他们也负责合约状态的升级。 diff --git a/zh/smart-contracts/quickstart.mdx b/zh/smart-contracts/quickstart.mdx new file mode 100644 index 00000000000..8be9a26a3ae --- /dev/null +++ b/zh/smart-contracts/quickstart.mdx @@ -0,0 +1,599 @@ +--- +title: 您的第一个智能合约 +sidebarTitle: 快速入门 +description: "使用您喜欢的语言创建您的第一个合约。" +--- + +import {Github} from '/snippets/github.jsx'; + +欢迎![NEAR 账户](../protocol/accounts-contracts/account-model)可以存储被称为智能合约的小型应用程序。在本快速教程中,我们将指导您在 NEAR **测试网**上创建您的第一个合约! + +让我们一起创建一个友好的拍卖合约,允许用户出价、追踪最高出价者并在拍卖结束时领取代币。 + + +**我应该使用哪种语言?** + +在本教程中,我们提供了用多种语言创建合约的说明。 + +我们建议将 **Rust** 用于生产应用,因为其工具链更成熟。但是,如果您只是在原型设计或学习阶段,可以随意使用 **JavaScript**、**Python** 或 **Go**! + + + + + + 想直接跳入代码而无需设置本地开发环境? + + 查看 [NEAR Playground](https://nearplay.app/),这是一个易于使用的在线 IDE,带有预配置的模板。 + + ![NEAR Playground](/assets/docs/smart-contracts/NEAR-Playground.png) + + [![在 NearPlay 中尝试计数器示例](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/3450a8a0-57dc-4d3a-b5d0-7bed58a0c2a9) + + + +--- + +## 前提条件 + +开始之前,请确保设置好您的开发环境。 + + + + +```bash +# 安装 Rust: https://www.rust-lang.org/tools/install +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# 合约将被编译为 wasm,因此我们需要添加 wasm 目标 +rustup target add wasm32-unknown-unknown + +# 安装 NEAR CLI-RS 以部署合约并与之交互 +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + +# 安装 cargo near 以帮助构建合约 +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh +``` + + + + + +```bash +# 使用 nvm 安装 Node.js(更多选项请参考: https://nodejs.org/en/download) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +nvm install latest + +# ⚠️ 仅适用于 Mac Silicon 用户,编译合约需要 Rosetta +# /usr/sbin/softwareupdate --install-rosetta --agree-to-license + +# 安装 NEAR CLI 以部署合约并与之交互 +npm install -g near-cli-rs@latest +``` + + + + + + ```bash +# 安装 Python(如果尚未安装) +# 使用系统包管理器或从 https://www.python.org/downloads/ 下载 + +# 安装 Emscripten(编译 Python 合约为 WebAssembly 所需) +# 适用于 Linux/macOS: +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +./emsdk install latest +./emsdk activate latest +source ./emsdk_env.sh +# 永久添加到 .bashrc 或 .zshrc: +# echo 'source "/path/to/emsdk/emsdk_env.sh"' >> ~/.bashrc +cd .. + +# 适用于 Windows: +# 下载并解压: https://github.com/emscripten-core/emsdk +# 然后在命令提示符中: +# cd emsdk +# emsdk install latest +# emsdk activate latest +# emsdk_env.bat + +# 验证安装: +emcc --version + +# 安装 uv 用于 Python 包管理 +curl -LsSf https://astral.sh/uv/install.sh | sh + +# 安装 NEAR CLI-RS 以部署合约并与之交互 +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh +```text + + + + +```bash +#适用于 Linux arm/x64 +sudo apt update && sudo apt upgrade -y +sudo apt install -y build-essential curl wget git libssl-dev pkg-config checkinstall +sudo apt install bison + +#适用于 Mac +xcode-select --install +brew update +brew install mercurial +brew install binaryen + +bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) +gvm install go1.25.4 -B +gvm use go1.25.4 --default + +curl -LO https://github.com/vlmoon99/near-cli-go/releases/latest/download/install.sh && bash install.sh + +``` + + + + +--- + +## 创建合约 + +使用以下脚手架工具之一创建智能合约,并按照说明操作: + + + + +```bash +cargo near +``` + +![img](/assets/docs/smart-contracts/hello-near-rs.gif) +_使用 `cargo near new` 创建项目_ + + + + + +```bash +npx create-near-app@latest +``` + +![img](/assets/docs/smart-contracts/hello-near-ts.gif) +_使用 `npx create-near-app@latest` 创建项目_ + + + +当提示选择模板时,选择基本的 `Auction` 模板以生成拍卖合约脚手架 + + + + + + + + +Python 快速入门教程即将推出! + +同时,请查看 [hello-near](https://github.com/near-examples/hello-near-examples/tree/main/contract-py) 示例。 + + + + + + + + +在本教程中,我们选择将项目命名为 `auction`,但您可以随意使用任何您喜欢的名称 + + + +--- + +## 合约结构 + +拍卖智能合约允许用户出价、追踪最高出价者并在拍卖结束时领取代币。 + +让我们来看看合约的不同组成部分,以及它们如何协同工作来实现这一功能。 + +### 合约状态与初始化 + +合约存储最高出价、拍卖结束时间、拍卖师地址以及用于追踪是否已领取收益的标志。为了设置这些参数,提供了一个 `init` 函数,必须首先调用该函数来初始化合约状态。 + + + + + + + + + + + + + + + +### 出价 + +为了出价,用户需要在调用 `bid` 函数时附上代表其出价金额的存款。 + +`bid` 函数将验证拍卖是否正在进行,如果用户出价高于已存储的金额,它将记录新出价并退还给前一个出价者。 + + + + + + + + + + + + + + + +### 领取收益 + +拍卖结束后,任何用户都可以调用 `claim` 将获胜出价转移给拍卖师: + + + + + + + + + + + + + + + +### 查询方法 + +用户随时可以通过调用查询方法(`get_highest_bid`、`get_auction_end_time`、`get_auctioneer`、`get_claimed`)查看拍卖的当前状态: + + + + + + + + + + + + + + + +--- + +## 测试合约 + +让我们通过运行测试来确认合约按预期工作。只需运行 `test` 命令,合约将被编译并部署到本地沙盒进行测试: + + + + + ```bash + cargo test + ``` + + + + + + ```bash + npm run test + ```` + + + + 确保您使用的是 `node v24 / 22 / 20`,如果您使用的是搭载 Apple Silicon 的 Mac,请确保已安装 `Rosetta` + + + + + + + + ```bash + uv run pytest + ``` + + + + +请随意查看测试文件,了解它们如何与合约交互。简而言之,会创建一个本地 NEAR 沙盒,部署合约,并调用不同的方法来验证预期行为。 + +--- + +## 构建并部署合约 + +现在我们知道测试通过了,让我们部署合约!首先,我们需要将其编译为 WebAssembly: + + + + + ```bash + cargo near build non-reproducible-wasm + ``` + + + + + + ```bash + npm run build + ``` + + + + + + ```bash + # 通过 uv 执行器使用 nearc 构建(无需安装) + uvx nearc contract.py + ``` + + 上述命令将把您的 Python 合约编译为可部署到 NEAR 区块链的 WebAssembly (WASM)。 + + + + 默认的 `nearc` 构建配置适用于大多数合约。我们使用 `uvx` 直接运行,无需单独安装 nearc。 + + + + :::important + + 此步骤需要在您的 `PATH` 中安装并可访问 [Emscripten](https://emscripten.org/)。如果在编译过程中遇到错误,请使用 `emcc --version` 验证 Emscripten 是否正确安装。 + + 常见编译错误及解决方案: + - `emcc: command not found` - Emscripten 不在您的 PATH 中。运行 `source /path/to/emsdk/emsdk_env.sh` 临时添加。 + - `error: invalid version of emscripten` - 您的 Emscripten 版本可能太旧。尝试使用 `./emsdk install latest && ./emsdk activate latest` 更新。 + - `Could not find platform micropython-dev-wasm32` - 这通常意味着 Emscripten 安装不完整或未正确激活。 + + ::: + + + + + ```bash + near-go build + ``` + + + **Near Go SDK 构建过程** + + 1. 主包中的所有代码(包括来自其他模块的导入)被合并到单个 **generated_build.go** 文件中。 + 2. **generated_build.go** 文件通过 **TinyGo** 编译为 `wasm32-unknown-unknown`。 + + + + + **自定义构建** + + 默认的 `near-go build` 命令适用于大多数标准项目,将当前目录中的源代码编译为 `main.wasm`。 + + 但是,如果您想指定自定义输出名称或**检查中间粘合代码**(生成的 JSON 序列化和 SDK 集成包装器)以便调试,可以使用以下可用标志: + + ```bash + near-go build --output my_contract.wasm --keep-generated + ``` + + + + + + +### 创建账户 + +现在让我们创建一个 NEAR 账户来部署合约: + +```bash +# 将 替换为您的合约账户名称 +near create-account --useFaucet +``` + + +**已有测试网账户?** + +如果您已经有一个 `testnet` 账户并想使用它,可以使用命令 `near login` 登录。 + + + + + +在 `WSL` 或任何其他无头 Linux 环境中工作时,您可能会在尝试创建账户时遇到问题,因为 `cli` 会尝试将密钥保存到系统的密钥链中。 + +在这种情况下,您可以尝试以下命令创建账户: + +```bash +near account create-account sponsor-by-faucet-service autogenerate-new-keypair save-to-legacy-keychain network-config testnet create +``` + + + +### 部署! + +合约准备好后,我们现在可以将其部署到之前创建的 `testnet` 账户: + + + + ```bash + near deploy ./target/near/auction.wasm + ``` + + + + ```bash + near deploy ./build/auction.wasm + ``` + + + + + ```bash + near deploy ./auction.wasm + ``` + + + + +**恭喜!** 您的合约现在已部署在 NEAR 测试网络上。 + +--- + +## 与合约交互 + +要与已部署的智能合约交互,您可以通过命令行调用其函数。 + +#### 初始化合约 +让我们通过设置拍卖结束时间和资金接收人(拍卖师)来初始化拍卖: + +```bash +# 获取从现在起 5 分钟后的时间戳(以纳秒为单位) +FIVE_MINUTES_FROM_NOW=$(( $(date +%s%N) + 5 * 60 * 1000000000 )) + +# 初始化拍卖 +near call init "{\"end_time\": \"$FIVE_MINUTES_FROM_NOW\", \"auctioneer\": \"influencer.testnet\"}" --useAccount +``` + + +您可以随意将 `influencer.testnet` 替换为任何有效的测试网账户——这是获胜出价将被发送的地址 + + +#### 出价 + +现在我们可以通过调用 `bid` 方法并附上一些 NEAR 存款来在拍卖中出价。每次出价时,最高出价和出价者信息将被记录在合约的[存储](./anatomy/storage)中。 + +```bash +# 创建一个新账户来出价 +near create-account --useFaucet + +# 出价 0.01 NEAR +near call bid '{}' --deposit 0.01 --useAccount +``` + + + +请注意,在这种情况下,我们使用 `` 账户(记得重命名!)来调用 `bid` 函数,同时附上 `0.01` NEAR 作为我们的出价 + + + +#### 查询最高出价 + +`get_highest_bid` 函数只从合约状态读取,因此不需要交易或签名: + +```bash +near view get_highest_bid '{}' +``` + + + + ```json + { + "bidder": "", + "amount": "10000000000000000000000" + } + ``` + + + + +您可以随意创建任意数量的出价者账户并出更多价,看看最高出价如何变化! + + + +#### 领取 + +拍卖结束后,任何人都可以调用 `claim` 方法,将`最高出价`金额转移给拍卖师并结束拍卖。 + +```bash +near call claim '{}' --useAccount +``` + + +**谁赢了?** + +拍卖结束后,只需再次调用 `get_highest_bid` 方法即可确定最高出价者 + + + +--- + +## 常见问题 + +#### 主网怎么办? +您可以使用相同的命令将合约部署到主网,只需确保创建主网账户并在 `near` CLI 命令中使用 `--networkId mainnet` 标志。 + +#### 部署费用是多少? +部署合约的费用取决于合约大小,大约 1Ⓝ 对应 100Kb。 + +#### 部署后还能更新合约吗? +可以。使用 `near deploy ` 重新部署。账户保持不变,代码更新。 + +#### 我应该选择哪种语言? +构建生产应用时优先选择 **Rust**,因为它提供最成熟的工具链和最佳性能。否则,如果您只是在原型设计或学习,可以在 **JavaScript**、**Python** 或 **Go** 之间选择您喜欢的语言。 + +#### 如何不部署就进行测试? +所有语言都支持沙盒测试(如本指南所示)。测试在本地使用模拟的 NEAR 环境运行。 + +--- + +## 继续前进 + + + + 查看拍卖前端教程,了解如何构建一个与拍卖合约交互的简单 Web 应用 + + + 跟随拍卖 NFT 教程,为最高出价者颁发非同质化代币(NFT),并允许用户使用同质化代币(FT)出价 + + + 查看我们的合约结构页面,了解构成 NEAR 智能合约的不同组件 + + + +
+ + + +撰写本文时,示例使用以下版本: + +- node: `22.18.0` +- rustc: `1.86.0` +- near-cli-rs: `0.22.0` +- cargo-near: `0.16.1` +- Python: `3.13` +- near-sdk-py: `0.7.3` +- uvx nearc: `0.9.2` +- emscripten: `4.0.9`(Python 合约必需) + + diff --git a/zh/smart-contracts/release/deploy.mdx b/zh/smart-contracts/release/deploy.mdx new file mode 100644 index 00000000000..bc219e5de75 --- /dev/null +++ b/zh/smart-contracts/release/deploy.mdx @@ -0,0 +1,121 @@ +--- +title: 部署 +sidebarTitle: 部署与使用 +description: "将合约部署到网络。" +--- +当您的合约准备好后,您可以将其部署到 NEAR 网络供所有人使用。 + +让我们指导您如何使用 [NEAR CLI](../../tools/cli) 来部署合约并调用其方法。 + + +在此页面,我们只会介绍 NEAR CLI 的基础知识。有关更多信息,请访问 +[NEAR CLI 文档页面](../../tools/cli)。 + + +--- + +## 部署合约 + +借助 `NEAR CLI`,部署合约非常简单: + +1. 将合约编译为 wasm。 +2. [创建账户](../../tools/cli#create)并使用 `NEAR CLI` [将合约部署](../../tools/cli#deploy)到该账户。 + +### 编译合约 + + + + + ```bash + cargo near build + ``` + + + + + + ```bash + yarn build + ``` + + + + + +### 创建账户并部署 + +```bash +# 创建一个由水龙头预充值的新账户 +near create-account --useFaucet + +# 部署合约 +near deploy +``` + +### 部署到现有账户 + +```bash +# 登录您的账户 +near login + +# 部署合约 +near deploy +``` + + +您可以通过在现有合约上部署另一个合约来覆盖它。在这种情况下,账户的逻辑会改变,但状态会保留。 + + + +默认情况下,`near-cli` 使用 `testnet` 网络。定义 `NEAR_ENV=mainnet` 以部署到 `mainnet`。 + + + +**公开方法的命名约定** +一旦合约部署到网络,任何人和任何其他合约(即 NEAR 上的任何其他账户)都可以通过调用其方法与之交互。此外,涉及合约的任何交易也将包含在网络的数据流中,这意味着其活动也可以被监听特定事件的任何人看到。 + +考虑到这一点,我们建议在所有 SDK 中使用 `snake_case` 命名方法,因为这与 NEAR 生态系统的其余部分兼容,后者主要由 Rust 合约组成。 + + +--- + +## 初始化合约 +如果您的合约有一个[初始化方法](../anatomy/storage),您可以调用它来初始化状态。如果您的合约为状态实现了 `default` 值,则不需要这样做。 + +```bash +# 调用初始化方法(在我们的示例中是 `init`) +near call [] --useAccount +``` + + +您可以在[部署合约](#部署合约)时使用 `--initFunction` 和 `--initArgs` 参数来初始化合约。 + + +--- + +## 调用合约 + +部署合约后,您可以立即使用 [NEAR CLI](../../tools/cli) 与之交互。 + +
+ +### 视图方法 +视图方法是执行**只读**操作的方法。调用这些方法是免费的,不需要指定用于进行调用的账户: + +```bash +near view +``` + + +视图方法默认有 200 TGAS 可用于执行 + + +
+ +### 变更方法 + +变更方法是执行读写操作的方法。对于这些方法,我们需要指定用于进行调用的账户,因为该账户将在调用中消耗 GAS。 + +```bash +near call --useAccount [--deposit ] [--gas ] +``` diff --git a/zh/smart-contracts/release/lock.mdx b/zh/smart-contracts/release/lock.mdx new file mode 100644 index 00000000000..7cc916c861b --- /dev/null +++ b/zh/smart-contracts/release/lock.mdx @@ -0,0 +1,30 @@ +--- +title: 锁定账户 +description: "了解如何锁定 NEAR 智能合约以防止未经授权的修改,并在需要时确保合约不可变性。" +--- + +在 NEAR 中,您可以通过删除账户的所有[完全访问密钥](/tools/cli#delete-key)来锁定账户。这对于防止以账户名义执行任何进一步的交易非常有用,从而有效地锁定账户。 + +从账户中删除所有[完全访问密钥](/tools/cli#delete-key)将有效地**锁定它**。 + +当账户被锁定时,任何人都无法以该账户的名义执行交易(例如更新代码或转移资金)。 + +#### 如何锁定账户 + +```bash +near list-keys +# result: [access_key: {"nonce": ..., "public_key": ''}] + +near delete-key '' +``` + +#### 为什么要锁定账户 + +锁定账户可以给终端用户更多保障,因为他们知道没有外部参与者能够操纵账户的合约或余额。 + + +**升级锁定的合约** + +虽然没有外部参与者可以更新合约,但合约**仍然可以自我升级**,详情请参见[此文章](/smart-contracts/release/upgrade#programmatic-update)。 + + diff --git a/zh/smart-contracts/release/upgrade.mdx b/zh/smart-contracts/release/upgrade.mdx new file mode 100644 index 00000000000..117e74adf1b --- /dev/null +++ b/zh/smart-contracts/release/upgrade.mdx @@ -0,0 +1,255 @@ +--- +title: 更新合约 +description: "了解如何安全升级 NEAR 智能合约,包括程序化更新、迁移策略和合约版本控制最佳实践。" +--- + +import { Github } from '/snippets/github.jsx' + +了解如何通过 NEAR CLI 等工具以及以编程方式更新 NEAR 智能合约。了解更改合约逻辑时状态迁移的影响。 + +NEAR 账户将其逻辑(合约代码)与状态(存储)分开,允许更改代码。 + +合约可以通过两种方式更新: + +1. **通过工具**,如 [NEAR CLI](../../tools/cli) 或 + [NEAR API](../../tools/near-api)(如果您持有 + 账户的[完全访问密钥](../../protocol/accounts-contracts/access-keys))。 +2. **以编程方式**,通过实现一个 + [接受新代码并部署它](#programmatic-update)的方法。 + +--- + +## 通过工具更新 + +只需使用您首选的工具重新部署另一个合约,例如使用 +[NEAR CLI](../../tools/cli): + +```bash +# (可选)如果您没有账户,请创建一个 +near create-account --useFaucet + +# 部署合约 +near deploy +``` + +--- + +## 程序化更新 + +智能合约也可以通过实现一个方法来自我更新,该方法: + +1. 接受新的 wasm 合约作为输入 +2. 创建一个 Promise 将其部署到自身上 + + + + + + +#### 如何调用此方法? + + + + +```bash +# 调用 update_contract 方法 +near contract call-function as-transaction update_contract file-args prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send +``` + + + + + +```js +// 加载合约的原始字节 +const code = fs.readFileSync("./path/to/wasm.wasm"); + +// 调用 update_contract 方法 +await wallet.callFunction({ + contractId: guestBook, + method: "update_contract", + args: code, + gas: "300000000000000", +}); +``` + + + + + + +**DAO 工厂** + +这就是 DAO 工厂 +[更新其合约](https://github.com/near-daos/sputnik-dao-contract/blob/main/sputnikdao-factory2/src/factory_manager.rs#L60)的方式。 + + + +--- + +## 迁移状态 + +由于账户的逻辑(智能合约)与账户的状态(存储)分离,**重新部署合约时账户的状态会保留**。 + +因此,**添加方法**或**修改现有方法**不会产生**任何问题**。 + +但是,部署一个**修改或删除存储在状态中的结构**的合约将引发错误:`Cannot deserialize the contract state`,在这种情况下,您可以选择: + +1. 使用不同的账户 +2. 回滚到之前的合约代码 +3. 添加一个迁移合约状态的方法 + +
+ +### 迁移方法 + +如果您别无选择,必须迁移状态,那么您需要实现一个方法: + +1. 读取合约的当前状态 +2. 应用不同的函数将其转换为新状态 +3. 返回新状态 + + +**DAO 更新** + +这就是 DAO +[自我更新](https://github.com/near-daos/sputnik-dao-contract/blob/main/sputnikdao2/src/upgrade.rs#L59)的方式。 + + + +
+ +### 示例:留言板迁移 + +假设您有一个留言板,您在其中存储消息,用户可以为这些消息付费以使其成为"优质"消息。您使用以下状态跟踪消息和付款: + + + + + + + + +#### 更新合约 + +在某个时刻,您意识到可以将 `payments` 跟踪放在 `PostedMessage` 本身内,因此您将合约更改为: + + + + + + + + +#### 不兼容的状态 + +如果您将更新部署到已初始化的账户,合约将无法反序列化账户的状态,因为: + +1. 状态中保存了一个额外的 `payments` 向量(来自之前的合约) +2. 存储的 `PostedMessages` 缺少 `payment` 字段(如之前的合约中所示) + +#### 迁移状态 + +要解决这个问题,您需要实现一个方法,遍历旧状态,删除 `payments` 向量并将信息添加到 `PostedMessages`: + + + + + + + + +请注意,`migrate` 实际上是一个[初始化方法](../anatomy/storage),它**忽略**现有状态(`[#init(ignore_state)]`),因此能够执行并重写状态。 + + + +为了理解为什么要从状态中删除旧结构,让我们看看数据是如何存储的。 + +例如,如果旧版本的合约存储了两条带有付款的消息,则 `get_messages` 和 `get_payments` 方法将返回以下结果: + + + ```bash + INFO --- Result ------------------------- + | [ + | { + | "premium": false, + | "sender": "test-ac-1719933221123-3.testnet", + | "text": "Hello" + | }, + | { + | "premium": false, + | "sender": "test-ac-1719933221123-3.testnet", + | "text": "Hello" + | } + | ] + | ------------------------------------ + ``` + + + + ```bash + INFO --- Result ------------------------- + | [ + | "10000000000000000000000", + | "10000000000000000000000" + | ] + | ------------------------------------ + ``` + + +但是,如果我们使用以下命令以文本形式查看存储,我们将看到每个付款都以 `p\` 前缀存储在其自己的键下。 + +```bash +near contract view-storage all as-text network-config testnet now +``` + + + ```bash + INFO Contract state (values): + | key: STATE + | value: \x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00m\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00p + | -------------------------------- + | key: m\x00\x00\x00\x00\x00\x00\x00\x00 + | value: \x00\x1f\x00\x00\x00test-ac-1719933221123-3.testnet\x05\x00\x00\x00Hello + | -------------------------------- + | key: m\x01\x00\x00\x00\x00\x00\x00\x00 + | value: \x00\x1f\x00\x00\x00test-ac-1719933221123-3.testnet\x05\x00\x00\x00Hello + | -------------------------------- + | key: p\x00\x00\x00\x00\x00\x00\x00\x00 + | value: \x00\x00@\xb2\xba\xc9\xe0\x19\x1e\x02\x00\x00\x00\x00\x00\x00 + | -------------------------------- + | key: p\x01\x00\x00\x00\x00\x00\x00\x00 + | value: \x00\x00@\xb2\xba\xc9\xe0\x19\x1e\x02\x00\x00\x00\x00\x00\x00 + | -------------------------------- + ``` + + +这意味着在将状态迁移到新版本时,我们不仅需要更改消息结构,还需要从状态中删除所有与付款相关的键。否则,旧键将保留为孤立键,仍然占用空间。 + +在 `migrate` 方法中删除它们时,我们在可变的 `old_state` 结构中的 payments 向量上调用 `clear()` 方法。此方法从集合中删除所有元素。 + + + + + +您可以在 +[官方迁移示例](https://github.com/near-examples/update-migrate-rust/tree/main/basic-updates/base)中逐步跟踪迁移过程。 +JavaScript 迁移示例测试文件可以在这里找到: +[test-basic-updates.ava.js](https://github.com/near/near-sdk-js/blob/develop/examples/__tests__/test-basic-updates.ava.js), +通过在 examples 目录中运行命令 `pnpm run test:basic-update` 执行。 + + diff --git a/zh/smart-contracts/security/callbacks.mdx b/zh/smart-contracts/security/callbacks.mdx new file mode 100644 index 00000000000..a2477710f7b --- /dev/null +++ b/zh/smart-contracts/security/callbacks.mdx @@ -0,0 +1,109 @@ +--- +title: 跨合约调用 +description: "了解 NEAR 智能合约中的回调安全,包括正确的错误处理、状态管理以及防止回调相关漏洞。" +--- + +## 概述 + +NEAR 协议智能合约可以通过跨合约调用相互调用。这一强大功能使构建复杂的去中心化应用程序成为可能,通过将较小的合约组合在一起。但是,跨合约调用引入了开发者必须理解并正确实现的关键安全考虑因素。 + +--- + +## 基本原则:异步和独立调用 + +NEAR 中的所有跨合约调用都是**独立**且**异步**的。这意味着: + +- 发起跨合约调用的方法和处理响应的回调方法是**完全独立**的执行上下文 +- 在初始调用和回调执行之间,**任何人都可以与您的合约交互** - 其他用户可以调用任何公共方法 +- 调用和回调之间合约状态可能发生变化,从而产生潜在的竞争条件 + +**安全影响:** + +1. **回调访问控制**:回调方法必须是公共的才能接收响应,但应该只能由您的合约本身调用 +2. **状态管理**:切勿在调用和回调之间让合约处于可被利用或不一致的状态 +3. **错误处理**:如果外部跨合约调用失败,在回调中手动回滚任何状态更改 + +--- + +## 私有回调:保护回调方法 + +**问题**:当跨合约调用完成时,您的合约需要接收回调。这要求回调方法是公开的,但您通常希望它是私有的以防止未经授权的访问。 + +**解决方案**:验证 `predecessor`(调用该方法的账户)等于 `current_account`(您的合约账户)。这确保只有您的合约可以调用回调。 + +**Rust 中的实现**:使用 `#[private]` 装饰器宏,它会自动添加前驱检查: + +```rust +#[private] +pub fn callback_method(&mut self) { + // 只有您的合约才能调用此方法 +} +``` + +--- + +## 在回调中处理用户资金 + +**关键规则**:当方法 panic 或失败时,任何附加的 NEAR 代币会自动返回给 `predecessor`(发起交易的账户)。 + +**场景**: +1. 用户调用您的合约并附加 10 NEAR +2. 您的合约向另一个合约发出跨合约调用 +3. 外部调用失败或 panic +4. 10 NEAR 返回到**您的合约**(而不是原始用户) + +**安全要求**:如果资金最初来自调用您的合约的用户,您**必须在回调处理程序中手动将其转回**用户。 + +**示例流程**: +- 用户发送 10 NEAR → 您的合约收到 +- 您的合约调用外部合约(失败) +- 10 NEAR 自动返回到您的合约 +- **您必须在回调中将 10 NEAR 转回用户** + +**重要警告**:始终确保您的回调有足够的 GAS 分配来执行退款转账。如果回调在完成退款之前耗尽了 gas,用户的资金可能会被锁定。 + +--- + +## 异步回调和重入攻击 + +**关键漏洞**:在跨合约调用和其回调之间,**任何人都可以执行您的合约的任何公共方法**。这为重入攻击创造了机会,重入攻击是智能合约中最常见和最危险的安全漏洞之一。 + +
+ +### 重入攻击示例:deposit_and_stake + +**易受攻击的实现(错误的)**: +1. 用户向您的合约发送资金 +2. 合约立即将资金添加到用户余额 +3. 合约尝试在验证者处质押资金 +4. 如果质押失败,回调删除余额 + +**攻击向量**: +- 攻击者使用 10 NEAR 调用 `deposit_and_stake` +- 合约将 10 NEAR 添加到攻击者的余额(步骤 2) +- 合约向验证者发出跨合约调用(步骤 3) +- **在回调执行之前**,攻击者调用 `withdraw` 方法 +- 攻击者提取 10 NEAR +- 如果质押失败,回调删除余额,但攻击者已经提取了 +- **结果**:攻击者收到两次资金,合约损失资金 + +**安全实现(正确的)**: +1. 用户向您的合约发送资金 +2. **不要立即添加到余额** - 存储在临时状态 +3. 合约尝试在验证者处质押资金 +4. 在回调中:**只有在质押成功后**,才将资金添加到用户余额 +5. 如果质押失败,将资金返回给用户 + +**关键原则**:延迟状态更改,直到回调确认外部操作成功。切勿在跨合约调用完成之前更新余额或关键状态。 + +--- + +## 最佳实践总结 + +1. 在 Rust 中为所有回调方法**使用 `#[private]` 装饰器** +2. 如果外部调用失败,在回调中**退还用户资金** +3. 为回调操作(尤其是退款)**分配足够的 GAS** +4. **延迟状态更新**,直到回调确认成功 +5. 在跨合约调用完成之前**切勿更新余额** +6. 在回调方法中**验证所有输入** +7. 在提交状态更改之前**检查外部调用结果** diff --git a/zh/smart-contracts/security/checklist.mdx b/zh/smart-contracts/security/checklist.mdx new file mode 100644 index 00000000000..3e194221b18 --- /dev/null +++ b/zh/smart-contracts/security/checklist.mdx @@ -0,0 +1,330 @@ +--- +title: ✅ 检查清单 +description: "安全最佳实践和常见保护措施。" +--- + +完成智能合约开发后,请遍历以下清单,以确保一切对终端用户都是安全的。 + + +查看我们的[安全文章](./introduction),了解如何提高合约的安全性。 + + +--- + +## 概述 + +在将任何 NEAR 智能合约部署到主网之前,应审查此综合安全检查清单。每个项目都解决了可能导致漏洞、利用或资金损失的关键安全问题。 + +--- + +## 结构:方法可见性和访问控制 + +### 1. 所有私有方法均已装饰为 `private` + +**要求**:每个只能由合约本身调用的方法都必须在 Rust 中用 `#[private]` 装饰器标记。 + +**为什么重要**: +- 防止未经授权的外部调用 +- 确保只有您的合约才能调用内部方法 +- 保护回调方法不被直接调用 + +**如何验证**: +```rust +// ✅ 正确 +#[private] +pub fn internal_method(&mut self) { + // 只有合约才能调用此方法 +} + +// ❌ 错误 - 缺少 #[private] +pub fn internal_method(&mut self) { + // 任何人都可以调用此方法! +} +``` + +--- + +## 环境:前驱和签名者的使用 + +### 2. `predecessor` 和 `signer` 使用正确 + +**要求**:在整个合约中,确保使用正确的环境变量: +- `predecessor` - 调用该方法的账户(可能是合约) +- `signer` - 签署交易的账户(始终是人/账户) + +**为什么重要**: +- 使用错误的变量可能允许未经授权的访问 +- 对访问控制和授权至关重要 +- 影响谁可以执行敏感操作 + +**常见错误**: +- 在需要 `signer` 时使用 `predecessor`(用于用户验证) +- 在需要 `predecessor` 时使用 `signer`(用于回调验证) +- 在应该检查时不进行检查 + +**如何验证**: +```rust +// ✅ 正确 - 检查签名者进行用户操作 +assert_eq!(env::signer_account_id(), user_id, "Unauthorized"); + +// ✅ 正确 - 检查前驱进行回调 +assert_eq!(env::predecessor_account_id(), env::current_account_id(), "Only contract"); +``` + +--- + +## 存储:成本管理和数据结构 + +### 3. 状态增长有足够的余额覆盖 + +**要求**:每次合约状态增长(存储新数据)时,确保有足够的合约余额来支付存储成本。存储成本通常由存储数据的用户承担(例如,当铸造新 NFT 时)。 + +**为什么重要**: +- 防止存储耗尽攻击 +- 确保合约可以继续运行 +- 避免因余额不足导致的交易失败 + +**如何验证**: +- 计算每次状态更改的存储成本 +- 在存储数据之前检查合约余额 +- 要求用户为其存储附加存款 +- 定期监控合约余额 + +
+ +### 4. 所有集合都有唯一 ID + +**要求**:每个集合(Vector、Map、TreeMap 等)都必须有唯一标识符,以防止冲突和数据损坏。 + +**为什么重要**: +- 防止不同集合的数据混合 +- 避免状态损坏 +- 确保数据完整性 + +**如何验证**: +```rust +// ✅ 正确 - 唯一的集合 ID +const USERS: StorageKey = StorageKey::new(b"users"); +const ORDERS: StorageKey = StorageKey::new(b"orders"); + +// ❌ 错误 - 不同集合使用相同 ID +const DATA1: StorageKey = StorageKey::new(b"data"); +const DATA2: StorageKey = StorageKey::new(b"data"); // 冲突! +``` + +
+ +### 5. 检查下溢和溢出 + +**要求**:在 Rust 中启用溢出检查,以防止整数下溢和溢出漏洞。 + +**为什么重要**: +- 防止可被利用的算术错误 +- 避免整数回绕带来的意外行为 +- 对财务计算至关重要 + +**如何验证**: +在 `Cargo.toml` 中添加: +```toml +[profile.release] +overflow-checks = true +``` + +**替代方案**:使用检查算术方法: +```rust +// ✅ 正确 - 检查算术 +let result = a.checked_add(b).expect("Overflow"); + +// ❌ 错误 - 未检查算术 +let result = a + b; // 可能静默溢出 +``` + +--- + +## 操作:资金转账和基金管理 + +### 6. 为存储成本保留足够余额 + +**要求**:从合约发送资金时,始终保留足够的余额来覆盖持续的存储成本。 + +**为什么重要**: +- 防止合约变得无法使用 +- 确保合约可以继续运行 +- 避免与存储相关的交易失败 + +**如何验证**: +```rust +// ✅ 正确 - 保留存储余额 +let storage_cost = self.calculate_storage_cost(); +let available = env::account_balance() - storage_cost; +assert!(amount <= available, "Insufficient balance after storage reserve"); +Promise::new(receiver_id).transfer(amount); + +// ❌ 错误 - 发送所有余额 +Promise::new(receiver_id).transfer(env::account_balance()); // 什么都不剩! +``` + +
+ +### 7. 发送之前扣除用户资金 + +**要求**:如果您在合约状态中跟踪用户资金,**始终在向用户发送资金之前从状态中扣除它们**。 + +**为什么重要**: +- 防止重入攻击 +- 确保状态一致性 +- 遵循检查-效果-交互模式 + +**如何验证**: +```rust +// ✅ 正确 - 先扣除,然后发送 +let user_balance = self.get_balance(&user_id); +self.set_balance(&user_id, 0); // 先扣除 +Promise::new(user_id).transfer(user_balance); // 然后发送 + +// ❌ 错误 - 先发送,然后扣除(容易受到重入攻击) +Promise::new(user_id).transfer(user_balance); +self.set_balance(&user_id, 0); // 太晚了! +``` + +--- + +## 回调:跨合约调用安全 + +### 8. 所有私有回调都标记为 `private` + +**要求**:每个只能由您的合约调用的回调方法都必须使用 `#[private]` 装饰器。 + +**为什么重要**:防止外部参与者直接调用您的回调并绕过安全检查。 + +```rust +// ✅ 正确 - 回调方法只能由合约本身调用 +#[private] +pub fn callback_after_stake(&mut self, #[callback_result] result: Result<(), PromiseError>) { + match result { + Ok(_) => { + self.balances + .insert(self.pending_user.clone(), self.pending_amount); + } + Err(_) => { + // 失败时回滚状态 + } + } +} + +// ❌ 错误 - 没有 #[private] 的回调 - 可以直接调用 +pub fn callback_after_stake(&mut self, #[callback_result] result: Result<(), PromiseError>) { + // 攻击者可以调用此方法并操纵状态! + match result { + Ok(_) => { + self.balances + .insert(self.pending_user.clone(), self.pending_amount); + } + Err(_) => { + // 攻击者可以触发此操作来回滚合法操作 + } + } +} +``` + +
+ +### 9. 所有跨合约调用都有回调 + +**要求**:每个跨合约调用都必须有相应的回调来处理响应。 + +**为什么重要**: +- 允许错误处理 +- 启用失败时的状态回滚 +- 确保异步操作的正确完成 + +
+ +### 10. 回调检查错误并回滚状态 + +**要求**:所有回调都必须检查外部调用是否成功或失败,并在调用失败时回滚任何状态更改。 + +**为什么重要**: +- 防止不一致的状态 +- 确保操作的原子性 +- 防范部分失败 + +**如何验证**: +```rust +#[private] +pub fn callback(&mut self, result: Result<(), String>) { + match result { + Ok(_) => { + // 外部调用成功,提交状态 + self.commit_state(); + } + Err(_) => { + // 外部调用失败,回滚状态 + self.rollback_state(); + } + } +} +``` + +
+ +### 11. 如有必要,回调将资金退还给前驱 + +**要求**:如果用户资金涉及到失败的跨合约调用,回调必须将资金退还给原始用户(前驱)。 + +**为什么重要**: +- 防止用户资金损失 +- 确保适当的退款处理 +- 维护用户信任 + +
+ +### 12. 回调中没有 `panic!` + +**要求**:回调方法永远不应该使用 `panic!` 或导致 panic。请使用适当的错误处理代替。 + +**为什么重要**: +- Panic 可能会让合约处于不一致状态 +- 防止适当的错误恢复 +- 可能导致用户资金损失 + +**如何验证**:在回调方法中搜索代码库中的 `panic!` 并用适当的错误处理替换。 + +
+ +### 13. 回调有足够的 GAS + +**要求**:所有回调都必须分配足够的 GAS 才能完全执行,包括任何退款或状态更新。 + +**为什么重要**: +- 防止部分执行 +- 确保退款可以完成 +- 避免交易卡住 + +**如何验证**:计算回调操作的 GAS 需求并确保足够的分配。 + +
+ +### 14. 调用和回调之间合约不处于可被利用的状态 + +**要求**:在跨合约调用和其回调之间,合约不得处于可被其他交易利用的状态。 + +**为什么重要**: +- 防止重入攻击 +- 确保状态一致性 +- 防范竞争条件 + +**如何验证**:审查状态更改——确保关键更新发生在回调中,而不是在外部调用之前。 + +--- + +## 其他安全考虑 + +除此清单外,还要考虑: + +- **输入验证** - 验证所有用户输入 +- **访问控制** - 验证调用者权限 +- **经济安全** - 确保经济激励是正确的 +- **测试** - 全面的测试覆盖 +- **代码审查** - 外部安全审查 +- **监控** - 部署后监控 diff --git a/zh/smart-contracts/security/frontrunning.mdx b/zh/smart-contracts/security/frontrunning.mdx new file mode 100644 index 00000000000..6766857f55e --- /dev/null +++ b/zh/smart-contracts/security/frontrunning.mdx @@ -0,0 +1,198 @@ +--- +title: 抢先交易 +description: "了解 NEAR 智能合约中的抢先交易攻击,以及如何通过适当的交易排序和 MEV 保护技术来防止它们。" +--- + +抢先交易是一种攻击,其中验证者(或其他参与者)在交易执行前看到待处理的交易,并提交自己的交易以从这些信息中获利。在 NEAR 协议中,验证者可以访问交易池,使其能够抢先执行用户的交易。 + +在 NEAR 网络中: +- **验证者可以访问交易池** - 他们可以看到所有待处理的交易 +- **交易在执行前是可见的** - 验证者可以分析它们 +- **验证者控制交易排序** - 他们决定包含哪些交易以及按什么顺序 +- **验证者可以插入自己的交易** - 他们可以在用户交易之前提交交易 + +--- + +## 攻击机制 + +### 基本抢先交易过程 + +1. **用户提交交易** - 例如,解决一个谜题以获取奖励 +2. **验证者看到交易** - 交易在池中,对验证者可见 +3. **验证者分析** - 验证者识别出有利可图的机会 +4. **验证者提交自己的交易** - 验证者创建带有正确答案的交易 +5. **验证者首先包含自己的交易** - 验证者排序交易以先执行自己的 +6. **验证者获得奖励** - 验证者的交易首先执行并获取奖励 +7. **用户的交易失败或一无所获** - 用户的交易执行时奖励已被获取 + +**结果**:验证者从他们不应该访问的信息中获利,用户蒙受损失。 + +--- + +## 示例:解谜游戏 + +### 易受攻击的游戏设计 + +假设您创建了一个游戏: +- 用户提交谜题解答 +- 第一个正确的解答赢得奖励 +- 解答作为交易提交 +- 合约向第一个解答者支付奖励 + +
+ +### 攻击过程 + +**场景**: +1. 用户解决谜题并提交带有正确答案的交易 +2. 验证者在池中看到该交易 +3. 验证者从用户的交易中提取正确答案 +4. 验证者用相同的答案创建自己的交易 +5. 验证者将自己的交易排在用户交易之前 +6. 验证者的交易首先执行并获取奖励 +7. 用户的交易执行时发现奖励已被获取 + +```rust +// ❌ 漏洞:先到先得 - 验证者可以抢先执行 +// 验证者在交易池中看到用户的解答并首先提交 +pub fn solve_puzzle(&mut self, puzzle_id: String, solution: String) { + // 验证者可以在池中看到此交易并提取解答 + let correct_answer = self.get_puzzle_answer(&puzzle_id); + + if solution == correct_answer { + // 第一个解答者获得奖励 - 验证者可以抢先执行! + if !self.solved_puzzles.contains_key(&puzzle_id) { + let solver = env::signer_account_id(); + self.solved_puzzles.insert(puzzle_id, solver.clone()); + + let reward = NearToken::from_near(1); + let previous_solver_reward = self.rewards.get(&solver).unwrap_or(&NearToken::ZERO); + let solver_reward = previous_solver_reward.saturating_add(reward); + self.rewards.insert(solver, solver_reward); + } + } +} +``` + +**结果**:验证者窃取了本应给用户的奖励。 + +--- + +## 抢先交易攻击类型 + +### 1. 简单抢先交易 +- 验证者看到有利可图的交易 +- 验证者提交相同或类似的交易 +- 验证者的交易首先执行 +- 验证者获利,用户蒙受损失 + +### 2. 三明治攻击 +- 验证者看到大型交易 +- 验证者在前面(抢先)和后面(跟随)放置交易 +- 验证者从价格影响中获利 +- 用户因验证者的交易而获得更差的价格 + +### 3. 优先 Gas 拍卖 +- 多个参与者竞相抢先执行 +- 他们出价更高的 gas 费以获得优先权 +- 出价最高者获得抢先执行位置 +- 创造昂贵的竞争 + +--- + +## 现实影响 + +抢先交易攻击对以下情形特别危险: + +- **奖励机制** - 游戏、竞赛、赏金 +- **DEX 交易** - 价格影响很重要的代币兑换 +- **NFT 铸造** - 先到先得的铸造 +- **拍卖系统** - 基于时间或先出价的拍卖 +- **预言机更新** - 依赖外部数据的交易 + +--- + +## 预防策略 + +### 1. 提交-揭示方案 + +**工作原理**: +1. 用户提交答案的哈希(答案 + 密钥) +2. 提交期结束后,用户揭示他们的答案和密钥 +3. 合约验证哈希是否与揭示的答案匹配 +4. 所有揭示后确定获胜者 + +**优点**: +- 验证者在提交阶段看不到实际答案 +- 只有在揭示后他们才能看到答案,但那时已经太晚了 +- 防止答案被抢先执行 + +
+ +### 2. 时间延迟执行 + +**工作原理**: +- 用户提交交易 +- 交易进入队列但不立即执行 +- 执行在延迟后发生(例如,下一个区块) +- 验证者无法预测最终状态 + +**优点**: +- 减少验证者的信息优势 +- 使抢先执行更加困难 +- 仍然容易受攻击但更难利用 + +
+ +### 3. 私有交易池 + +**工作原理**: +- 使用私有交易提交 +- 交易在公共池中不可见 +- 只有在包含在区块中时才执行 + +**局限性**: +- 需要基础设施支持 +- 可能不适用于所有网络 +- 增加复杂性 + +
+ +### 4. 随机执行 + +**工作原理**: +- 随机化哪些交易执行 +- 不使用先到先得 +- 使用抽签或随机选择 + +**优点**: +- 减少抢先执行的优势 +- 使攻击更难以预测 +- 更公平的分配 + +
+ +### 5. 经济抑制措施 + +**工作原理**: +- 要求参与者存款 +- 对恶意行为罚款存款 +- 使抢先执行无利可图 + +--- + +## 最佳实践 + +1. **永远不要为有价值的奖励使用先到先得** +2. **对游戏和竞赛使用提交-揭示方案** +3. **添加时间延迟**以减少信息优势 +4. **尽可能随机化选择** +5. **要求存款**以阻止恶意行为 +6. **使用抢先执行场景测试**,然后再部署 +7. **为用户记录抢先执行风险** + +## 其他资源 + +有关抢先执行和 MEV(最大可提取价值)的更多信息,请参阅: +- Paradigm 的 "以太坊是一片黑暗森林" 博客文章:https://www.paradigm.xyz/2020/08/ethereum-is-a-dark-forest +- 这解释了区块链系统中抢先执行的更广泛概念 diff --git a/zh/smart-contracts/security/introduction.mdx b/zh/smart-contracts/security/introduction.mdx new file mode 100644 index 00000000000..22ee9b8ce0d --- /dev/null +++ b/zh/smart-contracts/security/introduction.mdx @@ -0,0 +1,35 @@ +--- +title: 简介 +description: "了解 NEAR 智能合约安全最佳实践,包括常见漏洞、攻击向量以及如何构建安全的去中心化应用程序。" +--- + +在这里,您将找到有关如何保持智能合约和去中心化应用程序安全的信息。 + + +请花一些时间阅读本节,它将帮助您保持 dApp 的安全 + + +--- + +## 🐞 漏洞赏金计划 +NEAR 有一个[漏洞赏金计划](https://hackenproof.com/company/near/programs)。如果您在协议、核心合约或 Web 中发现任何安全错误,请使用它。黑客们——帮助审计、测试和加固 NEAR! + +## ✅ 安全检查清单 +通过我们的[安全检查清单](./checklist)确保您的智能合约安全。 + +## 🛡️ 安全概念 +学习重要的安全概念,保持您的 dApp 安全稳健。请阅读: + +- 保持[回调安全](./callbacks) +- 了解[抢先交易](./frontrunning) +- 了解[女巫攻击](./sybil) +- 了解[重入攻击](./reentrancy) +- 确保账户的所有者是[进行调用的人](./one_yocto) +- 了解[生成随机数](./random)的复杂性 +- 防护[小额存款攻击](./storage)耗尽您的账户 + +## 🎞️ 外部资源 + +[https://twitter.com/timurguvenkaya](https://twitter.com/timurguvenkaya) 在 YouTube 上发布了关于智能合约安全的精彩视频系列: + + diff --git a/zh/smart-contracts/security/one_yocto.mdx b/zh/smart-contracts/security/one_yocto.mdx new file mode 100644 index 00000000000..d5bda75fa32 --- /dev/null +++ b/zh/smart-contracts/security/one_yocto.mdx @@ -0,0 +1,187 @@ +--- +title: 确认是用户本人(1yⓃ) +description: "了解 NEAR 智能合约中的 one yocto 安全模式,用于验证账户所有权和防止未经授权的访问。" +--- + +在 NEAR 协议中,验证交易实际来自用户(而不是来自具有函数调用密钥的网站)对于安全性至关重要,尤其是在转移有价值的资产时。要求附加 1 yoctoNEAR(NEAR 的最小单位)是确保用户授权的一种简单有效的方法。 + +## NEAR 访问密钥系统 + +NEAR 使用[访问密钥系统](../../protocol/accounts-contracts/access-keys)来简化账户管理。有两种主要类型的密钥: + +### 1. 完全访问密钥 +- 对账户拥有**完全控制** +- 可以执行所有[操作](../anatomy/actions)(转账、部署等) +- 可以将 NEAR 作为存款附加 +- **仅存储在用户钱包中** - 从不与网站共享 + +### 2. 函数调用密钥 +- **有限权限** - 只能调用指定的智能合约方法 +- **无法将 NEAR 作为存款附加** +- 当用户登录网站时创建 +- **存储在网站中** - 网站可以自动使用它们 + +--- + +## 安全问题 + +### 网站登录的工作原理 + +当用户[登录网站](../../web3-apps/tutorials/wallet-login)以与您的合约交互时: + +1. 创建一个 `Function Call` 密钥 +2. 密钥存储在网站中(浏览器存储) +3. 网站可以使用此密钥**自动**调用授权方法 +4. 每次交易都不需要用户交互 + +
+ +### 风险 + +对于大多数操作,这是方便且安全的。但是,对于**有价值的资产转移**(NFT、同质化代币、大量 NEAR),这会产生安全风险: + +- **网站拥有密钥** - 可以在没有用户确认的情况下发起转账 +- **用户不知情** - 用户可能不知道正在发生转账 +- **恶意网站** - 受损或恶意网站可能耗尽资产 +- **用户无法验证** - 无法确保用户实际授权了转账 + +**关键场景**: +- 转移 NFT +- 转移同质化代币 (FT) +- 大量 NEAR 转账 +- 任何涉及有价值资产的操作 + +--- + +## 解决方案:要求 1 YoctoNEAR + +### 工作原理 + +要求用户在敏感操作中附加 **1 yoctoNEAR**(1 yⓃ): + +```rust +pub fn transfer_nft(&mut self, token_id: String, receiver_id: AccountId) { + // 要求 1 yoctoNEAR 以确保用户授权 + require!( + env::attached_deposit() == NearToken::from_yoctonear(1), + "Requires attached deposit of exactly 1 yoctoNEAR" + ) + + // 继续进行转账 +} +``` + + +[`near_sdk`](../../tools/sdk) 提供了一个辅助方法来断言 one yoctoNEAR 存款: + +```rust +use near_sdk::{assert_one_yocto}; + +... + +pub fn transfer_nft(&mut self, token_id: String, receiver_id: AccountId) { + // 要求 1 yoctoNEAR 以确保用户授权 + assert_one_yocto(); + + // 继续进行转账 +} + +... +``` + + +
+ +### 为什么这有效 + +1. **函数调用密钥不能附加 NEAR** - 它们只能调用没有存款的方法 +2. **只有完全访问密钥才能附加 NEAR** - 这些存储在用户的钱包中 +3. **钱包需要用户确认** - 当附加 NEAR 时,钱包会提示用户 +4. **用户必须批准** - 没有用户明确批准,交易无法进行 + +**结果**:如果交易包含 1 yoctoNEAR,您可以相信它是用户通过其钱包明确授权的。 + +--- + +## 实现示例 + +### 带验证的 NFT 转账 + +```rust +pub fn transfer_nft(&mut self, token_id: String, receiver_id: AccountId) { + // 验证用户授权 + // 要求 1 yoctoNEAR 以确保用户授权 + require!( + env::attached_deposit() == NearToken::from_yoctonear(1), + "Requires attached deposit of exactly 1 yoctoNEAR" + ) + + // 验证调用者拥有 NFT + let owner = self.get_token_owner(&token_id); + assert_eq!(env::predecessor_account_id(), owner, "Not the owner"); + + // 执行转账 + self.transfer_token(token_id, receiver_id); +} +``` + +
+ +### 同质化代币转账 + +```rust +pub fn transfer_ft(&mut self, amount: U128, receiver_id: AccountId) { + // 要求 1 yoctoNEAR 以确保用户授权 + require!( + env::attached_deposit() == NearToken::from_yoctonear(1), + "Requires attached deposit of exactly 1 yoctoNEAR" + ) + + // 转账代币 + self.internal_transfer(env::predecessor_account_id(), receiver_id, amount.0); +} +``` + +--- + +## 何时使用此模式 + +### 1 YoctoNEAR 要求适用于: + +- ✅ NFT 转账 +- ✅ 同质化代币转账 +- ✅ 大量 NEAR 转账 +- ✅ 账户所有权变更 +- ✅ 权限修改 +- ✅ 任何涉及有价值资产的操作 + +### 不必要的情况: + +- ❌ 只读操作(视图方法) +- ❌ 低价值操作 +- ❌ 不转移资产的操作 +- ❌ 公共数据查询 + +## 最佳实践 + +1. **始终要求 1 yoctoNEAR** 进行资产转账 +2. **记录要求** - 告知用户为什么需要它 +3. **返还 yoctoNEAR** 如果您不需要它(可选,但对用户友好) +4. **使用一致的模式** - 应用于所有敏感操作 +5. **使用函数调用密钥测试** - 确保它们在没有存款的情况下失败 + +## 替代方案:返还 YoctoNEAR + +您可以在验证后将 1 yoctoNEAR 退还给用户: + +```rust +pub fn transfer_nft(&mut self, token_id: String, receiver_id: AccountId) { + assert!(env::attached_deposit() >= 1, "Verification required"); + + // 执行转账 + self.transfer_token(token_id, receiver_id); + + // 返还 yoctoNEAR(可选) + Promise::new(env::predecessor_account_id()).transfer(1); +} +``` diff --git a/zh/smart-contracts/security/random.mdx b/zh/smart-contracts/security/random.mdx new file mode 100644 index 00000000000..32e31626a90 --- /dev/null +++ b/zh/smart-contracts/security/random.mdx @@ -0,0 +1,186 @@ +--- +title: 随机数 +description: "了解 NEAR 智能合约中的安全随机数生成,以及如何避免可预测的随机性漏洞。" +--- + +在区块链环境中生成安全随机数具有挑战性,因为区块链是确定性的。NEAR 协议提供了一种 `random seed`(随机种子)机制,但了解其属性和局限性对于构建依赖随机性的安全应用程序至关重要。 + +--- + +## NEAR 随机种子的工作原理 + +NEAR 提供了一个 `random seed`,使智能合约能够创建随机数和字符串。此种子具有独特属性: + +### 确定性和可验证性 + +随机种子是**确定性和可验证的**: +- 它来自产生区块的验证者 +- 验证者用他们的私钥签署前一个区块哈希 +- 签名成为当前区块的随机种子 +- 任何人都可以验证种子,但只有验证者事先知道它 + +--- + +## 安全属性 + +随机种子的创建方式提供了两个重要的安全属性: + +### 1. 只有验证者可以预测 +- **只有挖掘交易的验证者才能预测**将生成哪个随机数 +- **其他人无法预测**,因为没有人知道验证者的私钥(除验证者本身外) +- 这提供了一定的安全性,但创造了中心化风险 + +### 2. 验证者不能干预 +- 验证者**不能干预**随机数的生成 +- 他们必须签署前一个区块哈希,而他们(很可能)对此没有控制权 +- 前一个区块可能由不同的验证者产生 + +**重要**:虽然验证者不能直接操纵种子,但他们仍然可以通过其他方式利用它。 + +--- + +## 攻击类型 1:操纵输入 + +### 漏洞 + +如果您的合约要求用户提供输入,并在它与随机种子匹配时奖励他们,验证者可以利用这一点: + +**示例**: +- 合约要求用户选择 1-100 之间的数字 +- 如果用户的数字与随机种子匹配,他们赢得奖励 +- 验证者在区块被挖掘之前就知道随机种子 +- 验证者创建带有获胜数字的交易 +- 验证者将自己的交易包含在区块中 +- 验证者赢得奖励 + +```rust +// ❌ 漏洞:操纵输入 - 验证者可以预测并赢得 +// 用户提供输入,随机种子在同一个区块中生成 +pub fn guess_number(&mut self, guess: u8) { + let account_id = env::signer_account_id(); + + // 存储用户的猜测 + self.bets.insert(account_id.clone(), guess.to_string()); + + // 在同一个区块中生成随机数 - 验证者知道它! + let random_seed = env::random_seed(); + let random_number = (random_seed[0] % 100) as u8; + + // 验证者可以看到用户的猜测并创建获胜交易 + if guess == random_number { + let reward = NearToken::from_near(1); + let previous_user_reward = self.rewards.get(&account_id).unwrap_or(&NearToken::ZERO); + let user_reward = previous_user_reward.saturating_add(reward); + self.rewards.insert(account_id, user_reward); + } +} +``` + +### 为什么有效 + +由于验证者知道其区块中将生成哪个 `random seed`,他们可以: +1. 计算获胜输入 +2. 创建带有该输入的交易 +3. 将自己的交易包含在区块中 +4. 每次都赢 + +**结果**:验证者可以在单区块游戏中保证获胜。 + +--- + +## 攻击类型 2:拒绝挖掘区块 + +### 两阶段解决方案 + +解决"操纵输入"的一种方法是使用两阶段过程: + +1. **投注阶段**:用户发送他们的输入/选择(例如,"正面"或"反面") +2. **解决阶段**:合约生成随机数并确定获胜者(在之后的区块中) + +这防止了验证者操纵输入,因为: +- 用户的选择在第一个区块中被锁定 +- 随机种子在不同(后面的)区块中生成 +- 验证者在用户做出选择时无法知道随机种子 + +### 残余漏洞 + +然而,验证者仍然可以通过选择性区块挖掘来利用这一点: + +**攻击过程**: +1. 验证者用自己的账户创建"投注"交易 +2. 验证者选择任一输入(无关紧要) +3. 当轮到验证者验证时: + - 他们检查将生成什么随机种子 + - 如果他们的投注会赢,他们将"解决"交易包含在区块中 + - 如果他们的投注会输,他们跳过"解决"交易 +4. 其他验证者可能挖掘它,但验证者提高了自己的获胜率 + +**结果**:验证者提高了获胜概率,虽然不能保证。 + +--- + +## 掷硬币示例:概率操纵 + +### 公平掷硬币(无攻击) + +在公平的掷硬币游戏中: +- 您在投注阶段选择"正面"或"反面" +- 随机种子在解决阶段确定结果 +- **获胜概率:50%**(1/2) + +### 使用拒绝挖掘攻击 + +如果您是验证者并可以拒绝挖掘失败的区块: + +**场景**: +- 您投注"正面" +- 如果随机种子 = "正面":您挖掘区块并赢得 ✅ +- 如果随机种子 = "反面":您跳过区块(其他验证者可能挖掘它) + - 如果其他验证者挖掘:您输了 ❌ + - 如果没有人挖掘:交易延迟,您稍后再试 + +**数学分析**: + +如果您总是投注"正面",并且可以在"反面"出现时选择重试: + +**可能的结果**(H = 正面,T = 反面): +- H H:赢 ✅ +- T H:赢 ✅(在 T 之后重试) +- H T:赢 ✅(在 H 之后重试) +- T T:输 ❌(在 T 之后重试,然后再次 T) + +**结果**:您在 4 个场景中的 3 个中赢 = **75% 的获胜率**(3/4) + +**提升**:从 50% 到 75% = **获胜概率提高了 25%** + +**注意**:这些赔率在具有更多可能结果的游戏中会被稀释(例如,有 6 面的骰子)。 + +--- + +## 随机性最佳实践 + +### 1. 使用多区块随机性 +- 将投注和解决分到不同的区块 +- 防止输入操纵攻击 +- 仍然容易受到选择性挖掘的影响,但降低了风险 + +### 2. 使用提交-揭示方案 +- 用户提交他们的选择(选择 + 密钥的哈希) +- 后来揭示选择和密钥 +- 揭示后生成随机种子 +- 防止输入操纵和选择性挖掘 + +### 3. 使用外部预言机 +- 使用受信任的外部随机性来源 +- Chainlink VRF 或类似服务 +- 更安全但需要外部依赖 + +### 4. 接受验证者优势 +- 对于非关键随机性,接受验证者有轻微优势 +- 用于可以接受小优势的游戏 +- 不适合高风险应用程序 + +### 5. 使用多个验证者 +- 在多个验证者之间分布随机性 +- 减少单个验证者的控制 +- 更去中心化的方法 diff --git a/zh/smart-contracts/security/reentrancy.mdx b/zh/smart-contracts/security/reentrancy.mdx new file mode 100644 index 00000000000..cdaa56db614 --- /dev/null +++ b/zh/smart-contracts/security/reentrancy.mdx @@ -0,0 +1,127 @@ +--- +title: 重入攻击 +description: "了解 NEAR 智能合约中的重入攻击,以及如何通过适当的安全措施和编码实践来防止它们。" +--- + +重入攻击是智能合约中最常见和最危险的安全漏洞之一。在 NEAR 协议中,跨合约调用的异步性质为攻击者利用状态不一致性创造了机会。 + +在跨合约调用和其回调之间,**任何人都可以执行您的合约的任何公共方法**。NEAR 异步执行模型的这一基本属性意味着: + +- **任何方法**都可能在方法执行和其回调之间被执行 +- **同一方法**可能在回调完成之前被多次执行 +- 回调之前所做的**状态更改**可能被恶意参与者利用 + +**关键规则**:始终确保合约状态在每个方法执行完成后保持一致和安全,即使回调处于待处理状态。 + +--- + +## 基本假设 + +在设计安全的智能合约时,您必须假设: + +1. **任何方法都可能执行**在您的方法和其回调之间 +2. **同一方法可以被重入**多次,在回调执行之前 +3. **攻击者会利用**他们找到的任何状态不一致性 +4. **状态更改在方法完成后立即可见**,即使在回调之前 + +--- + +## 重入攻击示例:deposit_and_stake + +### 易受攻击的实现(错误的) + +考虑一个 `deposit_and_stake` 函数,具有以下有缺陷的逻辑: + +1. 用户向合约发送资金 +2. 合约**立即将资金添加到用户余额**(状态更改) +3. 合约向验证者发出跨合约调用以质押资金 +4. 如果质押失败,回调删除余额 + +**攻击场景**: +- 攻击者使用 10 NEAR 调用 `deposit_and_stake` +- 合约将 10 NEAR 添加到攻击者的余额(步骤 2 完成) +- 合约向验证者发起跨合约调用(步骤 3) +- **在回调执行之前**,攻击者调用 `withdraw()` 方法 +- 攻击者成功提取 10 NEAR(余额已经更新) +- 如果质押失败,回调删除余额,但攻击者已经提取了 +- **结果**:攻击者收到 10 NEAR,合约损失资金 + +```rust +// ❌ 漏洞:重入攻击 - 状态在外部调用之前更新 +pub fn deposit_and_stake(&mut self) { + let amount = env::attached_deposit(); + let account_id = env::signer_account_id(); + + // 漏洞:在外部调用完成之前更新余额 + let balance = self + .balances + .get(&account_id) + .unwrap_or(&NearToken::ZERO) + .saturating_add(amount); + self.balances.insert(account_id.clone(), balance); + + let _ = Promise::new("validator.near".parse().unwrap()) + .function_call( + "deposit_and_stake".to_string(), + NO_ARGS, + amount, + Gas::from_tgas(10), + ) + .then( + Self::ext(env::current_account_id()) + .with_static_gas(XCC_GAS) + .callback_after_stake(), + ); +} +``` + +**为什么会发生这种情况**:状态更改(添加到余额)在外部调用完成之前发生。攻击者利用状态更新和回调之间的时间窗口。 + +
+ +### 安全实现(正确的) + +解决方案是延迟状态更改,直到回调确认成功: + +1. 用户向合约发送资金 +2. **不要立即添加到余额** - 存储在临时/待处理状态 +3. 合约向验证者发出跨合约调用以质押资金 +4. **在回调中**:只有在质押成功后,才将资金添加到用户余额 +5. 如果质押失败,将资金退还给用户(无需余额更新) + +**关键原则**:切勿在外部操作完成之前更新关键状态(如余额)。始终等待回调确认后再提交状态更改。 + +--- + +## 防预防策略 + +### 1. 延迟状态更新 +- 切勿在跨合约调用之前更新余额或关键状态 +- 在临时状态中存储待处理操作 +- 只有在回调确认成功后才提交状态更改 + +### 2. 使用检查-效果-交互模式 +- **检查**:验证所有输入和前提条件 +- **效果**:更新状态(但只有在外部调用完成后) +- **交互**:最后进行外部调用 + +### 3. 实现重入保护 +- 使用标志防止关键操作期间的重入 +- 在执行期间将方法标记为"进行中" +- 只有在所有操作完成后才清除标志 + +### 4. 在回调中验证 +- 始终在回调中检查外部调用的结果 +- 如果外部操作失败,回滚任何状态更改 +- 永远不要假设外部调用会成功 + +--- + +## 最佳实践 + +1. **假设重入是可能的** - 防御性地设计您的合约 +2. **在外部调用之前最小化状态更改** +3. **在回调中验证所有内容**,然后再提交状态 +4. **使用攻击场景测试** - 模拟重入尝试 +5. **仔细审查回调逻辑** - 这是漏洞隐藏的地方 +6. **始终保持状态一致**,即使在异步操作期间 diff --git a/zh/smart-contracts/security/storage.mdx b/zh/smart-contracts/security/storage.mdx new file mode 100644 index 00000000000..4e24f89c95e --- /dev/null +++ b/zh/smart-contracts/security/storage.mdx @@ -0,0 +1,144 @@ +--- +title: 存储成本攻击 +description: "了解 NEAR 智能合约中的存储安全最佳实践,包括存储成本、状态管理以及如何防止存储相关漏洞。" +--- + +在 NEAR 协议中,智能合约为其使用的存储付费。这种存储成本模型创造了一个潜在的攻击向量,恶意行为者可以通过强制合约支付过多的存储成本来耗尽合约资金。 + +--- + +## NEAR 中的存储成本工作原理 + +NEAR 使用[存储质押模型](../../protocol/storage/storage-staking),其中: + +- **合约为存储付费** - 存储的数据越多,所需的 NEAR 余额就越多 +- **存储被锁定** - 余额被锁定以覆盖存储成本,而不是被花费 +- **存储可以释放** - 删除数据会将锁定的余额释放回合约 + +**关键规则**:如果您的合约不要求用户覆盖自己的存储成本,攻击者可以通过创建许多小的存储条目来耗尽您的合约余额。 + +--- + +## 攻击场景:留言板示例 + +### 设置 + +1. 您将留言板智能合约部署到 `example.near` +2. 用户可以在留言板上添加消息 +3. 用户只需支付少量 gas 费用来存储他们的消息 +4. **您的合约为每条消息支付存储成本**。 + +
+ +### 攻击 + +**问题**:如果存储消息对用户的成本很低(只是 gas),但对您的合约成本显著更高(存储租金),这会造成经济失衡,攻击者可以利用这一点。 + +**攻击向量**: +- 攻击者创建一个脚本,发送数千条小消息 +- 每条消息对攻击者的 gas 费用最低 +- 每条消息迫使您的合约锁定 NEAR 用于存储 +- 发送多条消息后,您的合约余额被锁定 +- 由于余额不足,合约无法再运行。 + +**结果**:您的合约变得无法使用,您可能需要持续为其提供资金或删除数据以释放余额。 + +
+ +### 示例流程 + +1. 合约 `example.near` 以 10 NEAR 余额开始 +2. 每条消息需要锁定 0.001 NEAR 用于存储 +3. 攻击者发送 10,000 条消息 +4. 合约必须锁定 10 NEAR(10,000 × 0.001) +5. 合约余额被完全锁定 +6. 合约无法接受新消息或执行其他操作。 + +--- + +## 解决方案:要求用户覆盖存储费用 + +### 实现 + +要求用户附加足够的 NEAR 来覆盖其数据的存储成本: + +```rust +pub fn add_message(&mut self, message: String) { + let storage_cost = self.calculate_storage_cost(&message); + assert!(env::attached_deposit() >= storage_cost, "Insufficient deposit for storage"); + + // Store the message + // Storage cost is covered by attached deposit +} +``` + +**关键点**: +- 计算正在存储的数据的存储成本 +- 要求用户至少附加该金额作为存款 +- 附加的存款自动覆盖存储成本 +- 想要存储数据的用户必须为此付费。 + +
+ +### 优点 + +1. **防止存储耗尽攻击** - 攻击者必须为自己的存储付费 +2. **经济可持续性** - 合约不需要持续资金 +3. **公平的成本分配** - 用户为其使用的内容付费 +4. **防止滥用** - 使垃圾攻击变得昂贵。 + +--- + +## 释放锁定的存储余额 + +**重要**:存储余额是**锁定的**,而不是被花费的。您可以通过删除数据来释放它: + +- 当您删除存储的数据时,锁定的余额将返回给合约 +- 这允许您在需要时释放余额 +- 对于维护或需要恢复锁定资金很有用。 + +**示例**: +- 合约有 50 NEAR 锁定用于存储 +- 您删除旧的/未使用的数据 +- 50 NEAR 被解锁并在合约余额中可用。 + +--- + +## 最佳实践 + +1. **始终要求用户覆盖存储成本**,针对他们存储的数据 +2. **准确计算存储成本** - 确保附加的存款覆盖成本 +3. **记录存储要求** - 告诉用户需要附加多少 +4. **监控合约余额** - 确保有足够的资金用于操作 +5. **实现数据过期** - 允许删除旧数据以释放余额 +6. **使用攻击场景测试** - 模拟许多小的存储操作。 + +--- + +## 常见攻击模式 + +### 1. 小额存款攻击 +- 攻击者发送许多小额存款 +- 每个都需要存储(用户记录、交易历史) +- 合约余额因存储成本而耗尽。 + +### 2. 数据垃圾攻击 +- 攻击者存储过量数据 +- 合约必须锁定余额用于所有存储 +- 合约变得无法使用。 + +### 3. 集合增长攻击 +- 攻击者向集合(映射、向量)添加许多项目 +- 每个项目都需要存储 +- 合约余额耗尽。 + +--- + +## 预防检查清单 + +- [ ] 用户必须附加存款以覆盖其存储成本 +- [ ] 存储成本计算是准确的 +- [ ] 合约监控并维持足够的余额 +- [ ] 旧数据可以删除以释放余额 +- [ ] 用户的存储要求已记录 +- [ ] 攻击场景已经过测试。 diff --git a/zh/smart-contracts/security/sybil.mdx b/zh/smart-contracts/security/sybil.mdx new file mode 100644 index 00000000000..ff78a6c6f73 --- /dev/null +++ b/zh/smart-contracts/security/sybil.mdx @@ -0,0 +1,226 @@ +--- +title: 女巫攻击 +description: "了解 NEAR 智能合约中的女巫攻击,以及如何通过适当的身份验证和反游戏机制来防止它们。" +--- + +女巫攻击发生在单个个人或实体创建多个账户以在系统中获得不成比例的影响力时。在 NEAR 协议中,账户创建相对便宜且简单,这使得女巫攻击成为依赖按账户投票、治理或资源分配的应用程序的重大安全隐患。 + +NEAR 协议允许任何人轻松创建多个账户: +- **低成本** - 账户创建很便宜 +- **无身份验证** - 无需 KYC 或身份检查 +- **无限账户** - 每人账户数量没有限制 +- **完整功能** - 每个账户都具有完整功能。 + +--- + +## 攻击场景:DAO 投票 + +### 易受攻击的投票系统 + +假设您创建了一个具有以下设计的 [DAO](../../primitives/dao)(去中心化自治组织): + +- **开放投票** - 社区中的任何人都可以投票 +- **每个账户一票** - 每个 NEAR 账户获得一票 +- **简单多数** - 由多数票做出决定 +- **无身份验证** - 无法确保一个人 = 一个账户。 + +
+ +### 攻击 + +**场景**: +1. DAO 提案需要社区投票 +2. 恶意行为者希望提案通过(或失败) +3. 攻击者创建 100 个 NEAR 账户 +4. 攻击者用所有 100 个账户投票支持他们偏好的结果 +5. 攻击者的 100 票超过了合法社区的投票 +6. 攻击者控制了结果。 + +```rust +// ❌ 漏洞:一账户一票 - 攻击者可以创建多个账户 +pub fn vote(&mut self, vote: bool) { + let voter = env::signer_account_id(); + + // No checks for multiple accounts from same person + // Attacker can create 100 accounts and vote 100 times + + if !self.votes.contains_key(&voter) { + self.votes.insert(voter, vote); + + if vote { + self.yes_votes += 1; + } else { + self.no_votes += 1; + } + } +} +``` + +**结果**:单个人通过多个账户控制治理决定,破坏了去中心化治理的目的。 + +--- + +## 现实影响 + +女巫攻击对以下情形特别危险: + +### 1. DAO 治理 +- **投票操纵** - 单人控制多票 +- **提案结果** - 攻击者可以强制提案通过/失败 +- **国库控制** - 攻击者可以影响资金分配 +- **协议变更** - 攻击者可以推动有害的协议更新。 + +### 2. 空投和代币分发 +- **不公平分配** - 攻击者声称多次空投 +- **资源耗尽** - 合法用户获得的更少 +- **经济操纵** - 攻击者积累不成比例的代币。 + +### 3. 声誉系统 +- **虚假评论** - 多个账户创建虚假正面/负面评论 +- **评级操纵** - 攻击者提升或损害声誉分数 +- **信任系统** - 攻击者操纵信任机制。 + +### 4. 资源分配 +- **公平分配** - 攻击者声称多份 +- **配额系统** - 攻击者绕过每账户限制 +- **奖励计划** - 攻击者声称多次奖励。 + +--- + +## 预防策略 + +### 1. 人性证明 / 身份验证 + +**工作原理**: +- 要求用户证明他们是独特的人类 +- 使用提供身份验证的服务 +- 在允许参与之前验证身份。 + +**优点**: +- 对女巫攻击的强力保护 +- 确保一个人 = 一票/账户。 + +**局限性**: +- 需要外部服务 +- 可能降低可访问性 +- 隐私问题。 + +
+ +### 2. 代币加权投票 + +**工作原理**: +- 投票按代币持有量加权 +- 更多代币 = 更多投票权 +- 仍然容易受攻击,但需要经济利益。 + +**优点**: +- 攻击者必须获取代币(成本高) +- 使激励与经济利益一致 +- 减少随意的女巫攻击。 + +**局限性**: +- 富有的参与者仍然有更多影响力 +- 不能防止富有的女巫攻击 +- 可能不适合所有用例。 + +
+ +### 3. 基于声誉的系统 + +**工作原理**: +- 随时间建立声誉 +- 要求最低声誉才能投票 +- 声誉通过合法活动获得。 + +**优点**: +- 使女巫攻击耗费时间 +- 需要持续的合法活动 +- 减少新假账户的影响。 + +**局限性**: +- 不能防止长期女巫攻击 +- 可能排除合法新用户 +- 实现复杂。 + +
+ +### 4. 经济壁垒 + +**工作原理**: +- 要求存款或费用才能参与 +- 使账户创建变得昂贵 +- 对恶意行为罚没存款。 + +**优点**: +- 增加女巫攻击的成本 +- 阻止随意攻击者 +- 经济抑制。 + +**局限性**: +- 可能排除合法用户 +- 富有的攻击者仍然负担得起 +- 不能解决根本问题。 + +
+ +### 5. 时间锁定参与 + +**工作原理**: +- 要求账户存在最短时间 +- 延迟新账户的投票权 +- 防止即时女巫攻击。 + +**优点**: +- 实现简单 +- 减少大量账户创建的影响 +- 给时间发现模式。 + +**局限性**: +- 不能防止长期攻击 +- 可能延迟合法参与 +- 攻击者可以提前计划。 + +
+ +### 6. 基于活动的要求 + +**工作原理**: +- 要求投票前有最低活动量 +- 必须多次与协议交互 +- 在参与前证明参与度。 + +**优点**: +- 使女巫攻击更加昂贵 +- 需要持续努力 +- 过滤掉随意的假账户。 + +**局限性**: +- 不能防止坚决的攻击者 +- 可能被自动化活动操纵 +- 难以定义"合法活动"。 + +--- + +## 最佳实践 + +1. **对于重要决策永远不要使用简单的一账户一票** +2. **结合多种策略** - 使用分层防御 +3. **监控模式** - 检测异常账户创建 +4. **要求经济利益** - 使攻击成本高昂 +5. **对关键治理使用身份验证** +6. **实现声誉系统**以获得长期保护 +7. **记录女巫攻击风险** - 告知用户局限性。 + +--- + +## DAO 特定建议 + +对于 DAO 治理系统: + +- **使用代币加权投票**,设置最低阈值 +- **要求身份验证**以做出重大决定 +- **实现声誉系统**以持续参与 +- **监控投票模式**以发现可疑活动 +- **对关键提案使用多签或时间延迟** +- **结合链上和链下**治理机制。 diff --git a/zh/smart-contracts/testing/integration-test.mdx b/zh/smart-contracts/testing/integration-test.mdx new file mode 100644 index 00000000000..173757cf4ce --- /dev/null +++ b/zh/smart-contracts/testing/integration-test.mdx @@ -0,0 +1,597 @@ +--- +title: 集成测试 +description: "了解如何使用 Sandbox 测试和真实区块链环境为 NEAR 智能合约编写和运行集成测试。" +--- + +import { Github } from '/snippets/github.jsx' + +集成测试使您能够在 NEAR `testnet` 或本地 `sandbox` 中部署合约,并创建测试用户与其交互。这样,您可以在真实环境中彻底测试您的合约。 + +此外,在使用本地 `sandbox` 时,您可以完全控制网络: + +1. 创建测试 `账户` 并操纵其 `状态` 和 `余额`。 +2. 在回调中模拟错误。 +3. 控制时间流并快进到未来(Rust 已就绪,TS 即将推出)。 + +在 NEAR 中,集成测试使用名为 **Sandbox** 的框架实现。Sandbox 有两种类型:[🦀 Rust](https://github.com/near/near-sandbox-rs) 和 [🌐 Typescript](https://github.com/near/workspaces-js)。 + +我们所有的[示例](https://github.com/near-examples)都附带集成测试。 + + +**Sandbox 测试** + +NEAR Sandbox 允许您编写一次测试,然后在 `testnet` 或本地 `Sandbox` 上运行它们。默认情况下,Sandbox 将启动一个 **sandbox** 并在**本地**运行您的测试。让我们深入了解我们框架的功能,看看它们如何帮助您。 + + +--- + +## 创建账户 + +### 账户 + + + + + + + + + +
+ +### 开发账户 + + + + + + + + + +### 子账户 + + + + + + + + + +### 使用私钥 + + + + + + + + + + + + + + +### 使用文件中的凭据 + + + + + + + + + + + + + + +--- + +## WASM 文件 + +### 编译合约代码 + + + + + + + + 您不需要每次都断言编译过程。您可以使用 `?` 运算符将结果作为 `Vec` 获取,而无需处理 `Result>, Error>` 类型。这样您可以直接使用此向量将 wasm 文件部署到账户中。如果编译过程失败,您的测试仍然会失败。 + + ```rust + let contract_wasm_path = cargo_near_build::build_with_cli(Default::default())?; + ``` + + + + + 如果您想在每次运行测试时编译合约,可以将以下脚本放入 `package.json` 文件中。在代码中,您可以使用 `process.argv[2]` 访问已编译文件的路径。 + + `package.json` 文件: + ```json + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "test": "$npm_execpath run build && ava -- ./build/hello_near.wasm" + }, + ``` + + `main.ava.js` 文件: + ```js + const pathToWasm = process.argv[2]; + await contract.deploy(pathToWasm); + ``` + + + + +### 从文件加载 + + + + + + + + 与从代码编译 wasm 的情况相同,您不需要每次都断言读取文件过程。您可以使用 `expect` 方法将读取文件结果作为 `Vec` 获取,并提供错误消息作为参数。如果编译过程失败,您的测试仍然会失败。 + + ```rust + let contract_wasm = std::fs::read(artifact_path) + .expect(format!("Could not read WASM file from {}", artifact_path).as_str()); + ``` + + + + + 如果您想使用预编译的合约,可以将以下脚本放入 `package.json` 文件中。在代码中,您可以使用 `process.argv[2]` 访问预编译文件的路径。 + + `package.json` 文件: + ```json + "scripts": { + "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", + "test": "ava -- ./build/hello_near.wasm" + }, + ``` + + `main.ava.js` 文件: + ```js + const pathToWasm = process.argv[2]; + await contract.deploy(pathToWasm); + ``` + + + + +--- + +## 部署合约 + +### 开发部署 + + + + + + + + + +### 部署到账户 + + + + + + + + + + + + + + +--- + +## 日志 + +显示合约日志。 + + + + + 当您想查看代码中的信息时,可以使用 `println` 或 `dbg!`。 + + + + 在 Rust 中,代码的输出默认被捕获,不显示在终端中。为了查看输出,您必须使用 `--nocapture` 标志 + + 例如 `cargo test -- --nocapture` + + 如果您想访问合约日志,可以在 `tx_outcome.logs()` Vec 中找到它们。 + + ```rust + let tx_outcome = contract + .call_function("set_greeting", json!({"greeting": "Hello World!"})) + .transaction() + .gas(Gas::from_tgas(100)) + .with_signer(contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await?; + assert!(tx_outcome.is_success()); + + dbg!(tx_outcome.logs()); + // [tests/test_basics.rs:29:5] tx_outcome.logs() = [ + // "Saving greeting: Hello World!", + // ] + ``` + + + + + 当您想查看代码中的调试信息时,使用 `console.log` 方法。 + + ```js + const balance = await account.balance(); + + console.log('balance: ', balance); + // balance: { + // total: , + // stateStaked: , + // staked: , + // available: + // } + ``` + + + + +--- + +## 账户余额 + + + + + + + + + + + + + + +--- + +## 交易 + +### 调用 + + + + + + + + + + + + + + +### 查看 + + + + + + + + + + + + + + +--- + +## 即时修补状态 + +在沙盒模式下,您可以使用 `patchState` 添加或修改任何合约状态、合约代码、账户或访问密钥。 + +您可以通过 `DeployContract`、`CreateAccount` 和 `AddKey` [操作](https://nomicon.io/RuntimeSpec/Actions#addkeyaction)使用普通交易更改合约代码、账户和访问密钥。但这限制您只能修改自己的账户或子账户。`patchState` 允许您对任何账户执行这些操作。 + + + + + + + + + + + + + + 要查看如何执行此操作的完整示例,请参阅 [patch-state 测试](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts)。 + + + + + + +作为 `patchState` 的替代方案,您可以停止节点,在创世状态时转储状态,编辑创世状态,然后重新启动节点。 +这种方法更复杂,而且如果不重新启动节点就无法执行。 + + +--- + +## 时间旅行 + +`sandbox` 支持将区块链状态向前推进到未来。这意味着需要时间敏感数据的合约不需要等待相同的时间让沙盒中的区块产生。我们只需调用 `sandbox.fast_forward` 就可以在时间上向前推进: + + + + + + + _[在 Github 上查看完整示例](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs)._ + + + + + + + + + + + +--- + +## 使用 Testnet + +NEAR Sandbox 的设置使您可以编写一次测试并针对本地 Sandbox 节点(默认行为)或 [NEAR TestNet](../../protocol/network/networks) 运行它们。这可能有用的一些原因: + +* 提高对您的合约按预期工作的信心 +* 您可以针对已部署的 testnet 合约进行测试 +* 如果 Sandbox 模式下的某些内容似乎不对,您可以将其与 testnet 进行比较 + + + + + + + + 如果您也可以在每次迭代中创建一个新账户。 + + + + + + + 您可以通过三种方式切换到 testnet 模式: + + + + 创建 Worker 时将网络设置为 `testnet` 并传递您的主账户(您拥有私钥的账户): + + ```ts + const worker = await Worker.init({ + network: 'testnet', + testnetMasterAccountId: '', + initialBalance: NEAR.parse(" N").toString(), + }) + ``` + + + + + + 运行测试时设置 `NEAR_WORKSPACES_NETWORK` 和 `TESTNET_MASTER_ACCOUNT_ID`(您拥有私钥的账户)环境变量: + + ```bash + NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js + ``` + + 如果您设置了此环境变量并将 `{network: 'testnet', testnetMasterAccountId: }` 传递给 `Worker.init`,配置对象优先。 + + + + + + 如果您使用 AVA,可以使用自定义配置文件。其他测试运行器允许类似的配置文件;请根据您的情况调整以下说明。 + + 在与您的 `package.json` 相同的目录中创建一个名为 `ava.testnet.config.cjs` 的文件,内容如下: + + ```js + module.exports = { + ...require('near-workspaces/ava.testnet.config.cjs'), + ...require('./ava.config.cjs'), + }; + module.exports.environmentVariables = { + TESTNET_MASTER_ACCOUNT_ID: '', + }; + ``` + + 其中主账户是您拥有私钥的账户。 + + [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) 导入为您设置 `NEAR_WORKSPACES_NETWORK` 环境变量。这种方法的一个好处是,您可以轻松忽略只应在 Sandbox 模式下运行的文件。 + + 现在您还需要在 `package.json` 的 `scripts` 部分添加 `test:testnet` 脚本: + + ```diff + "scripts": { + "test": "ava", + + "test:testnet": "ava --config ./ava.testnet.config.cjs" + } + ``` + + + + 要使用这些账户,您需要创建 `.near-credentials/workspaces/testnet` 目录,并为您的主账户添加文件,例如: + + ```js + // .near-credentials/workspaces/testnet/.testnet.json + {"account_id":".testnet","public_key":"ed25519:...","private_key":"ed25519:..."} + ``` + + 示例: + + + + + + + +--- + +## 复制合约 + +[复制区块链](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) 是将数据从一个网络复制到另一个网络。NEAR Sandbox 使从 Mainnet 或 Testnet 合约复制数据到您的本地 Sandbox 环境变得容易: + + + + + + 指定您要从 `testnet` 拉取的合约名称,以及引用特定时间的特定区块 ID。(以防您引用的合约已被更改或更新) + + 创建一个名为 `pull_contract` 的函数,该函数将从链上拉取合约的 `.wasm` 文件并将其部署到您的本地沙盒。您必须用所有数据重新初始化它才能运行测试。这是因为合约的数据太大,RPC 服务无法拉取。(限制设置为 50Mb) + + + + + + + +```ts +const refFinance = await root.importContract({ + mainnetContract: 'v2.ref-finance.near', + blockId: 50_000_000, + withData: true, +}); +``` + +这会将 [v2.ref-finance.near](https://nearblocks.io/address/v2.ref-finance.near) 在区块 `50_000_000` 时存在的 Wasm 字节和合约状态复制到您的本地区块链。这利用了 Sandbox 的特殊[即时修补状态](#patch-state-on-the-fly)功能,即使顶级账户可能在本地不存在,也可以保持合约名称相同(请注意,这意味着它只在 Sandbox 测试模式下工作)。然后,您可以以确定性方式与合约交互,就像与 near-workspaces 创建的所有其他账户交互一样。 + + + +`withData` 只有在合约数据为 50kB 或更少时才能开箱即用。这是由于 RPC 服务器的默认配置;请参阅[此处的"注意"说明](/api/rpc/contracts#view-contract-state)。 + + + +查看[复制合约的 TypeScript 示例](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts)。 + + + + + +--- + +## 代码片段 + +### 代码片段 I:测试 Hello NEAR + +让我们看看我们的[快速入门项目](../quickstart) [👋 Hello NEAR](https://github.com/near-examples/hello-near-examples) 的测试,我们在账户上部署合约并测试它正确地检索和设置问候语。 + + + + + + +### 代码片段 II:测试捐款 + +在大多数情况下,我们将希望测试涉及多个用户和资金转移的复杂方法。一个完美的例子是我们的[捐款示例](https://github.com/near-examples/donation-examples),它使用户能够向受益人 `donate`(捐款)。让我们看看它的集成测试 + + + + + + +--- + +## 其他资源 + +### 高级示例 + +- [Rust](https://github.com/near/near-sandbox-rs/tree/main/examples) +- [JavaScript](https://github.com/near/near-workspaces-js/tree/main/__tests__) + +### 使用 Workspaces 和 AVA 进行测试驱动设计 + +以下视频介绍如何将 TDD 与 Workspaces 和 AVA 用于简单合约: + + diff --git a/zh/smart-contracts/testing/introduction.mdx b/zh/smart-contracts/testing/introduction.mdx new file mode 100644 index 00000000000..4742d63deda --- /dev/null +++ b/zh/smart-contracts/testing/introduction.mdx @@ -0,0 +1,17 @@ +--- +title: 介绍 +description: "了解 NEAR 智能合约的测试,包括单元测试、集成测试以及确保合约可靠性和安全性的最佳实践。" +--- + +在开发智能合约时,您需要测试它是否按预期工作,更重要的是,是否安全地运行。在 NEAR,我们开发了工具来帮助您进行此类测试。 + +基本上,您可以执行两种类型的测试: + +1. **单元测试**用于单独测试方法。它们以合约的语言编写,并在本地执行。 +2. **集成测试**用于测试合约在真实环境中的行为。您可以用 Rust 或 Typescript 编写它们,并在本地 Sandbox 或 NEAR testnet 中执行。 + +我们建议所有开发人员实施这两种类型的测试,因为每种测试都适合检测不同类型的错误,并使您的代码更有意图。此外,我们强烈建议先在 testnet 上发布所有项目,让用户有时间试用,然后再在 mainnet 上发布。 + +--- +## 设置测试 +测试智能合约涉及使用不同的软件,具体取决于您正在执行哪种类型的测试。为了省去您自行设置的麻烦,我们建议您从我们的[示例项目](https://github.com/near-examples)之一复制结构。 diff --git a/zh/smart-contracts/testing/unit-test.mdx b/zh/smart-contracts/testing/unit-test.mdx new file mode 100644 index 00000000000..5b73b0dd547 --- /dev/null +++ b/zh/smart-contracts/testing/unit-test.mdx @@ -0,0 +1,57 @@ +--- +title: 单元测试 +description: "了解如何为 NEAR 智能合约编写和运行单元测试,以隔离方式测试各个方法和函数。" +--- + +import { Github } from '/snippets/github.jsx' + +单元测试允许您单独测试合约方法。它们适合检查存储是否正确更新,以及方法是否返回其预期值。它们以合约的语言编写,并在本地执行。 + +如果您使用了我们的[示例](https://github.com/near-examples/docs-examples)之一作为模板,则只需导航到合约的文件夹,并使用 `yarn test`。如果没有,那么我们建议您从我们的模板之一复制必要的节点文件(例如 `package.json`)。 + + +您可以从每个项目的根文件夹运行 `yarn test`,以同时运行单元测试和[集成测试](/smart-contracts/testing/integration-test)。 + + +--- + +## 代码片段 I:测试计数器 + +[计数器示例](https://github.com/near-examples/counters)中的测试依赖于基本函数来检查 `increment`、`decrement` 和 `reset` 方法是否正常工作。 + + + + + + +--- + +## 代码片段 II:修改上下文 + +在进行单元测试时,您可以通过 `VMContextBuilder` 修改[环境变量](../anatomy/environment)。这将使您能够,例如,模拟来自不同用户的调用,带有特定的附加存款和 GAS。这里我们展示了一个代码片段,说明如何通过操纵 `predecessor` 和 `attached_deposit` 来测试我们[捐款示例](https://github.com/near-examples/donation-examples)中的 `donate` 方法。 + + + + + + +--- + +## ⚠️ 局限性 + +单元测试对于检查代码完整性和检测孤立方法中的基本错误很有用。但是,由于单元测试不在区块链上运行,因此有很多事情它们无法检测。单元测试不适合: + +- 测试 [gas](../anatomy/environment) 和[存储](../anatomy/storage)使用 +- 测试[转账](../anatomy/actions) +- 测试[跨合约调用](../anatomy/crosscontract) +- 测试复杂交互,即多个用户向合约存款 + +对于所有这些情况,有必要用[集成测试](/smart-contracts/testing/integration-test)**补充**单元测试。 diff --git a/zh/smart-contracts/tutorials/advanced/near-drop.mdx b/zh/smart-contracts/tutorials/advanced/near-drop.mdx new file mode 100644 index 00000000000..997c48c8561 --- /dev/null +++ b/zh/smart-contracts/tutorials/advanced/near-drop.mdx @@ -0,0 +1,283 @@ +--- +title: Near Drop +description: "了解 NEAR Drop 如何通过私钥实现代币投送(NEAR、FT、NFT)领取。" +--- + +import { Github } from '/snippets/github.jsx' + +NEAR Drop 是一个智能合约,允许用户创建代币投送(`$NEAR`、同质化代币和非同质化代币),并将其链接到特定的私钥。拥有私钥的人可以将投送领取到现有账户,或要求合约为他们创建一个新账户。 + +特别是,它展示了: + +1. 如何创建代币投送(NEAR、FT 和 NFT) +2. 如何利用函数调用密钥实现出色的用户体验 + + + +此示例展示了 [Keypom](https://github.com/keypom/keypom) 和[代币投送工具](https://docs.near.org/toolbox?tab=linkdrops)用于向用户分发代币的合约的简化版本 + + + +--- + +## 合约概述 + +合约公开了 3 个方法来创建 NEAR 代币、FT 和 NFT 的投送。要领取代币,合约公开了两个方法:一个用于在现有账户中领取,另一个将创建新账户并将代币领取到其中。 + +这个合约利用了 NEAR 独特的[函数调用密钥](/protocol/accounts-contracts/access-keys)功能,允许合约代表用户创建新账户并领取代币。 + +想象 Alice 想要向 Bob 投送一些 NEAR: + +1. Alice 将调用 `create_near_drop`,传递一些 NEAR 金额和一个**公共**访问密钥 +2. 合约将检查 Alice 是否附加了足够的代币并创建投送 +3. 合约将 `PublicKey` 作为 `FunctionCall Key` 添加到自身,**只允许调用领取方法** +4. Alice 将`私钥`给 Bob +5. Bob 将使用密钥签署调用 `claim_for` 方法的交易 +6. 合约将检查密钥是否链接到投送,如果是,它将发送投送 + +需要注意的是,在步骤(5)中,Bob 将使用合约的账户签署交易,而不是他自己的账户。记住在步骤(3)中,合约将密钥添加到自身,这意味着任何拥有密钥的人都可以以合约的名义调用领取方法。 + + + +#### `create_near_drop(public_keys, amount_per_drop)` +创建 `#public_keys` 个投送,每个包含 `amount_per_drop` NEAR 代币 + +#### `create_ft_drop(public_keys, ft_contract, amount_per_drop)` +创建 `#public_keys` 个投送,每个包含对应 `ft_contract` 的 `amount_per_drop` FT 代币 + +#### `create_nft_drop(public_key, nft_contract)` +创建一个带有 NFT 代币的投送,该代币将来自 `nft_contract` + +#### `claim_for(account_id)` +领取投送,将发送到现有的 `account_id` + +#### `create_account_and_claim(account_id)` +创建 `account_id`,然后将代币投送到其中 + + + +--- + +## 合约状态 + +我们可以在合约的状态中看到,合约跟踪不同的 `PublicKeys`,并将它们链接到特定的 `DropId`,这只是 `Drop`(见下文)的标识符。 + +- `top_level_account`:将用于创建新账户的账户,通常为 `testnet` 或 `mainnet` +- `next_drop_id`:用于为每个投送分配唯一标识符的简单计数器 +- `drop_id_by_key`:`PublicKey` 和 `DropId` 之间的 `Map`,允许合约知道给定密钥可以领取哪些投送 +- `drop_by_id`:链接每个 `DropId` 与实际 `Drop` 数据的简单 `Map`。 + + + +--- + +## 投送类型 + +有 3 种类型的投送,区别在于用户领取对应投送时将收到什么 - NEAR、同质化代币(FT)或非同质化代币(NFT)。 + + + + + + + + +请注意,在此示例实现中,用户不能混合投送。即您可以投送 NEAR 代币、FT 或 NFT,但不能混合(即您不能在同一个投送中投送 1 NEAR 代币和 1 FT 代币) + + + +--- + +## 创建投送 + +所有 `create` 都从检查用户是否存入了足够的资金来创建投送开始,然后继续将访问密钥作为[函数调用密钥](/protocol/accounts-contracts/access-keys)添加到合约的账户。 + + + + + + + + + + + + + + + + + +### 存储成本 + +虽然我们不会详细介绍存储成本的计算方式,但重要的是要了解考虑了什么: + +1. 存储每个投送的成本,包括存储与 `Drop` 结构相关的所有字节 +2. 存储映射中每个 `PublicKey -> DropId` 关系的成本 +3. 在账户中存储每个 `PublicKey` 的成本 + +请注意,(3)不是将 `PublicKey` 的字节表示存储在状态中的成本,而是将密钥作为函数调用密钥添加到合约账户的成本。 + +--- + +## 领取投送 + +为了领取投送,用户需要使用`私钥`签署交易,该私钥是添加到合约的`公钥`的对应部分。 + +所有 `Drop` 都有一个 `counter`,每次领取投送时减少 1。这样,当所有投送都被领取(`counter` == 0)时,我们可以从投送中删除所有信息。 + +有两种方式领取投送:为现有账户领取和为新账户领取。它们之间的主要区别在于,前者将代币发送到现有账户,而后者将创建一个新账户并将代币发送到其中。 + + + + + + + + + + + + + +--- + +### 测试合约 + +合约已经包含沙盒测试来验证其功能。要执行测试,请运行以下命令: + + + + + ```bash + cargo test + ``` + + + + + +`集成测试`使用沙盒创建 NEAR 用户并模拟与合约的交互。 + + +--- + +### 将合约部署到 NEAR 网络 + +要部署合约,您需要创建一个 NEAR 账户。 + + + + + ```bash + # Create a new account pre-funded by a faucet + near create-account --useFaucet + ``` + + + + + ```bash + # Create a new account pre-funded by a faucet + near account create-account sponsor-by-faucet-service .testnet autogenerate-new-keypair save-to-keychain network-config testnet create + ``` + + + +然后构建并部署合约: + +```bash +cargo near build + +cargo near deploy with-init-call new json-args '{"top_level_account": "testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send +``` + +--- + +### CLI:与合约交互 + +要通过控制台与合约交互,可以使用以下命令: + + + + + ```bash + # create a NEAR drop + near call create_near_drop '{"public_keys": ["ed25519:AvBVZDQrg8pCpEDFUpgeLYLRGUW8s5h57NGhb1Tc4H5q", "ed25519:4FMNvbvU4epP3HL9mRRefsJ2tMECvNLfAYDa9h8eUEa4"], "amount_per_drop": "10000000000000000000000"}' --useAccount --deposit 1 --gas 100000000000000 + + # create a FT drop + near call create_ft_drop '{"public_keys": ["ed25519:HcwvxZXSCX341Pe4vo9FLTzoRab9N8MWGZ2isxZjk1b8", "ed25519:5oN7Yk7FKQMKpuP4aroWgNoFfVDLnY3zmRnqYk9fuEvR"], "amount_per_drop": "1", "ft_contract": ""}' --useAccount --gas 100000000000000 + + # create a NFT drop + near call create_nft_drop '{"public_key": "ed25519:HcwvxZXSCX341Pe4vo9FLTzoRab9N8MWGZ2isxZjk1b8", "nft_contract": ""}' --useAccount --gas 100000000000000 + + # claim to an existing account + # see the full version + + # claim to a new account + # see the full version + ``` + + + + + ```bash + # create a NEAR drop + near contract call-function as-transaction create_near_drop json-args '{"public_keys": ["ed25519:AvBVZDQrg8pCpEDFUpgeLYLRGUW8s5h57NGhb1Tc4H5q", "ed25519:4FMNvbvU4epP3HL9mRRefsJ2tMECvNLfAYDa9h8eUEa4"], "amount_per_drop": "10000000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet sign-with-keychain send + + # create a FT drop + near contract call-function as-transaction create_ft_drop json-args '{"public_keys": ["ed25519:HcwvxZXSCX341Pe4vo9FLTzoRab9N8MWGZ2isxZjk1b8", "ed25519:5oN7Yk7FKQMKpuP4aroWgNoFfVDLnY3zmRnqYk9fuEvR"], "amount_per_drop": "1", "ft_contract": ""}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + + # create a NFT drop + near contract call-function as-transaction create_nft_drop json-args '{"public_key": "ed25519:HcwvxZXSCX341Pe4vo9FLTzoRab9N8MWGZ2isxZjk1b8", "nft_contract": ""}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + + # claim to an existing account + near contract call-function as-transaction claim_for json-args '{"account_id": ""}' prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-plaintext-private-key --signer-public-key ed25519:AvBVZDQrg8pCpEDFUpgeLYLRGUW8s5h57NGhb1Tc4H5q --signer-private-key ed25519:3yVFxYtyk7ZKEMshioC3BofK8zu2q6Y5hhMKHcV41p5QchFdQRzHYUugsoLtqV3Lj4zURGYnHqMqt7zhZZ2QhdgB send + + # claim to a new account + near contract call-function as-transaction create_account_and_claim json-args '{"account_id": ""}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-plaintext-private-key --signer-public-key ed25519:4FMNvbvU4epP3HL9mRRefsJ2tMECvNLfAYDa9h8eUEa4 --signer-private-key ed25519:2xZcegrZvP52VrhehvApnx4McL85hcSBq1JETJrjuESC6v6TwTcr4VVdzxaCReyMCJvx9V4X1ppv8cFFeQZ6hJzU send + ``` + + + + +**本文版本信息** + +在撰写本文时,此示例适用于以下版本: + +- near-cli: `0.17.0` +- rustc: `1.82.0` + + diff --git a/zh/smart-contracts/tutorials/advanced/update.mdx b/zh/smart-contracts/tutorials/advanced/update.mdx new file mode 100644 index 00000000000..fde7aaf29e8 --- /dev/null +++ b/zh/smart-contracts/tutorials/advanced/update.mdx @@ -0,0 +1,67 @@ +--- +title: 自升级与状态迁移 +description: "了解 NEAR 智能合约升级,包括自更新合约、状态迁移和版本控制模式。" +--- + +import { Github } from '/snippets/github.jsx' + +关于如何处理更新和[状态迁移](/smart-contracts/release/upgrade)的三个示例: +1. [状态迁移](https://github.com/near-examples/update-migrate-rust/tree/main/basic-updates):如何实现 `migrate` 方法以在合约更新之间迁移状态。 +2. [状态版本控制](https://github.com/near-examples/update-migrate-rust/tree/main/enum-updates):如何对状态使用现成的版本控制,以便以后简化更新。 +3. [自更新](https://github.com/near-examples/update-migrate-rust/tree/main/self-updates):如何实现可以更新自身的合约。 + +--- + +## 状态迁移 +[状态迁移示例](https://github.com/near-examples/update-migrate-rust/tree/main/basic-updates)展示了如何处理合约更新之间的破坏性状态更改。 + +它由 2 个合约组成: +1. 基础:一个人们可以写消息的留言板。 +2. 更新:一个更新,我们在其中删除一个参数并更改内部结构。 + + + + + +#### 迁移方法 +迁移方法反序列化当前状态(`OldState`)并遍历消息,将它们更新为包含 `payment` 字段的新 `PostedMessage`。 + + +注意,migrate 实际上是一个[初始化方法](/smart-contracts/anatomy/storage),它忽略了现有状态(`[#init(ignore_state)]`),因此能够执行并重写状态。 + + +--- + +## 状态版本控制 +[状态版本控制示例](https://github.com/near-examples/update-migrate-rust/tree/main/enum-updates)展示了如何使用 +[枚举](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html)在合约上实现状态版本控制。 + +版本控制简化了合约更新,因为您只需要添加新版本的结构。 +所有版本可以共存,因此您不需要更改以前存在的结构。 + +该示例由 2 个合约组成: +1. 基础:使用版本化 `PostedMessages`(`PostedMessagesV1`)的留言板合约。 +2. 更新:添加新版本 `PostedMessages`(`PostedMessagesV2`)的更新。 + + + + + +--- + +## 自更新 +[自更新示例](https://github.com/near-examples/update-migrate-rust/tree/main/self-updates)展示了如何实现可以更新自身的合约。 + +它由 2 个合约组成: +1. 基础:一个人们可以写消息的留言板,实现了 `update_contract` 方法。 +2. 更新:一个更新,我们在其中删除一个参数并更改内部结构。 + + + + diff --git a/zh/smart-contracts/tutorials/basic-contracts.mdx b/zh/smart-contracts/tutorials/basic-contracts.mdx new file mode 100644 index 00000000000..535e9af08e8 --- /dev/null +++ b/zh/smart-contracts/tutorials/basic-contracts.mdx @@ -0,0 +1,265 @@ +--- +title: 使用我们的基础示例 +description: "通过实际示例学习 NEAR 智能合约基础知识:计数器、留言板、捐款、抛硬币和 Hello World。" +--- + +我们创建了一系列基础智能合约,帮助您开始在 NEAR 上构建智能合约。 + +![img](/assets/docs/smart-contracts/tutorials/basic-contracts.png) + +这些示例涵盖了状态管理、函数调用和代币交互等基本概念。每个示例都设计得简单易懂,非常适合初学者。 + + + +在处理这些示例之前,请务必先遵循我们的[快速入门指南](../quickstart) + + + +--- + +## 示例 + + + + 一个简单的智能合约,在其状态中存储 `string` 消息。[在 NearPlay 中打开](https://nearplay.app/embed/919be62a-e1bc-49b5-b858-fb3d567d8489) + + + 一个友好的计数器,存储一个数字,并提供递增、递减和重置它的方法。[在 NearPlay 中打开](https://nearplay.app/embed/3450a8a0-57dc-4d3a-b5d0-7bed58a0c2a9) + + + 用户可以在留言板上签名,可选择支付 `0.01 Ⓝ` 将其消息标记为"高级"。[在 NearPlay 中打开](https://nearplay.app/embed/3862fb26-aeee-471f-90ad-f900e820de3f) + + + 将 NEAR 代币转发给受益人,同时跟踪所有捐款。了解合约如何处理代币转账。[在 NearPlay 中打开](https://nearplay.app/embed/436d9ff9-a15c-418a-8350-ae5eead387c7) + + + 猜测抛硬币的结果并赚取积分。演示如何在区块链上处理随机性。[在 NearPlay 中打开](https://nearplay.app/embed/10aab4a0-deee-45a3-b847-8d229358cad3) + + + + +**无需设置即可尝试** +想立即尝试这些示例?点击上方任意卡片上的**"在 NearPlay 中打开"**按钮,开始在 [NearPlay](https://nearplay.app) 中编码 - 无需安装! + + +--- + +## 示例结构 + +所有示例都遵循一致的结构,使在它们之间导航变得容易。每个存储库包含在 **Rust**、**Javascript** 以及有时 **Python** 中实现的**相同智能合约**,以及用于与合约交互的**简单前端**。 + +```bash +┌── contract-rs # contract's code in Rust +│ ├── src # contract's code +│ ├── tests # sandbox test +│ ├── Cargo.toml +│ └── rust-toolchain.toml +├── contract-ts # contract's code in Typescript +│ ├── src # contract's code +│ ├── sandbox-test # sandbox test +│ ├── package.json +│ └── tsconfig.json +├── contract-py # contract's code in Python (some examples) +│ ├── contract.py # contract's code +│ ├── tests # sandbox test +│ ├── pyproject.toml +│ └── uv.lock +├── frontend # React + Next.JS frontend +│ ├── src # frontend's implementation +│ ├── public +│ ├── package.json +│ ├── next.config.js +│ └── jsconfig.json +└── README.md +``` + +--- + +## 前端 + +每个示例都包含一个非常容易启动的 **Next.JS** 前端: + +```bash +cd frontend +yarn +yarn dev +``` + +这些前端对于演示如何将 Web 应用程序连接到 NEAR,以及如何与智能合约交互非常有用。 + + +每个前端都连接到**合约的预部署版本**。查看 `./frontend/config.js` 以了解正在使用哪个合约,或将其更改为您自己部署的合约 + + +### NEAR Connector Hooks + +所有前端都使用 [`near-connect-hooks`](https://www.npmjs.com/package/near-connect-hooks),它封装了 [NEAR Connector](../../web3-apps/tutorials/wallet-login) 的功能,用于处理 Web 应用程序与 NEAR 区块链之间的连接。 + +`near-connect-hooks` 公开了一个 `NearProvider`,用于包装整个应用程序,通常在 `pages/_app.js` 中: + +```jsx +import { NearProvider } from "near-connect-hooks"; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + + ); +} +``` + +
+ +然后,我们可以在任何组件中使用 **`useNearWallet` hook** 来访问所有 NEAR 相关功能,例如登录/注销、视图和调用函数以及签署交易: + +```jsx +import { useNearWallet } from 'near-connect-hooks'; + +export default function App() { + // Login / Logout functionality + const { loading, signIn, signOut, signedAccountId } = useNearWallet(); + + // To interact with the contract + const { viewFunction, callFunction, signAndSendTransactions } = useNearWallet(); +} +``` + +--- + +## 智能合约 + +所有存储库都包含以不同语言实现的相同智能合约,包括 **Rust**、**Javascript** 以及有时 **Python**。 + +合约按照每个 SDK 的最新版本实现,并包含展示如何在真实环境中正确测试智能合约的沙盒测试。 + +### 测试 + +每个合约都包含模拟真实用户交互的沙盒测试。例如,在 `留言板` 示例中,测试涵盖了多个账户在留言板上签名的场景,包括高级消息。 + + + + +```bash +cd contract-rs +cargo test +``` + + + + +```bash +cd contract-ts +yarn +yarn test +``` + + + + +```bash +cd contract-py +uv run pytest +``` + + + + +### 创建账户 + +所有智能合约都可以使用 `NEAR CLI` 构建和部署。一个好的第一步是始终创建一个新的 NEAR 账户来部署您的合约: + +```bash +near create-account --useFaucet +``` + + + +这里我们使用 `--useFaucet` 标志创建一个新账户,并从 [testnet 水龙头](../../getting-started/faucet)预先充值 + + + +### 构建和部署 + +创建账户来托管合约后,您可以构建并部署它: + + + + +```bash +cd contract-rs +cargo near deploy build-non-reproducible-wasm +``` + + + + +```bash +cd contract-ts +npm run build +near deploy ./build/.wasm +``` + + + + +```bash +cd contract-py +uvx nearc contract.py +near deploy .wasm +``` + + + + +### 通过 CLI 交互 + +部署合约后,查看每个存储库的 `README.md` 以了解可以调用的可用方法。 + +作为一般指南,`NEAR CLI` 有两种主要方式与智能合约交互: + +```bash +# Call a read-only (view) method +near view + +# Call a method that changes state +near call --useAccount + +# Call a method and attach NEAR tokens +near call --useAccount --deposit 1 +``` + + +查看每个存储库的 README 以了解该合约中可用的具体方法。 + + +--- + +## 继续前进 + +探索这些基础示例后,您可以: + +- **修改合约** - 尝试添加新功能以加深您的理解 +- **学习基础知识** - 查看[合约解析](/smart-contracts/anatomy/anatomy)和[存储](/smart-contracts/anatomy/storage) +{/* - **探索高级示例** - 请参阅[跨合约调用](./xcc)和[工厂合约](./factory) */} diff --git a/zh/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx b/zh/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx new file mode 100644 index 00000000000..b7c4ef1cc62 --- /dev/null +++ b/zh/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx @@ -0,0 +1,332 @@ +--- +title: 复杂跨合约调用 +description: "批量操作、并行操作和回调处理。" +--- + +import { Github } from '/snippets/github.jsx' + +此示例展示了 NEAR 区块链上复杂跨合约调用的 3 个实例,展示了如何批量多次函数调用到同一合约、并行调用多个合约以及在回调中处理响应。它包括智能合约和前端组件。 + + +**简单跨合约调用** + +查看关于如何使用[简单跨合约调用](/smart-contracts/tutorials/cross-contracts/xcc)的教程 + + + +--- + +## 获取跨合约调用示例 + +您有两种方式启动捐款示例: + +1. 您可以通过 `Github Codespaces` 使用应用程序,这将打开一个基于 Web 的交互式环境。 +2. 在本地克隆存储库并从您的计算机中使用它。 + +| Codespaces | 本地克隆 | +| ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/near-examples/cross-contract-calls?quickstart=1) | 🌐 `https://github.com/near-examples/cross-contract-calls` | + +--- + +## 示例结构 + +智能合约有两种版本:Rust 和 JavaScript + + + + + +```bash +┌── sandbox-ts # sandbox testing +│ ├── external-contracts +│ │ ├── counter.wasm +│ │ ├── guest-book.wasm +│ │ └── hello-near.wasm +│ └── main.ava.ts +├── src # contract's code +│ ├── internal +│ │ ├── batch_actions.ts +│ │ ├── constants.ts +│ │ ├── multiple_contracts.ts +│ │ ├── similar_contracts.ts +│ │ └── utils.ts +│ └── contract.ts +├── package.json +├── README.md +└── tsconfig.json +``` + + + + + +```bash +┌── tests # sandbox testing +│ ├── external-contracts +│ │ ├── counter.wasm +│ │ ├── guest-book.wasm +│ │ └── hello-near.wasm +│ └── main.ava.ts +├── src # contract's code +│ ├── batch_actions.rs +│ ├── lib.rs +│ ├── multiple_contracts.rs +│ └── similar_contracts.rs +├── Cargo.toml # package manager +├── README.md +└── rust-toolchain.toml +``` + + + + + +--- + +## 智能合约 + +### 批量操作 + +您可以将针对同一合约的多个操作聚合到一个批量交易中。 +以这种方式调用的方法按顺序执行,额外的好处是,如果一个失败, +则**所有都会被回滚**。 + + + + + + + +#### 获取最后一个响应 + +在这种情况下,回调可以访问链中**最后一个操作**返回的值。 + + + + + + + + +--- + +### 调用多个合约 + +一个合约可以调用多个其他合约。这会创建多个并行执行的交易。 +如果其中一个失败,其余的**不会被回滚**。 + + + + + + + +#### 获取所有响应 + +在这种情况下,回调可以访问一个**响应数组**,其中包含每个调用返回的值或错误消息。 + + + + + + + + +--- + +### 多次调用 - 相同结果类型 + +此示例是前一个的特殊情况([调用多个合约](#calling-multiple-contracts))。 +它简单地展示了通过直接访问 `promise_result` 数组来检查结果的不同方式。 + +在这种情况下,我们调用将返回相同类型的多个合约: + + + + + + + +#### 获取所有响应 + +在这种情况下,回调再次可以访问一个**响应数组**,我们可以遍历它来检查结果。 + + + + + + + + +--- + +### 测试合约 + +合约已经包含一套单元测试和沙盒测试来验证其功能。要执行测试,请运行以下命令: + + + + + ```bash + cd contract-advanced-ts + yarn + yarn test + ``` + + + + + ```bash + cd contract-advanced-rs + cargo test + ``` + + + + + + +`集成测试`使用沙盒创建 NEAR 用户并模拟与合约的交互。 + + +### 将合约部署到 NEAR 网络 + +要部署合约,您需要创建一个 NEAR 账户。 + + + + + ```bash + # Create a new account pre-funded by a faucet + near create-account --useFaucet + ``` + + + + + ```bash + # Create a new account pre-funded by a faucet + near account create-account sponsor-by-faucet-service .testnet autogenerate-new-keypair save-to-keychain network-config testnet create + ``` + + + +进入包含智能合约的目录(`cd contract-advanced-ts` 或 `cd contract-advanced-rs`),构建并部署它: + + + + + + ```bash + npm run build + near deploy ./build/cross_contract.wasm --initFunction new --initArgs '{"hello_account":"hello.near-example.testnet","guestbook_account":"guestbook_account.near-example.testnet","counter_account":"counter_account.near-example.testnet"}' + ``` + + + + + ```bash + cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet","guestbook_account":"guestbook_account.near-example.testnet","counter_account":"counter_account.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send + ``` + + + + + +### CLI:与合约交互 + +要通过控制台与合约交互,可以使用以下命令: + + + + + ```bash + # Execute contracts sequentially + # Replace with your account ID + near call batch_actions --useAccount --gas 300000000000000 + + # Execute contracts in parallel + # Replace with your account ID + near call multiple_contracts --useAccount --gas 300000000000000 + + # Execute multiple instances of the same contract in parallel + # Replace with your account ID + near call similar_contracts --useAccount --gas 300000000000000 + ``` + + + + + ```bash + # Execute contracts sequentially + # Replace with your account ID + near contract call-function as-transaction batch_actions json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + + # Execute contracts in parallel + # Replace with your account ID + near contract call-function as-transaction multiple_contracts json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + + # Execute multiple instances of the same contract in parallel + # Replace with your account ID + near contract call-function as-transaction similar_contracts json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + ``` + + + + +如果您在某些时候收到"Exceeded the prepaid gas"错误,请尝试增加调用其他合约时函数内使用的 gas 量 + + + +**本文版本信息** + +在撰写本文时,此示例适用于以下版本: + +- near-cli: `4.0.13` +- node: `18.19.1` +- rustc: `1.77.0` + + diff --git a/zh/smart-contracts/tutorials/cross-contracts/xcc.mdx b/zh/smart-contracts/tutorials/cross-contracts/xcc.mdx new file mode 100644 index 00000000000..ead183a297a --- /dev/null +++ b/zh/smart-contracts/tutorials/cross-contracts/xcc.mdx @@ -0,0 +1,262 @@ +--- +title: 跨合约调用 +description: "了解如何在 NEAR 上执行基本的跨合约调用以设置和检索问候语。" +--- + +import { Github } from '/snippets/github.jsx' + +此示例执行最简单的跨合约调用:它调用我们的 [Hello NEAR](https://github.com/near-examples/hello-near-examples) 示例来设置和检索问候语。 +这是进行跨合约调用的最简单示例之一,是进入互操作合约世界的完美入口。 + + +**高级跨合约调用** +查看关于如何[批量和并行](./advanced-xcc)执行跨合约调用的教程 + + +--- + +## 克隆示例 + +您有两种方式启动项目: + +1. 您可以通过 `Github Codespaces` 使用应用程序,这将打开一个基于 Web 的交互式环境。 +2. 在本地克隆存储库并从您的计算机中使用它。 + +| Codespaces | 本地克隆 | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/near-examples/cross-contract-calls?quickstart=1) | 🌐 `https://github.com/near-examples/cross-contract-calls` | + +--- + +## 示例结构 + +智能合约有两种版本:Rust 和 JavaScript + + + + + +```bash +┌── sandbox-ts # sandbox testing +│ ├── hello-near +│ │ └── hello-near.wasm +│ └── main.ava.ts +├── src # contract's code +│ └── contract.ts +├── package.json +├── README.md +└── tsconfig.json +``` + + + + + +```bash +┌── tests # sandbox testing +│ ├── hello-near +│ │ └── hello-near.wasm +│ └── tests.rs +├── src # contract's code +│ ├── external.rs +│ └── lib.rs +├── Cargo.toml # package manager +├── README.md +└── rust-toolchain.toml +``` + + + + + +--- + +## 智能合约 + +合约公开了查询问候语并更改它的方法。这些方法除了调用 `hello-near` 示例中的 `get_greeting` 和 `set_greeting` 之外什么都不做。 + +### 查询问候语 + +合约向 `hello.near-example.testnet` 执行跨合约调用以获取问候语消息,然后在**回调函数**中处理响应。 + + + + + + + + + + + + 这需要您定义外部合约接口: + + + + + +### 回调函数 + +回调函数处理跨合约调用的结果。在这种情况下,它只是返回从 `hello-near` 合约获得的问候语消息。 + +注意,回调函数被标记为**private**,意味着它只能由合约本身调用。 + + + + + + + + + + +--- + +## 测试合约 + +合约已经包含一套单元测试和沙盒测试来验证其功能。要执行测试,请运行以下命令: + + + + + ```bash + cd contract-simple-ts + yarn + yarn test + ``` + + + + + ```bash + cd contract-simple-rs + cargo test + ``` + + + + + +`集成测试`使用沙盒创建 NEAR 用户并模拟与合约的交互。 + + +特别是在这个项目中,集成测试首先部署 `hello-near` 合约。然后,它们测试跨合约调用是否正确设置和检索消息。您将在 `sandbox-ts/` 中找到 JavaScript 版本的集成测试,在 `tests/` 中找到 Rust 版本。 + + + + + + +### 将合约部署到 NEAR 网络 + +要部署合约,您需要创建一个 NEAR 账户。 + + + + + ```bash + # Create a new account pre-funded by a faucet + near create-account --useFaucet + ``` + + + + + ```bash + # Create a new account pre-funded by a faucet + near account create-account sponsor-by-faucet-service .testnet autogenerate-new-keypair save-to-keychain network-config testnet create + ``` + + + +进入包含智能合约的目录(`cd contract-advanced-ts` 或 `cd contract-advanced-rs`),构建并部署它: + + + + + + ```bash + npm run build + near deploy ./build/cross_contract.wasm --initFunction new --initArgs '{"hello_account":"hello.near-example.testnet"}' + ``` + + + + + ```bash + cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send + + ``` + + + + + +### CLI:与合约交互 + +要通过控制台与合约交互,可以使用以下命令: + + + + + ```bash + # Get message from the hello-near contract + # Replace with your account ID + near call query_greeting --useAccount + + # Set a new message for the hello-near contract + # Replace with your account ID + near call change_greeting '{"new_greeting":"XCC Hi"}' --useAccount + ``` + + + + + ```bash + # Get message from the hello-near contract + # Replace with your account ID + near contract call-function as-transaction query_greeting json-args '{}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + + # Set a new message for the hello-near contract + # Replace with your account ID + near contract call-function as-transaction change_greeting json-args '{"new_greeting":"XCC Hi"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send + ``` + + + +--- + +## 继续前进 + +学习的好方法是尝试扩展合约。修改跨合约示例以使用 [guest-book](https://github.com/near-examples/guest-book-examples) 合约!这样,您可以尝试进行附加资金的跨合约调用。请记住正确[处理回调](/smart-contracts/anatomy/crosscontract#callback-function),并在出现错误时将资金退还给用户。 + +### 高级跨合约调用 + +您的合约可以同时执行多个跨合约调用,创建并行执行的承诺,或作为批量交易。查看[高级跨合约调用教程](./advanced-xcc)了解更多信息。 + + +**本文版本信息** + +在撰写本文时,此示例适用于以下版本: + +- near-cli: `4.0.13` +- node: `18.19.1` +- rustc: `1.77.0` + + diff --git a/zh/smart-contracts/tutorials/factories/factory.mdx b/zh/smart-contracts/tutorials/factories/factory.mdx new file mode 100644 index 00000000000..a95ebc26596 --- /dev/null +++ b/zh/smart-contracts/tutorials/factories/factory.mdx @@ -0,0 +1,130 @@ +--- +title: 工厂合约 +description: "了解工厂合约如何使用全局合约 ID 在子账户上部署其他合约。" +--- + +import { Github } from '/snippets/github.jsx' + +工厂是一种智能合约,它存储一个全局合约 ID,并自动化在新子账户上部署合约。 + +我们有一个[**通用工厂**](https://github.com/near-examples/factory-rust),它根据全局合约 ID 部署新合约。全局合约 ID 可以是全局账户 ID 或哈希。您可以通过调用 `update_global_contract_id` 方法来更改全局合约 ID。工厂创建自身的子账户,并在其上部署相应的合约。 + + +您可以在[此处](/smart-contracts/global-contracts)了解有关 NEAR 上全局合约的更多信息。 + + +--- + +## 概述 + +工厂是一个智能合约,它: + +1. 创建自身的子账户并在其上部署其合约(`deploy`) +2. 可以更新它部署的合约 + + + + + + +--- + +## 快速入门 + +1. 确保您已安装 [rust](https://www.rust-lang.org/)。 +2. 安装 [`NEAR CLI`](/tools/cli#installation) +3. 安装 [`cargo near`](https://github.com/near/cargo-near) 扩展。 + +
+ +### 构建和部署工厂 + +您可以通过运行以下命令在 NEAR testnet 上自动编译和部署合约: + +```bash +cargo near deploy +``` + +
+ +### 将存储的合约部署到子账户 + +`deploy` 方法将创建工厂的子账户,并使用存储的全局合约 ID 在其上部署合约。它还会断言附加的存款至少是工厂中存储的最低要求存款。 + + +从技术上讲,使用全局合约部署合约不需要附加任何存款。但是要进一步初始化它,您需要一些 NEAR 代币来覆盖存储成本。由于初始化方法不能接受存款,因此在创建账户和部署合约期间附加一些最低数量的代币是有意义的。 + + +以下命令将创建 `sub.`,其上将部署全局合约。 + +```bash +near contract call-function as-transaction deploy json-args '{"name": "sub"}' prepaid-gas '100.0 Tgas' attached-deposit '0.2 NEAR' sign-as network-config testnet sign-with-keychain send +``` + +默认情况下,全局合约是[同质化代币](/primitives/ft/ft#global-contracts)原语合约 `ft.globals.primitives.testnet`。要初始化合约,您可以调用其 `new_default_meta` 方法: + +```bash +near contract call-function as-transaction sub. new_default_meta json-args '{"owner_id": "", "total_supply": "100000000000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send +``` + +然后您可以调用 `ft_metadata` 方法来验证合约已正确部署和初始化: + +```bash +near contract call-function as-read-only sub. ft_metadata json-args {} network-config testnet now +# The response should be like this: +# { +# "decimals": 24, +# "icon": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 288 288'%3E%3Cg id='l' data-name='l'%3E%3Cpath d='M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'/%3E%3C/g%3E%3C/svg%3E", +# "name": "Example NEAR fungible token", +# "reference": null, +# "reference_hash": null, +# "spec": "ft-1.0.0", +# "symbol": "EXAMPLE" +#} +``` + +
+ +### 更新存储的合约 + +`update_global_contract_id` 允许更改工厂存储的全局合约 ID。 + +该方法很有趣,因为它没有声明参数,但仍然接受输入:新合约作为字节流存储。 + +要使用它,我们需要传递我们要存储的合约 ID。它可以是账户 ID 或全局合约哈希的形式。关于它们之间的区别,您可以在[全局合约](/smart-contracts/global-contracts#solution)部分阅读。 + +```bash +near contract call-function as-transaction update_global_contract_id json-args '{"contract_id": "3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as network-config testnet sign-with-keychain send +``` + +--- + +## 工厂 - 概念与局限性 + +工厂是一个有趣的概念,这里我们进一步解释它们的一些实现方面以及它们的局限性。 + +
+ +### 自动创建账户 + +NEAR 账户只能创建自身的子账户,因此,`factory` 只能在其自己的子账户上创建和部署合约。 + +这意味着工厂: + +1. **可以**创建 `sub.factory.testnet` 并在其上部署合约。 +2. **不能**创建 `predecessor` 的子账户。 +3. **可以**创建新账户(例如 `account.testnet`),但**不能**在其上部署合约。 + +重要的是要记住,虽然 `factory.testnet` 可以创建 `sub.factory.testnet`,但在创建后对其没有控制权。 + +
+ +### Deploy 方法 + +在创建子账户期间,我们将签名者的公钥作为完全访问密钥添加到新子账户。这意味着前驱在创建后将能够控制新子账户。 + +此外,在教程中,我们将存款附加到 `deploy` 调用,该存款直接进入新子账户。当我们使用全局合约部署新合约时,我们需要在部署后初始化它。这些代币将在部署的合约初始化后覆盖存储。 diff --git a/zh/smart-contracts/tutorials/factories/global-contracts.mdx b/zh/smart-contracts/tutorials/factories/global-contracts.mdx new file mode 100644 index 00000000000..96c49e349cf --- /dev/null +++ b/zh/smart-contracts/tutorials/factories/global-contracts.mdx @@ -0,0 +1,265 @@ +--- +title: 全局合约 +sidebarTitle: 全局合约 +description: "了解如何部署全局合约并从另一个账户使用它。" +--- +import { Github } from '/snippets/github.jsx' + +如果您曾经将相同的合约代码部署到多个账户,您可能注意到每次部署都需要再次支付全部存储成本。 + +想象一下,您想将相同的 500 KB 合约部署到三个账户:每次部署为存储支付 5 NEAR —— 仅为存储相同的代码就锁定了总共 15 NEAR。 +[全局合约](/smart-contracts/global-contracts)通过允许相同的合约代码在多个账户之间共享来消除这种冗余,因此存储成本只需支付一次。 + +在本教程中,您将学习如何按[账户 ID](#global-contract-by-account-id) 或[按哈希](#global-contract-by-hash)部署和使用[全局合约](/smart-contracts/global-contracts),具体取决于您的需求。 + + +**全局合约类型** +- [按账户](/smart-contracts/global-contracts#reference-by-account):可升级的合约在特定账户 ID 下全局发布。 +- [按哈希](/smart-contracts/global-contracts#reference-by-hash):不可变的合约全局部署,并通过其代码哈希识别。 + + +## 示例 + +让我们定义两个可以使用全局合约部署的示例智能合约,并评估哪种替代方案(按账户 ID 或按哈希)对每种情况最合适。 + +- **DAO 工厂**:一个允许任何用户部署其自己的 DAO 治理实例的工具。 +- **NFT 集合工厂**:一个允许用户创建具有固定元数据和版税值的 NFT 合约的服务。 + + +**您需要全局合约吗?** +不确定是否需要全局合约?查看这些问题来决定[何时使用全局合约](/smart-contracts/global-contracts#when-to-use-global-contracts)。 + + +### 按账户 ID 的全局合约 + +例如,让我们考虑 **DAO 工厂**的情况。 + +由于相同的合约被最终用户在不同账户上多次部署,这显然达到了[全局合约](/smart-contracts/global-contracts)在经济上变得高效的阈值。此外,由于可升级性在未来可能有用(例如,修复错误或扩展功能),这是使用[按账户 ID 的全局合约](/smart-contracts/global-contracts#reference-by-account)的绝佳案例。 + +### 按哈希的全局合约 + +现在让我们以 **NFT 集合工厂**为例。 + +由于每个用户部署相同的合约,但一旦部署,它就不应该更改(安全性和不可变性至关重要),这是使用[按哈希的全局合约](/smart-contracts/global-contracts#reference-by-hash)的完美案例。 + +## 部署全局合约 + +接下来,让我们看看如何按账户 ID(对于 DAO 工厂)或按哈希(对于 NFT 集合工厂)部署全局合约。 +如前所述,全局合约可以以两种方式部署: +- 按其[哈希](/smart-contracts/global-contracts#reference-by-hash)。 +- 按所有者[账户 ID](/smart-contracts/global-contracts#reference-by-account)。 + +按哈希部署的合约实际上是不可变的,无法更新。 +按账户 ID 部署时,所有者可以重新部署合约,为其所有用户更新它。 + + +请注意,部署全局合约会产生高存储成本。代币被销毁以补偿在链上存储合约,与常规合约不同,常规合约的代币根据合约大小被锁定。 + + +### 部署 + +全局合约可以使用 [`NEAR CLI`](/tools/cli) 或通过代码使用 [NEAR API](/tools/near-api#deploy-a-global-contract)(JavaScript 和 Rust)进行部署。 + +由于引用全局合约有两种方式([按账户](/smart-contracts/global-contracts#reference-by-account)或[按哈希](/smart-contracts/global-contracts#reference-by-hash)),部署步骤取决于您想要存储在区块链上的全局合约类型。 + + + + + 该过程类似于[部署常规合约](/smart-contracts/release/deploy#deploying-the-contract),但应使用 `deploy-as-global` 命令代替 `deploy`。 + + + + + 要按账户部署全局合约,您需要使用 `as-global-account-id` 并传递将部署合约的源 ``。 + + ```bash + near contract deploy-as-global use-file as-global-account-id network-config testnet sign-with-keychain send + ``` + + + + + + 要按哈希部署全局合约,您需要使用 `as-global-hash` 函数并传递源 `` 来资助交易。 + + ```bash + near contract deploy-as-global use-file as-global-hash network-config testnet sign-with-keychain send + ``` + + + + + + + + 创建账户实例后,您可以将常规合约部署为全局合约。 + + + + + 让我们看一个按账户部署全局合约的示例。 + + 为此,使用 `deployGlobalContract` 函数,并将模式设置为 `accountId`,以及合约的代码字节。 + + + + + + + + 让我们看一个按哈希部署全局合约的示例。 + + 为此,使用 `deployGlobalContract` 函数,并将模式设置为 `codeHash`,以及合约的代码字节。 + + + + + + + + + + + 创建账户实例后,您可以将常规合约部署为全局合约。 + + + + + 让我们看一个按账户部署全局合约的示例。 + + 为此,使用 `deploy_global_contract_code` 函数并使用 `as_account_id` 方法,以及合约的代码字节。 + + + + + + + + 让我们看一个按哈希部署全局合约的示例。 + + 为此,使用 `deploy_global_contract_code` 函数并使用 `as_hash` 方法,以及合约的代码字节。 + + + + + + + + + + + +查看 [NEAR API 参考文档](/tools/near-api#deploy-a-global-contract)以获取完整的代码示例。 + + +### 使用 + +一旦[全局合约](/smart-contracts/global-contracts)已经[部署](#deployment),它可以使用 [`NEAR CLI`](/tools/cli) 或通过代码使用 [NEAR API](/tools/near-api#use-a-global-contract)(JavaScript 和 Rust)附加到任何 NEAR 账户。 + +让我们看看如何从另一个账户引用和使用您的全局合约。 + + + + + 使用 `near deploy` 命令。此类合约的行为与常规合约完全相同。 + + + + + 要按账户引用全局合约,您需要调用 `useGlobalContract` 函数并传递原始部署合约的源 `accountId`。 + + ```bash + # Using global contract deployed by account id + near contract deploy use-global-account-id without-init-call network-config testnet + ``` + + + + + + 要按哈希引用全局合约,您需要调用 `useGlobalContract` 函数并传递原始合约的源 `codeHash`。 + + ```bash + # Using global contract deployed by hash + near contract deploy use-global-hash without-init-call network-config testnet + ``` + + + + + + + + + + + 要按账户引用全局合约,您需要调用 `useGlobalContract` 函数并传递原始部署合约的源 `accountId`。 + + + + + + + + 要按哈希引用全局合约,您需要调用 `useGlobalContract` 函数并传递原始合约的源 `codeHash`。 + + ```js + const hash = bs58.encode(sha256(wasm)); + ``` + + + + + + + + + + + + + + 要按账户引用全局合约,您需要调用 `use_global_account_id` 函数并传递原始部署合约的源 `accountId`。 + + + + + + + + 要按哈希引用全局合约,您需要调用 `use_global_hash` 函数并传递原始合约的源 `hash`。 + + ```rust + // Get a global contract hash + let code = std::fs::read("../contracts/contract.wasm").unwrap(); + let global_hash = near_primitives::hash::CryptoHash::hash_bytes(&code); + ``` + + + + + + + + + + + +查看 [NEAR API 参考文档](/tools/near-api#use-a-global-contract)以获取完整的代码示例。 + diff --git a/zh/smart-contracts/tutorials/zero-to-hero/fts.mdx b/zh/smart-contracts/tutorials/zero-to-hero/fts.mdx new file mode 100644 index 00000000000..8f696a8c8f2 --- /dev/null +++ b/zh/smart-contracts/tutorials/zero-to-hero/fts.mdx @@ -0,0 +1,47 @@ +--- +title: 同质化代币从零到英雄 +sidebarTitle: 从头构建 FT 合约 +description: "掌握 NEAR 同质化代币,从预部署合约到构建功能完整的 FT 智能合约。" +--- + +在这个_从零到英雄_系列中,您将找到一套涵盖同质化代币(FT)智能合约各个方面的教程。您将从与预部署合约交互开始,最终构建出支持所有标准扩展的功能完整的 FT 智能合约。 + +--- + +## 先决条件 + +要成功完成这些教程,您需要: + +- [Rust](/smart-contracts/quickstart#prerequisites) +- [NEAR 钱包](https://testnet.mynearwallet.com) +- [NEAR-CLI](/tools/cli#installation) +- [cargo-near](https://github.com/near/cargo-near) + + +**Rust 新手?** +如果您是 Rust 新手并想深入了解智能合约开发,我们的[快速入门指南](https://docs.near.org/smart-contracts/quickstart)是一个很好的起点。 + + +--- + +## 概述 + +以下是将带您从**_零_**到**_英雄_**的步骤!💪 + +| 步骤 | 名称 | 描述 | 在 NearPlay 中试用 | +| ---- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| 1 | [预部署合约](https://near-examples.github.io/ft-tutorial/predeployed-contract) | 无需编写、创建或部署智能合约即可接收 FT。 | - | +| 2 | [合约架构](https://near-examples.github.io/ft-tutorial/skeleton) | 了解 FT 智能合约的基本架构并编译代码。 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/9fc2893b-3189-474f-aa56-6153f30a3c8d) | +| 3 | [定义代币](https://near-examples.github.io/ft-tutorial/defining-a-token) | 充实什么是 FT 以及如何自定义您自己的 FT。 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/2da829b9-b042-4a74-b39c-6258464a620a) | +| 4 | [流通供应](https://near-examples.github.io/ft-tutorial/circulating-supply) | 了解如何创建初始供应并让代币显示在您的钱包中。 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/bcb2b84a-d97e-4e00-a095-0f8f11d6ec1e) | +| 5 | [注册账户](https://near-examples.github.io/ft-tutorial/registering-accounts) | 探索如何实现和理解存储管理标准,以避免恶意用户耗尽您的资金。 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/a561a30e-b7d4-41fa-b1cb-3663c369b9b9) | +| 6 | [转移 FT](https://near-examples.github.io/ft-tutorial/transfers) | 了解如何转移 FT,并发现核心标准带来的一些真正力量 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/dcdae678-db6a-45e0-9de2-c1294aa05bfd) | +| 7 | [市场](https://near-examples.github.io/ft-tutorial/marketplace) | 了解常见市场在 NEAR 上的运作方式,并深入了解允许使用同质化代币购买和出售 NFT 的一些代码。 | [![Open in NearPlay](https://img.shields.io/badge/Open%20in%20NearPlay-14b8a6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTE4IDcgNCA0LTQgNE00IDEybDQgNC00LTQgNC00Ii8+PC9zdmc+&logoColor=white)](https://nearplay.app/embed/e313c93f-4752-4b68-808d-55ad0a98476a) | + +--- + +## 下一步 + +准备好了吗?跳转到[预部署合约](https://near-examples.github.io/ft-tutorial/predeployed-contract)教程,开始您的学习之旅! + +如果您已经了解同质化代币和智能合约,请随意跳过并直接跳到您感兴趣的教程。这些教程设计为可以从任何给定点开始! diff --git a/zh/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx b/zh/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx new file mode 100644 index 00000000000..f5dcf86d631 --- /dev/null +++ b/zh/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx @@ -0,0 +1,50 @@ +--- +title: NFT 从零到英雄 JavaScript 版 +sidebarTitle: 从头构建 NFT 合约(JS) +description: "通过这个从零到英雄系列,学习从铸造到构建功能完整的智能合约的 NFT 开发。" +--- + +> 在这个_从零到英雄_系列中,您将找到一套涵盖非同质化代币(NFT)智能合约各个方面的教程。 +> 您将从使用预部署合约铸造 NFT 开始,最终构建出支持所有扩展的功能完整的 NFT 智能合约。 + +## 先决条件 + +要成功完成这些教程,您需要: + +- [Node.js](/smart-contracts/quickstart?code-tabs=js) +- [NEAR 钱包](https://testnet.mynearwallet.com/create) +- [NEAR-CLI](/tools/cli#installation) + +--- + +## 概述 + +以下是将带您从**_零_**到**_英雄_**的步骤!💪 + +| 步骤 | 名称 | 描述 | +|------|------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| 1 | [预部署合约](https://near-examples.github.io/nft-tutorial-js/predeployed-contract) | 无需编写、创建或部署智能合约即可铸造 NFT。 | +| 2 | [合约架构](https://near-examples.github.io/nft-tutorial-js/skeleton) | 了解 NFT 智能合约的基本架构并编译代码。 | +| 3 | [铸造](https://near-examples.github.io/nft-tutorial-js/minting) | 充实骨架,使智能合约能够铸造非同质化代币。 | +| 4 | [升级合约](https://near-examples.github.io/nft-tutorial-js/upgrade-contract) | 了解升级现有智能合约的过程。 | +| 5 | [枚举](https://near-examples.github.io/nft-tutorial-js/enumeration) | 探索可用于返回智能合约状态的枚举方法。 | +| 6 | [核心](https://near-examples.github.io/nft-tutorial-js/core) | 使用允许代币转移的核心标准扩展 NFT 合约 | +| 7 | [批准](https://near-examples.github.io/nft-tutorial-js/approvals) | 扩展合约,允许其他账户代表您转移 NFT。 | +| 8 | [版税](https://near-examples.github.io/nft-tutorial-js/royalty) | 添加 NFT 版税,允许向代币创建者支付设定的百分比。 | +| 9 | [事件](https://near-examples.github.io/nft-tutorial-js/events) | 在本教程中,您将探索事件扩展,允许合约对某些事件做出反应。 | +| 10 | [市场](https://near-examples.github.io/nft-tutorial-js/marketplace) | 了解常见市场在 NEAR 上的运作方式,并深入了解允许购买和出售 NFT 的一些代码 | + +--- + +## 下一步 + +准备好了吗?跳转到[预部署合约](https://near-examples.github.io/nft-tutorial-js/predeployed-contract)教程,开始您的学习之旅! + +如果您已经了解非同质化代币和智能合约,请随意跳过并直接跳到您感兴趣的教程。这些教程设计为可以从任何给定点开始! + + +**有问题?** + +👉 加入我们的 [Discord](https://near.chat/) 并在 `#development` 频道告诉我们。👈 + + diff --git a/zh/smart-contracts/tutorials/zero-to-hero/nfts.mdx b/zh/smart-contracts/tutorials/zero-to-hero/nfts.mdx new file mode 100644 index 00000000000..0f74d5f13e8 --- /dev/null +++ b/zh/smart-contracts/tutorials/zero-to-hero/nfts.mdx @@ -0,0 +1,51 @@ +--- +title: NFT 从零到英雄 +sidebarTitle: 从头构建 NFT 合约 +description: "通过这个从零到英雄系列,逐步学习如何铸造 NFT 并构建完整的 NFT 合约。" +--- + +在这个_从零到英雄_系列中,您将找到一套涵盖非同质化代币(NFT)智能合约各个方面的教程。 +您将从使用预部署合约铸造 NFT 开始,最终构建出支持所有扩展的功能完整的 NFT 智能合约。 + +--- + +## 先决条件 + +要成功完成这些教程,您需要: + +- [Rust](https://www.rust-lang.org/tools/install) +- [Testnet 钱包](https://testnet.mynearwallet.com/create) +- [NEAR-CLI](/tools/cli#installation) +- [cargo-near](https://github.com/near/cargo-near) + + +**Rust 新手?** +如果您是 Rust 新手并想深入了解智能合约开发,我们的[快速入门指南](/smart-contracts/quickstart)是一个很好的起点 + + +--- + +## 概述 + +以下是将带您从**_零_**到**_英雄_**的步骤!💪 + +| 步骤 | 名称 | 描述 | +|------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| 1 | [预部署合约](https://near-examples.github.io/nft-tutorial/predeployed-contract) | 无需编写、创建或部署智能合约即可铸造 NFT。 | +| 2 | [合约架构](https://near-examples.github.io/nft-tutorial/skeleton) | 了解 NFT 智能合约的基本架构并编译代码。 | +| 3 | [铸造](https://near-examples.github.io/nft-tutorial/minting) | 充实骨架,使智能合约能够铸造非同质化代币。 | +| 4 | [升级合约](https://near-examples.github.io/nft-tutorial/upgrade-contract) | 了解升级现有智能合约的过程。 | +| 5 | [枚举](https://near-examples.github.io/nft-tutorial/enumeration) | 探索可用于返回智能合约状态的枚举方法。 | +| 6 | [核心](https://near-examples.github.io/nft-tutorial/core) | 使用允许代币转移的核心标准扩展 NFT 合约。 | +| 7 | [事件](https://near-examples.github.io/nft-tutorial/events) | 事件扩展,允许合约对某些事件做出反应。 | +| 8 | [批准](https://near-examples.github.io/nft-tutorial/approvals) | 扩展合约,允许其他账户代表您转移 NFT。 | +| 9 | [版税](https://near-examples.github.io/nft-tutorial/royalty) | 添加 NFT 版税,允许向代币创建者支付设定的百分比。 | +| 10 | [市场](https://near-examples.github.io/nft-tutorial/marketplace) | 了解常见市场在 NEAR 上的运作方式,并深入了解允许购买和出售 NFT 的一些代码。 | + +--- + +## 下一步 + +准备好了吗?跳转到[预部署合约](https://near-examples.github.io/nft-tutorial/predeployed-contract)教程,开始您的学习之旅! + +如果您已经了解非同质化代币和智能合约,请随意跳过并直接跳到您感兴趣的教程。这些教程设计为可以从任何给定点开始! diff --git a/zh/smart-contracts/what-is.mdx b/zh/smart-contracts/what-is.mdx new file mode 100644 index 00000000000..1508730f451 --- /dev/null +++ b/zh/smart-contracts/what-is.mdx @@ -0,0 +1,98 @@ +--- +title: 什么是智能合约? +sidebarTitle: 简介 +description: "了解可以存在于我们账户中的应用程序。" +--- + +智能合约是存在于 NEAR 账户中的**可执行代码**片段。它们可以**存储数据**、以账户名义**执行交易**,并**公开方法**供其他账户与之交互。 + +![img](/assets/docs/welcome-pages/4.smart-contracts.webp) + +开发者可以选择使用 Javascript 或 Rust 在 NEAR 上编写智能合约。无论选择哪种语言,合约都将被编译为 WebAssembly,随后可以在 NEAR 平台上部署和执行。 + + + +如果您不了解智能合约区块链的工作原理,不必担心。作为开发者,只需理解 NEAR 智能合约的以下特点: +1. 计算资源是**有限的**。 +2. 与其他合约的交互是**异步**的。 +3. 涉及**真实货币**,因此安全性必须是重中之重。 + + + +--- + +## 合约存在于何处? +智能合约部署在 [**NEAR 账户**](../protocol/accounts-contracts/account-model) 中。任何 NEAR 账户都可以持有合约,但需要为**合约代码**和**存储的数据**付费。 + +合约部署到账户后,任何人都可以与之交互。由于底层网络架构,执行合约代码既**快速**(平均 1.4 秒确定性)又**低成本**。此外,**只读**操作对所有人都是**免费的**。 + + +存储 100kb 需要 1Ⓝ,因此部署合约通常只需几个 `$NEAR`。 + + +--- + +## 它们能做什么? + +智能合约对账户拥有**完全控制权**,因此可以**代表账户执行任何操作**。例如,合约可以: +- 转移 `$NEAR` 代币 +- 调用其他合约的方法 +- 创建新账户并在其上部署合约 +- 更新自身代码 + +此外,智能合约可以在账户存储中存储数据。这使得合约能够创建几乎任何类型的应用程序,从简单游戏到复杂金融系统。 + + +**合约无法做到的事情** +- 智能合约**无法访问互联网**,因此无法发起 HTTP 请求或访问外部数据 +- 智能合约**无法自动执行**,需要由外部账户调用 + + +--- + +## 它们有什么用途? +智能合约适合创建**去中心化应用程序**。一些典型示例包括: +- [去中心化自治组织](https://nearcatalog.xyz/?cat=dao),用户在其中创建并投票提案 +- [市场](https://nearcatalog.xyz/?cat=marketplaces),用户在其中创建和交易数字艺术品 +- [去中心化交易所](https://nearcatalog.xyz/?cat=exchanges),用户可以交换不同货币 +- [以及更多...](https://nearcatalog.xyz/) + +例如,您可以轻松创建一个接受 `$NEAR` 的众筹合约。如果在规定时间内达到目标,创建者可以领取资金;否则,支持者将获得退款。 + +--- + +## 开发流程 + +与任何软件一样,智能合约也有开发流程——从创建开始,到监控结束,我们的文档涵盖了所有内容。 + +![img](/assets/docs/welcome-pages/contract-lifecycle.png) + "build/building-smart-contracts/testing/introduction", +开发流程可以概括如下: +- [**脚手架**](./quickstart):最简单的项目创建方式是从模板开始。 +- [**构建**](./anatomy/anatomy):使用 Rust 或 Javascript 编写合约。 +- [**测试**](./testing/introduction):我们的沙盒环境支持在真实环境中模拟单个或多个合约的交互。 +- [**部署**](./release/deploy):确认合约安全后,开发者可以将合约部署到他们的账户。 +- [**使用**](https://mynearwallet.com):任何用户都可以通过 NEAR 钱包与合约交互。 +- [**监控**](../data-infrastructure/what-is):可以通过简单的 API 监控合约活动。 + +#### 支持的语言 +在整个开发周期中,开发者可以选择 [JavaScript](https://www.learn-js.org/) 和 [Rust](https://www.rust-lang.org/),在每个开发阶段使用自己喜欢的语言。 + + + +从理论上讲,任何可以编译为 Wasm 的语言都可以用于开发 NEAR 智能合约。但为了提供友好的用户体验,我们需要提供一个封装底层运行时 API 并提供其他高级功能的库。 + +我们设想在未来,将支持更多语言,并且这种支持将通过更广泛社区的努力来实现,而不仅仅依赖 NEAR 本身。 + + + +--- + +## 合约原语 +FT、NFT 和 DAO 等合约原语是基础构建块,可以组合起来创造出色的用户体验,例如奖励代币、决策工具和市场。 + + + +查看我们的[原语](../primitives/what-is)部分以了解更多信息 + + diff --git a/zh/tools/clear-state.mdx b/zh/tools/clear-state.mdx new file mode 100644 index 00000000000..af93dc659bd --- /dev/null +++ b/zh/tools/clear-state.mdx @@ -0,0 +1,103 @@ +--- +title: 状态清理工具 +icon: trash-2 +description: "清理合约的状态数据。" +--- + +import { Github } from '/snippets/github.jsx' + +这个简单的命令行工具允许您在不删除 NEAR 账户的情况下清理其状态数据。 + +## 工作原理 + +这个 JavaScript CLI 工具会部署一个 [`state-cleanup.wasm`](https://github.com/near-examples/near-clear-state/blob/main/contractWasm/state_cleanup.wasm) 合约来替换当前合约,然后使用新合约清理账户的状态数据,以便您可以轻松地重新部署新合约或以任何其他方式使用该账户。 + +以下是合约主要代码的简短片段: + + + + +查看 GitHub 仓库,了解更多关于 [State Cleanup tool](https://github.com/near-examples/near-clear-state) 的信息。 + + +## 使用方法 + +### 环境要求 + +您需要安装 [NEAR CLI](/tools/cli)。可通过以下命令安装: + +```bash +npm install -g near-cli-rs@latest +``` + +### 清理账户状态 + +请按照以下步骤清理您的账户状态。 + + + + 这将在您的本地计算机上存储一个完全访问密钥。请选择您希望清理状态的账户。 + + ```bash + near login + ``` + + + 请务必选择 `Store the access key in my legacy keychain (compatible with the old near CLI)`,以便将访问密钥存储在旧版密钥链中。 + + + + ```bash + git clone https://github.com/near-examples/near-clear-state.git + ``` + + + ```bash + cd near-clear-state && npm i + ``` + + + ```bash + npx near-clear-state clear-state --account + ``` + + + 如果您想清理 `mainnet` 账户的状态,请使用 `--network` 选项: + + ```bash + npx near-clear-state clear-state --account --network mainnet + ``` + + + + 查看账户中已被清除的所有状态键: + + ```bash + near view-state + ``` + + + +## 故障排除 + +如果您的合约状态数据量较大,根据所使用的 RPC 节点,您可能会收到以下错误: + +``` +State of contract example.near is too large to be viewed. +``` + +这是一个 RPC 问题,因为该 RPC 节点对合约状态视图有大小限制。 + +您可以通过 JSON RPC API 调用 `view-state` 时选择其他 RPC 提供商来避免此错误。您可以在 [RPC 提供商列表](/api/rpc/providers) 中找到不同的提供商。 + + + +例如,您可以将 [`commands/clearState.js`](https://github.com/near-examples/near-clear-state/blob/main/commands/clearState.js) 中的默认 RPC 节点替换为其他 RPC 服务器: + +```js +config = { + networkId: netId, + keyStore, + nodeUrl: "https://endpoints.omniatech.io/v1/near/" + netId + "/public", + ... +``` diff --git a/zh/tools/cli.mdx b/zh/tools/cli.mdx new file mode 100644 index 00000000000..0d5ac523a64 --- /dev/null +++ b/zh/tools/cli.mdx @@ -0,0 +1,506 @@ +--- +title: NEAR CLI +icon: terminal +description: "通过终端与 NEAR 进行交互。" +--- + +NEAR [命令行界面](https://github.com/near/near-cli-rs)(CLI)是一款可让您直接在 Shell 中与 NEAR 网络交互的工具。通过 NEAR CLI,您可以创建和管理账户、发送 NEAR、FT 和 NFT 等代币、部署智能合约、调用合约上的函数以及管理访问密钥。 + +## 安装 + + + + ```bash + npm install -g near-cli-rs@latest + ``` + + + ```bash + cargo install near-cli-rs + ``` + + + ```bash + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + ``` + + + ```bash + irm https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.ps1 | iex + ``` + + + +## 配置文件 + +访问密钥目录及可用连接网络由配置文件(`near-cli/config.toml`)定义,该文件根据操作系统存储在以下位置: + +- macOS:`$HOME/Library/Application Support`(例如 `/Users/Alice/Library/Application Support`) +- Linux:`$XDG_CONFIG_HOME` 或 `$HOME/.config`(例如 `/home/alice/.config`) +- Windows:`{FOLDERID_RoamingAppData}`(例如 `C:\Users\Alice\AppData\Roaming`) + +您可以在[此处](https://github.com/near/near-cli-rs/blob/main/docs/GUIDE.en.md#config---manage-connections-in-a-configuration-file)了解更多关于配置文件使用的信息。 + + +您可以通过修改 `near-cli` 设置中的 `rpc_url` 参数来配置自定义 [RPC 服务器](/api/rpc/providers): + +```bash +near config edit-connection testnet --key rpc_url --value https://archival-rpc.testnet.near.org/ +``` + + +## 交互模式 + +只需在终端中运行以下命令即可使用 `near-cli`: + +```bash +near +``` + +您将看到如下界面。使用方向键并按下 `enter`,或直接输入可用选项之一进行选择。 + +![NEAR CLI interactive mode](/assets/docs/tools/near-cli-rs.png) + + +我们仅提供最常用命令的示例。这些命令可能有两个版本——**完整**版本和**简短**版本。如果您想探索 `near-cli` 提供的所有选项,请使用上述交互模式。 + + +## 账户 + +此选项允许您管理、控制和检索账户信息。 + +### 摘要 + +`view-account-summary` - 查看账户属性。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + near account view-account-summary $ACCOUNT_ID network-config testnet now + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + near state $ACCOUNT_ID --networkId testnet + ``` + + + +### 导入 + +`import-account` - 导入已有账户(即"登录")。 + + + + ```bash + near account import-account using-web-wallet network-config testnet + ``` + + + ```bash + near login --networkId testnet + ``` + + + +### 导出 + +`export-account` - 导出已有账户。 + +```bash +export ACCOUNT_ID=bob.testnet +near account export-account $ACCOUNT_ID using-web-wallet network-config testnet +``` + +### 创建 + +`create-account` - 创建新账户。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + near account create-account sponsor-by-faucet-service $ACCOUNT_ID autogenerate-new-keypair save-to-keychain network-config testnet create + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + near create-account $ACCOUNT_ID --useFaucet --networkId testnet + ``` + + + +### 删除 + +`delete-account` - 删除账户。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + export BENEFICIARY_ID=alice.testnet + + near account delete-account $ACCOUNT_ID beneficiary $BENEFICIARY_ID network-config testnet sign-with-keychain send + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + export BENEFICIARY_ID=alice.testnet + + near delete-account $ACCOUNT_ID $BENEFICIARY_ID --networkId testnet + ``` + + + +## 密钥 + +显示、添加和删除账户密钥。 + +### 列出密钥 + +`list-keys` - 查看账户的密钥列表。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + near account list-keys $ACCOUNT_ID network-config testnet now + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + near list-keys $ACCOUNT_ID --networkId testnet + ``` + + + +### 添加密钥 + +`add-key` - 向账户添加访问密钥。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + near account add-key $ACCOUNT_ID grant-full-access use-manually-provided-public-key ed25519:CXqAs8c8kZz81josLw82RQsnZXk8CAdUo7jAuN7uSht2 network-config testnet sign-with-keychain send + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + near add-key $ACCOUNT_ID ed25519:CXqAs8c8kZz81josLw82RQsnZXk8CAdUo7jAuN7uSht2 --networkId testnet + ``` + + + +### 删除密钥 + +`delete-keys` - 从账户删除访问密钥。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + near account delete-keys $ACCOUNT_ID public-keys ed25519:HdkFZFEPoWfgrrLK3R4t5dWtNoLC8WymBzhCXoP3zrjh network-config testnet sign-with-keychain send + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + near delete-key $ACCOUNT_ID ed25519:HdkFZFEPoWfgrrLK3R4t5dWtNoLC8WymBzhCXoP3zrjh --networkId testnet + ``` + + + +## 代币 + +管理您的代币资产,包括 NEAR、FT 和 NFT。 + +### 发送 NEAR + +`send-near` - 以 NEAR 或 yoctoNEAR 为单位向指定接收方转账 NEAR。 + + + + ```bash + export ACCOUNT_ID=bob.testnet + export RECEIVER_ID=alice.testnet + near tokens $ACCOUNT_ID send-near $RECEIVER_ID '0.5 NEAR' network-config testnet sign-with-keychain send + ``` + + + ```bash + export ACCOUNT_ID=bob.testnet + export RECEIVER_ID=alice.testnet + + near send-near $ACCOUNT_ID $RECEIVER_ID 0.5 --networkId testnet + ``` + + + +### 发送 FT + +`send-ft` - 向指定用户转账同质化代币。 + +```bash +export ACCOUNT_ID=bob.testnet +export RECEIVER_ID=alice.testnet +export FT_CONTRACT_ID=0c97251cd1f630c444dbusdt.testnet + +near tokens $ACCOUNT_ID send-ft $FT_CONTRACT_ID $RECEIVER_ID amount-ft '1 USDT' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' network-config testnet sign-with-keychain send +``` + +### 发送 NFT + +`send-nft` - 在账户之间转账 NFT。 + +```bash +export ACCOUNT_ID=bob.testnet +export RECEIVER_ID=alice.testnet +export NFT_CONTRACT_ID=nft.examples.testnet + +near tokens $ACCOUNT_ID send-nft $NFT_CONTRACT_ID $RECEIVER_ID 1 --prepaid-gas '100.0 Tgas' --attached-deposit '1 yoctoNEAR' network-config testnet sign-with-keychain send +``` + +### 查看 NEAR 余额 + +`view-near-balance` - 查看 NEAR 代币余额。 + +```bash +export ACCOUNT_ID=bob.testnet +near tokens $ACCOUNT_ID view-near-balance network-config testnet now +``` + +### 查看 FT 余额 + +`view-ft-balance` - 查看同质化代币余额。 + +```bash +export ACCOUNT_ID=bob.testnet +export FT_CONTRACT_ID=0c97251cd1f630c444dbusdt.testnet +near tokens $ACCOUNT_ID view-ft-balance $FT_CONTRACT_ID network-config testnet now +``` + +### 查看 NFT 余额 + +`view-nft-assets` - 查看 NFT 代币余额。 + +```bash +export ACCOUNT_ID=bob.testnet +export NFT_CONTRACT_ID=nft.examples.testnet +near tokens $ACCOUNT_ID view-nft-assets $NFT_CONTRACT_ID network-config testnet now +``` + +## 合约 + +此选项允许您管理智能合约并与之交互。 + +### 调用 + +`call-function` - 执行函数(合约方法)。 + + + + ```bash + # View method + export CONTRACT_ID=nft.examples.testnet + near contract call-function as-read-only $CONTRACT_ID nft_tokens json-args '{"from_index": "0", "limit": 2}' network-config testnet now + + # Call method + export ACCOUNT_ID=bob.testnet + near contract call-function as-transaction $CONTRACT_ID nft_mint json-args '{"metadata": {"copies": 1, "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "title": "GO TEAM"}, "receiver_id": "bob.testnet", "token_id": "5895"}' prepaid-gas '100.0 Tgas' attached-deposit '0.1 NEAR' sign-as $ACCOUNT_ID network-config testnet sign-with-keychain send + ``` + + + ```bash + # View method + export CONTRACT_ID=nft.examples.testnet + near view $CONTRACT_ID nft_tokens '{"from_index": "0", "limit": 2}' --networkId testnet + + # Call method + export ACCOUNT_ID=bob.testnet + near call $CONTRACT_ID nft_mint '{"metadata": {"copies": 1, "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "title": "GO TEAM"}, "receiver_id": "bob.testnet", "token_id": "5896"}' --deposit 0.1 --useAccount $ACCOUNT_ID --networkId testnet + ``` + + + +### 部署 + +`deploy` - 添加新的合约代码。 + + + + ```bash + export CONTRACT_ID=contract.testnet + near contract deploy $CONTRACT_ID use-file ../target/near/contract.wasm without-init-call network-config testnet sign-with-keychain send + ``` + + + ```bash + export CONTRACT_ID=contract.testnet + near deploy $CONTRACT_ID ../target/near/contract.wasm --networkId testnet + ``` + + + +### 检查 + +`inspect` - 获取可用函数名称列表。 + + + + ```bash + export CONTRACT_ID=nft.examples.testnet + near contract view-storage $CONTRACT_ID all as-text network-config testnet now + ``` + + + ```bash + export CONTRACT_ID=nft.examples.testnet + near storage $CONTRACT_ID --finality final --utf8 --networkId testnet + ``` + + + +## 交易 + +操作交易。 + +### 查看状态 + +`view-status` - 查看交易状态。 + + + + ```bash + near transaction view-status BFrVVtjqD2p1zYX1UCvn4nJpy7zPHpY5cTgQaKCZjBvw network-config testnet + ``` + + + ```bash + near tx-status BFrVVtjqD2p1zYX1UCvn4nJpy7zPHpY5cTgQaKCZjBvw --networkId testnet + ``` + + + +## 配置 + +管理 `near-cli` 的 `config.toml` 文件中的连接参数。 + +此选项允许您更改或修改 CLI 的网络连接设置。 + +### 显示连接 + +`show-connections` - 显示网络连接列表。 + +```bash +near config show-connections +``` + +### 编辑连接 + +`edit-connection` - 编辑网络连接。 + +```bash +near config edit-connection testnet --key rpc_url --value https://test.rpc.fastnear.com +``` + + +我们仅提供最常用命令的示例。如果您想探索 `near-cli` 提供的所有选项,请使用[交互模式](#interactive-mode)。 + + +## 验证者 + +您可以使用以下命令与区块链交互并查看验证者统计信息。共有三种报告用于监控验证者状态: + +- [提案](#proposals) +- [当前验证者](#current-validators) +- [下一轮验证者](#next-validators) + + +要使用这些命令,您**必须**安装 CLI [验证者扩展](#validator-extension)。 + + +### 验证者扩展 + +如果您想通过命令行与 [NEAR 验证者](https://pages.near.org/papers/economics-in-sharded-blockchain/#validators) 进行交互,可以安装 [NEAR Validator CLI Extension](https://github.com/near-cli-rs/near-validator-cli-rs): + + + + ```bash + npm install -g near-validator + ``` + + + ```bash + cargo install near-validator + ``` + + + ```bash + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near-cli-rs/near-validator-cli-rs/releases/latest/download/near-validator-installer.sh | sh + ``` + + + ```bash + irm https://github.com/near-cli-rs/near-validator-cli-rs/releases/latest/download/near-validator-installer.ps1 | iex + ``` + + + +### 提案 + +验证者的提案表示其希望加入验证者集合。提案被接受的前提是必须满足最低席位价格要求。 + +```bash +near-validator proposals network-config mainnet +``` + +### 当前验证者 + +显示当前纪元中活跃验证者的列表,包括已生产区块数、预期区块数及在线率。用于监控验证者是否出现异常。 + +```bash +near-validator validators network-config mainnet now +``` + +### 下一轮验证者 + +显示提案在上一个纪元被接受、将在下一个纪元加入验证者集合的验证者。 + +```bash +near-validator validators network-config mainnet next +``` + +### 质押 + +对于验证者,还提供了无需部署质押池智能合约即可质押 NEAR 代币的选项。 + +#### 查看验证者质押 + +查看最新区块上的验证者质押额: + +```bash +near-validator staking view-stake examples.testnet network-config testnet now +``` + +#### 直接质押(无需质押池) + +质押指定金额: + +```bash +near-validator staking stake-proposal examples.testnet ed25519:AiEo5xepXjY7ChihZJ6AsfoDAaUowhPgvQp997qnFuRP '1500 NEAR' network-config testnet sign-with-keychain send +``` + +#### 直接取消质押(无需质押池) + +取消质押: + +```bash +near-validator staking unstake-proposal examples.testnet ed25519:AiEo5xepXjY7ChihZJ6AsfoDAaUowhPgvQp997qnFuRP network-config testnet sign-with-keychain send +``` diff --git a/zh/tools/contracts-list.mdx b/zh/tools/contracts-list.mdx new file mode 100644 index 00000000000..d9b44635976 --- /dev/null +++ b/zh/tools/contracts-list.mdx @@ -0,0 +1,57 @@ +--- +title: 参考合约 +icon: list +description: "探索部署在各项目中的 NEAR 合约。" +--- + +部署在 NEAR 上的各类项目合约列表。此列表并不详尽,仅作为开发者和用户的参考资料。 + + +**了解更多:** +[NEAR Catalog](https://nearcatalog.xyz) - NEAR 项目目录 +[NEAR Audit Database](https://github.com/NEARBuilders/audits) - NEAR 生态系统公开审计列表 + + +## 合约列表 +| 合约 | 项目 | 标签 | 描述 | +|-------------------------------------------------------------------------------------------------|----------------------------------------------------|----------------------------------|-------------------------------------------------------------------------------------------------------| +| [Burrow.finance (public archive)](https://github.com/NearDefi/burrowland) | **[Rhea finance](https://rhea.finance/)** | DeFi | 借贷平台 | +| [ref-exchange](https://github.com/ref-finance/ref-contracts/tree/main/ref-exchange) | **[Rhea finance](https://rhea.finance/)** | DeFi | DEX 合约 | +| [ref-farming](https://github.com/ref-finance/ref-contracts/tree/main/ref-farming) | **[Rhea finance](https://rhea.finance/)** | DeFi | DEX LP 流动性挖矿 | +| [Meta Token](https://github.com/Narwallets/meta-pool/tree/master/meta-token) | **[Metapool](https://www.metapool.app/)** | FT | FT 合约 | +| [metapool](https://github.com/Narwallets/meta-pool/tree/master/metapool) | **[Metapool](https://www.metapool.app/)** | staking | 流动性质押 | +| [donation](https://github.com/PotLock/core/blob/main/contracts/donation) | **[PotLock](https://www.potlock.org/)** | FT, donation | 向任意合约捐赠 FT | +| [pot](https://github.com/PotLock/core/blob/main/contracts/pot) | **[PotLock](https://www.potlock.org/)** | donation | QF 融资轮合约 | +| [pot_factory](https://github.com/PotLock/core/blob/main/contracts/pot_factory) | **[PotLock](https://www.potlock.org/)** | factory, donation | QF 融资轮工厂合约 | +| [registry](https://github.com/PotLock/core/blob/main/contracts/registry) | **[PotLock](https://www.potlock.org/)** | donation, misc | QF 融资轮注册表 | +| [sybil](https://github.com/PotLock/core/blob/main/contracts/sybil) | **[PotLock](https://www.potlock.org/)** | misc | 女巫攻击防护提供商注册表 | +| [ShardDog-NFT-Protocol](https://github.com/joe-rlo/ShardDog-NFT-Protocol) | **[ShardDog](https://shard.dog/)** | NFT | 管理多个 NFT 集合的 NFT 合约 | +| [NFT-Minting-withFT](https://github.com/joe-rlo/NFT-Minting-withFT) | **[ShardDog](https://shard.dog/)** | NFT, FT | 支持 FT 代币铸造 NFT,含版税、付款分配及预铸造功能。 | +| [contest-contract](https://github.com/joe-rlo/contest-contract) | **[ShardDog](https://shard.dog/)** | raffle | 加权抽奖系统。 | +| [core](https://github.com/near/intents/tree/main/core) | **[NEAR Intents](https://near-intents.org/)** | cross-chain, DeFi | 多链金融产品协议 | +| [contest-contract](https://github.com/NEAR-DevHub/race-of-sloths/tree/main/contest-contract) | **[Race of Sloths](https://race-of-sloths.com/)** | raffle | 加权抽奖合约 | +| [contract](https://github.com/NEAR-DevHub/race-of-sloths/tree/main/contract) | **[Race of Sloths](https://race-of-sloths.com/)** | misc | 包含连续记录的积分榜 | +| [contract-mvp](https://github.com/Templar-Protocol/contract-mvp) | **[Templar Protocol](https://www.templarfi.org/)** | DeFi | 无许可密码学借贷 | +| [index-fund-contract](https://github.com/Templar-Protocol/index-fund-contract) | **[Templar Protocol](https://www.templarfi.org/)** | DeFi | 指数基金合约 | +| [linear](https://github.com/linear-protocol/LiNEAR/tree/main/contracts/linear) | **[Linear](https://linearprotocol.org/)** | staking | 流动性质押 | +| [hodl-lockup](https://github.com/sweatco/hodl-lockup) | **[Sweat Economy](https://sweateconomy.com/)** | FT, lockup | FT 锁仓合约 | +| [sweat-jar](https://github.com/sweatco/sweat-jar) | **[Sweat Economy](https://sweateconomy.com/)** | FT, staking | FT 质押合约 | +| [sweat-claim](https://github.com/sweatco/sweat-claim/tree/main/contract) | **[Sweat Economy](https://sweateconomy.com/)** | FT | FT 管理合约 | +| [sweat-booster](https://github.com/sweatco/sweat-booster) | **[Sweat Economy](https://sweateconomy.com/)** | FT, NFT | 用于 FT 的 NFT 凭证 | +| [multisig](https://github.com/sweatco/multisig) | **[Sweat Economy](https://sweateconomy.com/)** | multisig | 多重签名合约 | +| [sweat-burn](https://github.com/sweatco/sweat-burn) | **[Sweat Economy](https://sweateconomy.com/)** | FT | FT 销毁合约 | +| [mb-factory-v2](https://github.com/Mintbase/mb-contracts/tree/main/mb-factory-v2) | **MintBase** | factory, NFT | NFT 工厂合约 | +| [mb-interop-market](https://github.com/Mintbase/mb-contracts/tree/main/mb-interop-market) | **MintBase** | NFT | NFT 市场合约 | +| [mb-nft-v2](https://github.com/Mintbase/mb-contracts/tree/main/mb-nft-v2) | **MintBase** | NFT | NFT 合约 | +| [neardevhub-contract](https://github.com/NEAR-DevHub/neardevhub-contract) | **[NEAR DevHub](https://neardevhub.org/)** | misc | 社区管理平台 | +| [lockup-factory](https://github.com/near/core-contracts/tree/master/lockup-factory) | **Core-Contracts** | factory, lockup | 部署锁仓合约的工厂合约 | +| [lockup](https://github.com/near/core-contracts/tree/master/lockup) | **Core-Contracts** | lockup | 在锁仓期内锁定并持有所有者代币 | +| [multisig-factory](https://github.com/near/core-contracts/tree/master/multisig-factory) | **Core-Contracts** | factory | 多重签名工厂合约 | +| [multisig](https://github.com/near/core-contracts/tree/master/multisig) | **Core-Contracts** | multisig | 多重签名合约 | +| [multisig2](https://github.com/near/core-contracts/tree/master/multisig2) | **Core-Contracts** | multisig | 多重签名合约 | +| [staking-pool-factory](https://github.com/near/core-contracts/tree/master/staking-pool-factory) | **Core-Contracts** | factory, staking-pool, validator | 质押池工厂合约 | +| [staking-pool](https://github.com/near/core-contracts/tree/master/staking-pool) | **Core-Contracts** | staking-pool, validator | 质押池合约 | +| [state-manipulation](https://github.com/near/core-contracts/tree/master/state-manipulation) | **Core-Contracts** | state-manipulation | 合约状态操作合约 | +| [voting](https://github.com/near/core-contracts/tree/master/voting) | **Core-Contracts** | validator | 验证者投票合约 | +| [w-near](https://github.com/near/core-contracts/tree/master/w-near) | **Core-Contracts** | FT | 封装原生 NEAR 的 FT 合约 | +| [whitelist](https://github.com/near/core-contracts/tree/master/whitelist) | **Core-Contracts** | whitelist, staking-pool | 质押池白名单合约 | diff --git a/zh/tools/near-api.mdx b/zh/tools/near-api.mdx new file mode 100644 index 00000000000..7bfdfd8d91f --- /dev/null +++ b/zh/tools/near-api.mdx @@ -0,0 +1,902 @@ +--- +title: API 库 +icon: code +description: "学习使用 JavaScript、Rust 和 Python 中的 API 与区块链进行交互。" +--- + +import { Github } from '/snippets/github.jsx'; + +我们提供一系列特定语言的库,使开发者能够在前端和后端应用程序中与 NEAR 区块链进行交互。通过这些不同的 API,您可以在 NEAR 区块链上执行多种操作,包括但不限于: + +1. 创建和管理 NEAR 账户 +2. 调用智能合约上的函数 +3. 转移代币,包括原生 NEAR、同质化代币和非同质化代币 +4. 对交易/元交易/消息进行签名并广播至网络 +5. 部署智能合约 + +--- + +## 可用 API + +我们提供 Javascript、Rust 和 Python 的 API。使用以下命令将其添加到您的项目: + + + + + ```bash + npm i near-api-js + ``` + + + + + ```bash + npm i near-kit + ``` + + + + + ```bash + cargo add near-api + ``` + + + + + ```shell + pip install py-near + ``` + + + + + +如果您正在构建 Web 应用并需要添加钱包登录功能,则需要使用 [钱包连接器](/web3-apps/tutorials/wallet-login)。 + + +--- + +## 账户 + +### 获取余额 + +获取账户的可用余额和质押余额(单位为 yoctoNEAR)。 + + + + + + + + + + + + + + + + ```python + from py_near.account import Account + + account = Account(account_id="example-account.testnet", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + account_balance = account.get_balance() + ``` + + + + +### 获取状态 + +获取账户的基本信息,例如其代码哈希和存储用量。 + + + + + + + + + + + + + + + + + + + + ```python + from py_near.account import Account + + account = Account(account_id="example-account.testnet", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + account_state = account.fetch_state() + ``` + + + + +### 创建命名账户 + +要创建如 `user.testnet` 这样的命名账户,您需要在 `near`(或 `testnet`)上调用 `create_account` 函数,并传入新账户 ID 及要添加为[完全访问密钥](/protocol/accounts-contracts/access-keys#full-access-keys)的公钥作为参数。 + + + + + + + + + + + + + + + + + + + 您也可以通过随机生成的助记词来创建账户。 + + + + + + + + + ```python + await account.function_call("testnet", "create_account", {"new_account_id": "example-account.testnet", "new_public_key": "ed25519:..."}, "30000000000000", 1 * NEAR) + ``` + + + + +### 创建子账户 + +NEAR 上的账户可以在其自身命名空间下创建子账户,这对于按用途组织账户非常有用——例如 `project.user.testnet`。 + + + + + + + + + + + + + + + + + + + + 使用主账户创建子账户并为其充值: + + ```python + from py_near.account import Account + from py_near.dapps.core import NEAR + + account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + res = account.create_account(account_id="sub.example-account.testnet", public_key="...", initial_balance=1 * NEAR) + ``` + + + + + +父账户对子账户**没有控制权**,子账户完全独立。 + + +### 删除账户 + +NEAR 上的账户可以删除自身,并将任何剩余余额转移至指定的受益人账户。 + + + + + + + + + + + + + + + + + + + + +删除账户**不会**影响其子账户——子账户将继续保持活跃状态。 + + + +**受益人仅接收 NEAR 代币** + +账户持有的同质化代币(FT)或非同质化代币(NFT)**不会**被自动转移。这些代币仍与该账户相关联,即使在账户删除后也是如此。请务必在删除前手动转移这些资产,否则将面临永久丢失的风险。一旦账户被删除,这些资产将被有效锁定,除非任何人(不一定是您)重新创建相同的账户。 + + + +**请确保受益人账户存在** + +如果受益人账户不存在,发送至该账户的所有 NEAR 代币将被销毁。请在继续操作前仔细核对账户 ID。 + + +--- + +## 交易 + +### 发送代币 + +账户可以向其他账户转移不同类型的代币,包括原生 NEAR 代币和 [NEP-141](https://github.com/near/NEPs/tree/master/neps/nep-0141.md) 同质化代币。 + + + + + + + + + + + + + + + + + + + + ```python + from py_near.account import Account + from py_near.dapps.core import NEAR + + account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + await account.send_money("receiver-account.testnet", 1 * NEAR) + ``` + + + + +### 调用函数 + +智能合约对外暴露其方法,调用会修改状态的函数需要 `Signer`/`KeyPair`。您可以选择性地在调用时附加 `NEAR` 存款。 + + + + + + + + + 使用 Typescript 时,您可以为 `callFunction` 的返回值指定类型。 + + + + + + + + + + + + + + + 使用 Typescript 时,您可以为 `Near.view` 和 `Near.call` 的返回值指定类型。 + + + + + + + + + + + + + + + + + + ```python + await account.function_call("usn.near", "ft_transfer", {"receiver_id": "bob.near", "amount": "1000000000000000000000000"}) + ``` + + + + +### 批量操作 + +您可以向**单个**接收方发送一批[操作](/protocol/transactions/transaction-anatomy#actions)。如果其中一个操作失败,整批操作都将被回滚。 + + + + + + + + + + + + + + + + + + + +### 同时发送多笔交易 + +实现真正意义上同时发送多笔交易的唯一方式是在同一账户上使用多个访问密钥。每个访问密钥维护自己的 nonce,从而允许使用不同密钥签名的交易并行处理: + + + + + + + + + + + + + + + + + + + + ```python + import asyncio + from py_near.account import Account + + account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + # Prepare the transactions + tx1 = account.function_call("guestbook.near-examples.testnet", "add_message", { "text": "Hello, world!" }) + tx2 = account.function_call("counter.near-examples.testnet", "increment", {}) + + # Send the transactions simultaneously + transactionsResults = await asyncio.gather(tx1, tx2) + ``` + + + + + +同时执行意味着无法保证顺序或成功。任何交易都可能独立失败。如果您的使用场景需要严格的顺序保证,则应坚持使用单个密钥按顺序发送交易。 + + +### 部署合约 + +在 NEAR 上,智能合约以 WASM 文件的形式部署。每个账户都有成为合约的潜力——您只需向其部署代码即可。 + + + + + + + + + + + + + + + 请注意,此处的 `signer` 需要是与构建 `Contract` 对象时使用的 `account_id` 相同账户的签名者。 + + + + + + + ```python + import asyncio + from py_near.account import Account + + account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") + await account.startup() + + with open("contract.wasm", "rb") as f: + contract_code = f.read() + await account.deploy_contract(contract_code) + ``` + + + + +### 部署全局合约 + +[全局合约](/smart-contracts/global-contracts)允许智能合约一次部署,由任意账户复用,而无需承担高额存储成本。 + +引用全局合约有两种方式: +- **[按账户](/smart-contracts/global-contracts#reference-by-account):** 合约代码与另一个账户绑定。 +- **[按哈希](/smart-contracts/global-contracts#reference-by-hash):** 通过合约不可变的代码哈希进行引用。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 创建 Account 实例后,您可以将普通合约作为全局合约进行部署。 + + + + + 要按账户部署全局合约,请使用带有 `as_account_id` 方法的 `deploy_global_contract_code` 函数。 + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_account_id(global_account_id) + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + [在 GitHub 上查看完整示例](https://github.com/near-examples/near-api-examples/blob/main/rust/examples/global_contract_accountid.rs) + + + + + 要按哈希部署全局合约,请使用带有 `as_hash` 方法的 `deploy_global_contract_code` 函数。 + + ```rust + let account_id: AccountId = "my-account.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_hash() + .with_signer(account_id, signer) + .send_to_testnet() + .await.unwrap(); + ``` + + [在 GitHub 上查看完整示例](https://github.com/near-examples/near-api-examples/blob/main/rust/examples/global_contract_hash.rs) + + + + + + + +### 使用全局合约 + +[全局合约](/smart-contracts/global-contracts)[部署](#deploy-a-global-contract)后,以下是从另一个账户引用和使用它的方法。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 要按账户引用全局合约,请调用 `use_global_account_id` 并传入合约最初部署所在的源 `accountId`。 + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let my_account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let my_signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(my_account_id) + .use_global_account_id(global_account_id) + .without_init_call() + .with_signer(my_signer) + .send_to_testnet() + .await.unwrap(); + ``` + + [在 GitHub 上查看完整示例](https://github.com/near-examples/near-api-examples/blob/main/rust/examples/global_contract_accountid.rs) + + + + + 要按哈希引用全局合约,请调用 `use_global_hash` 并传入原始合约的 `hash`。 + + ```rust + let global_hash: types::CryptoHash = "DxfRbrjT3QPmoANMDYTR6iXPGJr7xRUyDnQhcAWjcoFF".parse().unwrap(); + let account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(account_id) + .use_global_hash(global_hash) + .without_init_call() + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + [在 GitHub 上查看完整示例](https://github.com/near-examples/near-api-examples/blob/main/rust/examples/global_contract_hash.rs) + + + + + + + +--- + +## 视图函数 + +视图函数是智能合约上的只读方法,不会修改状态。您无需使用账户或签署交易即可调用它们。 + + + + + + + + 使用 Typescript 时,您可以为 `callFunction` 的返回值指定类型。 + + + + + + + + + 使用 Typescript 时,您可以为 `Near.view` 的返回值指定类型。 + + + + + + + + + + + ```python + view_call_result = await account.view_function("guestbook.near-examples.testnet", "total_messages", {}) + # If args are required, they can be passed in like this in the 3rd argument: + # { + # "from_index": "0", + # "limit": "10" + # } + print(view_call_result) + ``` + + + + +--- + +## 密钥 + +### 获取所有访问密钥 + + + + + + + + + + + + + + + ```python + keys = await account.get_access_key_list() + ``` + + + + +### 添加完全访问密钥 + +[完全访问密钥](/protocol/accounts-contracts/access-keys#full-access-keys)授予对账户的完整控制权。 + +拥有此密钥的任何人都可以转移资金、签署交易、与合约交互,甚至删除账户。 + + + + + + + + + + + + + + + + + + + + ```python + keys = await account.add_full_access_public_key("5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj") + ``` + + + + +### 添加函数调用密钥 + +[函数调用访问密钥](/protocol/accounts-contracts/access-keys#function-call-keys)专门用于对特定合约签署仅包含 [`functionCall` 操作](/protocol/transactions/transaction-anatomy#actions)的交易。 + +您可以通过以下方式进一步限制此密钥: +- 限制可调用的方法名称 +- 设置密钥可用于交易手续费的 `NEAR` 上限 + + + + + + + + + + + + + + + + + + + + ```python + await account.add_public_key( + "5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj", + "example-contract.testnet", # Contract this key is allowed to call + ["example_method"], # Methods this key is allowed to call (optional) + 0.25 * NEAR # Gas allowance key can use to call methods (optional) + ) + ``` + + + + + +出于安全原因,函数调用访问密钥**只能用于附加零 `NEAR` 代币的函数调用**。任何尝试附加存款的操作都将导致交易失败。 + + +### 删除访问密钥 + +NEAR 上的账户可以删除自己的密钥。 + + + + + + + + + + + + + + + + + + + + ```python + await account.delete_public_key("5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj") + ``` + + + + + +删除密钥时请格外谨慎。从账户中删除所有密钥将导致您永久失去对该账户的访问权限。 + + +--- + +## 验证消息签名 + +用户可以使用 `wallet-selector` 的 `signMessage` 方法对消息进行签名,该方法会返回一个签名。可使用以下代码对该签名进行验证: + + + + + + + + + + + + + + +--- + +## 其他资源 + + + + + - [文档](https://near.github.io/near-api-js) + - [Github](https://github.com/near/near-api-js) + - [完整示例](https://github.com/near-examples/near-api-examples/tree/main) + + + + + - [Github](https://github.com/r-near/near-kit/tree/main) + - [完整示例](https://github.com/near-examples/near-api-examples/tree/main/near-kit) + + + + + - [文档](https://docs.rs/near-api/latest/near_api/) + - [Github](https://github.com/near/near-api-rs) + - [完整示例](https://github.com/near-examples/near-api-examples/tree/main/rust) + + + + + - [Github](https://github.com/pvolnov/py-near) + + + diff --git a/zh/tools/near-connect.mdx b/zh/tools/near-connect.mdx new file mode 100644 index 00000000000..b4afdd9a566 --- /dev/null +++ b/zh/tools/near-connect.mdx @@ -0,0 +1,50 @@ +--- +title: 钱包连接器 +icon: "wallet" +description: "零依赖的轻量级钱包连接器" +--- + +**NEAR Connect** 是一个零依赖、稳健、安全且轻量的 NEAR 区块链钱包连接器。它在隔离的沙盒环境中执行钱包集成代码,而非将所有钱包代码打包到单一软件包中。 + +![Preview](https://github.com/user-attachments/assets/c4422057-38bb-4cd9-8bd0-568e29f46280) + + + + 通过在隔离的 iframe 中运行钱包脚本,降低供应链风险。 + + + 得益于在运行时加载钱包代码的共享清单,始终保持最新状态。 + + + 轻量且自包含,无任何外部依赖。 + + + 支持 HOT Wallet、Meteor Wallet、Nightly、WalletConnect 及其他 10 余种钱包。 + + + + +想将 NEAR Connect 集成到您的应用中?请查阅 [Web 登录指南](../web3-apps/tutorials/wallet-login) 获取分步说明。 + + +--- + +## 集成指南 + +请阅读我们的 [Web 登录指南](../web3-apps/tutorials/wallet-login),获取将 NEAR Connect 集成到您的应用中的分步演练,包括代码示例和最佳实践。 + + + + 支持可选消息签名的登录功能。 + + + 签名并发送交易。 + + + +--- + +## 其他资源 + +- [NEAR Web 登录指南](../web3-apps/concepts/web-login) +- [Github](https://github.com/azbang/near-connect) diff --git a/zh/tools/sdk.mdx b/zh/tools/sdk.mdx new file mode 100644 index 00000000000..297ea7e234e --- /dev/null +++ b/zh/tools/sdk.mdx @@ -0,0 +1,86 @@ +--- +title: SDK 库 +icon: package +description: "选择 SDK 开始构建合约。" +--- + +NEAR SDK 是一个用于开发智能合约的库。目前,NEAR SDK 有两个版本:一个适用于 Rust,另一个适用于 JavaScript。 + + +开始学习的最佳途径是我们的 [快速入门指南](/smart-contracts/quickstart)。 + + + + + Rust SDK 参考文档。 + + + JavaScript SDK 参考文档。 + + + +## NEAR 上的智能合约 + +以下是使用 NEAR SDK 以 Rust 和 JavaScript 编写的智能合约示例: + + + + ```js + @NearBindgen({}) + class HelloNear { + greeting: string = 'Hello'; + + @view({}) // This method is read-only and can be called for free + get_greeting(): string { + return this.greeting; + } + + @call({}) // This method changes the state, for which it costs gas + set_greeting({ greeting }: { greeting: string }): void { + near.log(`Saving greeting ${greeting}`); + this.greeting = greeting; + } + } + ``` + + + ```rust + #[near(contract_state)] + pub struct Contract { + greeting: String, + } + + impl Default for Contract { + fn default() -> Self { + Self { greeting: "Hello".to_string(), } + } + } + + #[near] + impl Contract { + pub fn get_greeting(&self) -> String { + self.greeting.clone() + } + + pub fn set_greeting(&mut self, greeting: String) { + self.greeting = greeting; + } + } + ``` + + + +## 准备好开始开发了吗? + +从我们的 [智能合约快速入门指南](/smart-contracts/quickstart) 开始,它将引导您了解构建智能合约的所有相关文档。 + +## 想查看示例? + +我们有一个专门的 [教程与示例](/smart-contracts/tutorials/basic-contracts) 章节,可帮助您了解各种使用场景及其实现方式。 + +## 参考文档 + +如果您需要查找特定的函数签名,或了解 SDK 的结构体和类,请访问各 SDK 专属页面: + +- [Rust SDK](https://docs.rs/near-sdk/latest/near_sdk/) +- [JavaScript SDK](https://near.github.io/near-sdk-js/) diff --git a/zh/web3-apps/backend/backend.mdx b/zh/web3-apps/backend/backend.mdx new file mode 100644 index 00000000000..08595c81fa1 --- /dev/null +++ b/zh/web3-apps/backend/backend.mdx @@ -0,0 +1,67 @@ +--- +title: 验证 NEAR 用户身份 +description: "了解如何通过创建质询、请求钱包签名并验证签名,在后端服务中验证 NEAR 用户的身份。" +--- + +import {Github} from '/snippets/github.jsx'; + +近期,NEAR 批准了一项新标准,该标准除其他功能外,还支持用户向后端服务进行身份验证。 + +其基本思路是:用户将使用其 NEAR 钱包对一个质询进行签名,后端则验证该签名。如果签名有效,则用户身份验证通过。 + +--- + +## 使用 NEAR 钱包进行后端身份验证 +验证用户身份是后端和 Web 应用程序的常见用例。这使得服务能够为用户提供个性化体验,并保护敏感数据。 + +为了验证用户身份,后端必须确认用户就是其所声称的人。为此,后端必须验证用户能够访问与其账户关联的全访问密钥。 + +为此,需要以下三个基本步骤: + +1. 为用户创建一个待签名的质询。 +2. 请求用户使用钱包对质询进行签名。 +3. 验证签名与用户相符。 + +### 1. 创建质询 +假设我们希望将用户登录到名为 `application-name` 的应用程序中。 + +我们首先需要创建一个质询,用户将使用其钱包对其进行签名。为此,建议使用密码学安全的随机数生成器来创建质询。 + +```js +import { randomBytes } from 'crypto' +const challenge = randomBytes(32) +const message = 'Login with NEAR' +``` + + +这里我们使用 [crypto.randomBytes](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback) 生成一个 32 字节的随机缓冲区。 + + +### 2. 请求用户对质询进行签名 +以下钱包支持签署质询所需的 `signMessage` 方法: +- Meteor Wallet +- Here Wallet +- Near Snap +- Nightly Wallet +- WELLDONE Wallet +- NearMobileWallet +- MyNearWallet +- Sender +- Intear Wallet + +用户需要签署的消息包含 4 个字段: +- 消息(Message):用户正在签署的消息内容。 +- 收件人(Recipient):消息的接收方。 +- 随机数(Nonce):用户正在签署的质询。 +- 回调 URL(Callback URL):钱包将携带签名调用的 URL。 + +```js +// 假设您已设置好钱包选择器 +const signature = wallet.signMessage({ message, recipient, nonce: challenge, callbackUrl: }) +``` + +### 3. 验证签名 +一旦用户完成签名,钱包将携带签名调用 `callbackUrl`。后端随后可以验证该签名。 + + diff --git a/zh/web3-apps/concepts/data-types.mdx b/zh/web3-apps/concepts/data-types.mdx new file mode 100644 index 00000000000..8f45bb505b4 --- /dev/null +++ b/zh/web3-apps/concepts/data-types.mdx @@ -0,0 +1,160 @@ +--- +title: 处理 NEAR 数据类型 +description: "了解如何在与 NEAR 交互时处理常见数据类型" +--- + +在调用合约方法或接收其返回结果时,您需要正确地对参数进行编码/解码。为此,了解合约如何编码时间戳、余额和 Gas 至关重要。 + + +**正在查找智能合约数据类型?** + +请查看[智能合约数据类型](../../smart-contracts/anatomy/types)章节,了解有关如何在智能合约中处理数据类型的更多信息。 + + + +--- + +## 时间 + +智能合约中的区块时间戳使用纳秒进行编码(即 19 位数字:`1655373910837593990`)。 + + + + +`Date.now()` 返回以毫秒为单位的时间戳(即 13 位数字:`1655373910837`)。请务必在毫秒和纳秒之间进行转换,以正确处理时间变量。 + + + + + +在 Rust 中,`std::time::SystemTime::now()` 返回一个 `SystemTime` 对象。您可以按如下方式将其转换为自 UNIX_EPOCH 以来的纳秒数: + +```rust +use std::time::{SystemTime, UNIX_EPOCH}; +let start = SystemTime::now(); +let since_the_epoch = start.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); +let in_nanoseconds = since_the_epoch.as_nanos(); +println!("Current time in nanoseconds since UNIX EPOCH: {}", in_nanoseconds); +``` + + + + +--- + +## 存款 + +智能合约始终使用 `yoctoNEAR` 处理 NEAR 金额(1Ⓝ = 10^24 yocto)。这意味着在与合约或用户余额交互时,您需要在 NEAR 和 yoctoNEAR 之间进行转换。 + +在底层,网络(和合约)将余额表示为 `u128`,并编码为字符串(因为 JSON 无法处理超过 52 位的整数)。 + +请**务必**以 `string` 形式发送金额(例如,1Ⓝ 为 `"1000000000000000000000000"`),**切勿**以 `number` 形式发送(例如 `1000000`)。相应地,每当您需要向用户显示金额时,请从 `string` 进行转换。 + + + + +在 JavaScript 中,您可以使用 `near-api-js` 库在 NEAR 和 yoctoNEAR 之间进行转换: + +```js +import { NEAR } from '@near-js/tokens' + +const units = NEAR.fromDecimal('1.5') // 1.5Ⓝ +console.log(units) // "1500000000000000000000000" + +const decimal = NEAR.fromUnits('1500000000000000000000000', 2) +console.log(decimal) // 1.5 +``` + + + + + +在 Rust 中,您可以直接使用 `near_token` crate 在 NEAR 和 yoctoNEAR 之间进行转换: + +```rust +use near_token::NearToken + +fn main() { + const TEN_NEAR: NearToken = NearToken::from_near(10); + + assert_eq!(TEN_NEAR.to_string(), "10.00 NEAR"); + assert_eq!(TEN_NEAR.as_near(), 10); + assert_eq!(TEN_NEAR.as_millinear(), 10000); + assert_eq!(TEN_NEAR.as_yoctonear(), 10000000000000000000000000); + + let input_str = "0.123456 NEAR"; + let input_near: NearToken = input_str.parse().unwrap(); + assert_eq!( + input_near, + NearToken::from_yoctonear(123456000000000000000000) + ); + +} +``` + + + + + +--- + +## Gas + +在调用合约方法时,您可以指定要附加到调用的 Gas 数量。在网络中,`Gas` 被表示为 `u64`,并编码为 `string`(因为 JSON 无法处理超过 52 位的整数)。 + +一般来说,计算量少的函数可以使用 `30 Tgas` 或更少的 Gas 进行调用,而更复杂的函数可能需要高达 `300 Tgas`。 + + + + + +在 JavaScript 中,没有内置库来处理 Gas 单位,但您可以轻松地按如下方式进行转换: + +```js +const TGAS = 1000000000000; // 1 Tgas = 10^12 gas units + +function toTgas(gas) { + return (gas * TGAS).toString(); +} + +function fromTgas(tgas) { + return (parseInt(tgas) / TGAS).toFixed(2); +} +``` + + + + + +```rust +use near_gas::NearGas; + +fn main() { + let data = "12.657 tgas"; + + let near_gas: NearGas = data.parse().unwrap(); + + // Convert the value to the most precise "gas" unit + assert_eq!(near_gas.as_gas(), 12657000000000); + // Convert the value to "gigagas" unit + assert_eq!(near_gas.as_ggas(), 12657); + + // Display Gas. It will print: "Here is 12.7 Tgas" + println!("Here is {}", near_gas); + + // When `serde` feature is enabled, NearGas can be used in serde-serializable structs. + // NearGas will be serialized to a gas-precision u64 value encoded as string. + #[derive(serde::Serialize)] + struct FunctionCallDetails { + used_gas: NearGas, + } + + let details = FunctionCallDetails { used_gas: near_gas }; + + assert_eq!(serde_json::to_string(&details).unwrap(), r#"{"used_gas":"12657000000000"}"#); +} +``` + + + diff --git a/zh/web3-apps/concepts/web-login.mdx b/zh/web3-apps/concepts/web-login.mdx new file mode 100644 index 00000000000..c02af446220 --- /dev/null +++ b/zh/web3-apps/concepts/web-login.mdx @@ -0,0 +1,39 @@ +--- +title: Web 登录方式 +description: "了解您的网站或 Web 应用可用的所有登录选项" +--- + +NEAR 提供多种选项,允许用户使用 NEAR 账户进行登录。以下是将 Web 登录集成到您的 Web 应用或网站中最流行的方法,每种方法都针对不同的使用场景和用户体验量身定制。 + +用户登录后,将能够使用其账户与 NEAR 区块链进行交互,包括调用智能合约、转移代币等操作。 + +--- + +## NEAR 连接器 + +[NEAR Connect](../tutorials/wallet-login) 是一个零依赖的轻量级库,允许用户使用其首选钱包连接到您的 dApp。它采用安全的基于沙箱的架构,钱包脚本在隔离的 iframe 中运行。 + +![Preview](https://github.com/user-attachments/assets/c4422057-38bb-4cd9-8bd0-568e29f46280) + + +您可以在 [NEAR 连接器教程](../tutorials/wallet-login)中学习如何将 NEAR Connect 集成到您的应用中。 + + +--- + +## 社交登录 + +[Privy](https://www.privy.io/) 是一项第三方服务,允许用户使用电子邮件或社交账户(Google、Facebook、Twitter 等)进行登录。登录后,系统会为用户创建一个 NEAR 钱包,他们可以充值并用于与您的 dApp 交互。 + + + +查看我们的 [Privy 集成示例](https://github.com/near-examples/hello-privy/),了解如何将 Privy 集成到您的 Web 应用中。 + + + +![Preview](https://framerusercontent.com/images/ugUCPrqIGlKFdxBwSbRoWriZtE.png?scale-down-to=2048&width=4018&height=2262) + + +**Web3Auth** +作为另一种社交登录方式,您可以查看 [Web3Auth](https://web3auth.io/)。我们有一个功能完整的 [Web3Auth 集成示例](https://github.com/near-examples/hello-web3auth/),展示如何将 Web3Auth 集成到您的 Web 应用中。 + diff --git a/zh/web3-apps/quickstart.mdx b/zh/web3-apps/quickstart.mdx new file mode 100644 index 00000000000..e6eed2891c5 --- /dev/null +++ b/zh/web3-apps/quickstart.mdx @@ -0,0 +1,151 @@ +--- +title: 您的第一个 Web3 应用 +sidebarTitle: 快速入门 +description: "快速指南:创建集成 NEAR 的 Web3 前端应用——构建一个 React/Next.js 应用,用户可以使用钱包登录并与智能合约交互。" +--- + +import { Github } from '/snippets/github.jsx' + +在本指南中,我们将向您展示如何**构建一个连接到 NEAR 的 React 应用**,用户可以使用钱包**登录**并与**合约**交互。 + + +**希望将 NEAR 集成到您的应用中?** + +如果您已经有一个应用程序,并希望将 NEAR 集成其中,我们建议您先阅读本指南,然后查看我们的[将 NEAR 集成到前端](./tutorials/wallet-login)文档。 + + + +--- + +## 模板设置 +如果您已安装 [Node.js](https://nodejs.org/en/download),可以使用 `create-near-app` 快速搭建模板: + +```bash + npx create-near-app@latest + + # ✔ What do you want to build? › Web Application + # ✔ Select a framework for your frontend › Next.js (Classic) + # ✔ Name your project (we will create a directory with that name) … near-template + # ✔ Run 'npm install' now? … yes +``` + +待文件夹准备好、所有依赖项安装完毕后,您可以使用 `pnpm` 启动开发服务器。 + +```bash +cd near-template # 进入您的项目文件夹 +npm run dev +``` + +在浏览器中访问 `http://localhost:3000` 以查看 dApp。请注意,由于 dApp 使用 NextJS,首次访问页面时加载时间可能较长。 + + + +请确保您使用的是 **node >= v22**,您可以使用 `nvm use 22` 轻松切换版本。 + + + + +**框架** +在本教程中,我们使用 **Next.js** 框架,采用"经典"的基于页面的路由方式。但您在创建应用时也可以选择其他框架,例如 Vite。 + + +--- + +## 落地页 + +应用启动后,您将看到落地页,其中渲染了一个导航栏,允许用户使用其 NEAR 钱包登录。您可以导航到文档或 `Near Integration` 页面(我们接下来会进入该页面)。 + +![img](/assets/docs/smart-contracts/tutorials/examples/hello-near-landing-page.png) +*Hello NEAR Gateway 的落地页* + +请使用您的 NEAR 账户登录。如果您还没有账户,可以立即创建一个。 + +
+ +### 上下文提供者 + +[Next.js](https://nextjs.org/) 使用模板系统,每个页面都是一个 React 组件。我们的主要逻辑定义在 `./src/pages/_app.js` 中,该文件: + +1. 创建一个 `NearProvider`,将其包裹整个应用以提供 NEAR 功能 +2. 渲染导航菜单和页面内容 + + + + + +NEAR Connect 是一个允许用户选择其首选 NEAR 钱包进行登录的库,我们的应用使用封装其功能的 **hooks**,使其更易于使用。 + + + +
+ +### 导航栏 +导航栏实现了一个按钮,允许用户使用 NEAR 钱包进行 `登录` 和 `注销`。主要逻辑来自 `useNearWallet` hook,该 hook 公开了所有与钱包相关的功能。 + + + +--- + +## 如何调用智能合约函数? + +现在您已了解落地页的工作原理,我们可以进入 `Near Integration` 页面,该页面从 [hello.near-examples.testnet](https://testnet.nearblocks.io/address/hello.near-examples.testnet) 合约中检索问候语。 + +### 读取操作与写入操作 + +| 操作类型 | 方法 | 费用 | 是否需要签名 | +|---------------|--------|------|-------------------| +| **读取(view)** | `viewMethod()` | 免费 | 否 | +| **写入(call)** | `callMethod()` | Gas 费(约 0.0001 Ⓝ) | 是 | + +视图方法查询合约状态而不修改它。调用方法会改变状态,并需要用户签署一笔交易。 + +![img](/assets/docs/smart-contracts/tutorials/examples/hello-near-gateway.png) +*`Near Integration` 页面视图* + +如果您尚未登录,请先登录,然后您将看到一个简单的表单,允许您在智能合约中存储问候语。 + +
+ +### 函数调用 Hooks +与[导航栏](#navigation-bar)类似,我们使用 `useNearWallet` hook 获取允许我们调用合约方法的函数: + +- `viewFunction` 用于调用只读函数 +- `callFunction` 用于调用修改合约状态的函数 + + + +#### 调用只读方法 + +例如,当我们想获取合约中存储的当前问候语时,我们在 `useEffect` hook 中使用 `viewFunction`: + + + +#### 调用写入方法 +另一方面,当用户提交新问候语时,我们使用 `callFunction` 向合约发送一笔交易: + + + +--- + +## 常见问题 + +### 用户需要 NEAR 代币才能登录吗? +不需要。钱包登录是免费的。用户只有在调用修改状态的合约函数时才需要支付 Gas 费。 + +### 用户可以使用哪些钱包进行连接? +NEAR 钱包(MyNearWallet、Meteor、HERE Wallet)和以太坊钱包(Metamask、WalletConnect、通过 EVM 支持的 Coinbase Wallet)。 + +### 如何避免用户每次都需要签署交易? +通过在钱包选择器配置中设置 `createAccessKeyFor: ` 来创建一个**函数调用访问密钥**。这允许应用程序自动签署不涉及付款的方法。 + +### 我可以在现有的 React 应用中使用吗? +可以。安装 `near-connect-hooks` 并按照我们的[集成指南](./tutorials/wallet-login)操作。 + +### 为什么使用 Next.js 而不是普通的 React? +Next.js 是推荐方案,但并非强制要求。您可以使用 Vite/React、Vue、Svelte 或任何框架。查看 `create-near-app` 模板以了解可用选项。 + +--- + +## 下一步 + +这就是我们的快速入门教程的全部内容。您现在已经看到了一个完整功能的前端,它可以与 NEAR 合约通信并渲染 Web3 组件。 diff --git a/zh/web3-apps/tutorials/frontend-multiple-contracts.mdx b/zh/web3-apps/tutorials/frontend-multiple-contracts.mdx new file mode 100644 index 00000000000..2eeba282b58 --- /dev/null +++ b/zh/web3-apps/tutorials/frontend-multiple-contracts.mdx @@ -0,0 +1,80 @@ +--- +title: 前端与多个合约交互 +sidebarTitle: 使用多个合约 +description: "在前端中与多个合约进行交互。" +--- + +import { Github } from '/snippets/github.jsx' + +本示例展示了如何从单个前端与多个合约进行交互。 + +具体而言,本示例演示了如何: + +1. 从多个合约查询数据。 +2. 同时调用多个合约中的方法。 + +--- + +## 从多个合约查询数据 + +要查询多个合约,只需执行多次 `view` 调用: + + + +--- + +## 分发多笔交易 + +`wallet` 对象支持同时分发多笔交易。但请注意,这些交易是独立执行的。 + +同时分发多笔交易只是一种改善用户体验的好方法,因为用户只需与钱包交互一次。 + + + +在本示例中,用户签署了两笔独立的交易: + +1. 一笔交易调用我们 [Hello NEAR 示例](https://github.com/near-examples/hello-near-examples)中的 `set_greeting` +2. 一笔交易调用我们 [GuestBook 示例](https://github.com/near-examples/guest-book-examples)中的 `add_message` + + +即使用户同时接受签署这些交易,这些交易仍然是**独立的**。也就是说,如果其中一笔失败,另一笔**不会**被回滚。 + + +--- + +## 批量操作 + +您可以将多个针对同一合约的[操作](../../smart-contracts/anatomy/actions)聚合到单笔交易中。批量操作**按顺序执行**,并且具有额外的好处:如果**其中一个失败**,则它们**全部会被回滚**。 + +```js + // 在一次操作中注册用户并向其转移 FT + const REGISTER_DEPOSIT = "1250000000000000000000"; + + const ftTx = { + receiverId: FT_ADDRESS, + actions: [ + { + type: 'FunctionCall', + params: { + methodName: 'storage_deposit', + args: { account_id: "" }, + gas: THIRTY_TGAS, deposit: REGISTER_DEPOSIT + } + }, + { + type: 'FunctionCall', + params: { + methodName: 'ft_transfer', + args: { receiver_id: "", amount: amount_in_yocto }, + gas: THIRTY_TGAS, deposit: 1 } + } + ] + } + + // 请求钱包签署并发送交易 + await wallet.signAndSendTransactions({ transactions: [ ftTx ] }) +``` diff --git a/zh/web3-apps/tutorials/localnet/introduction.mdx b/zh/web3-apps/tutorials/localnet/introduction.mdx new file mode 100644 index 00000000000..6526923f387 --- /dev/null +++ b/zh/web3-apps/tutorials/localnet/introduction.mdx @@ -0,0 +1,24 @@ +--- +title: 简介 +description: "了解 NEAR 上的本地网络(localnet)是什么。" +--- + +每个区块链本质上都是一个由许多计算机(称为节点)组成的点对点网络,这些节点相互通信、共享数据并验证交易是否遵循协议规则。在 NEAR 协议中,每个节点运行 [NEAR 运行时软件](https://github.com/near/nearcore),负责处理交易、生产区块和验证状态。 + +本地网络(Localnet)通常指的是完全在您自己机器上运行的私有 NEAR 网络,但在某些设置中,它也可以在同一网络上互相可见的多台机器上启动。然而,在大多数情况下,localnet 指的是单机网络。这是因为开发者通常将其用于测试合约、运行集成测试或自动化 CI/CD 管道,在这些场景中您并不真正关心共识问题,并且由于它是在验证合约/dApp 逻辑而非模拟主网去中心化,使用单个节点完全没有问题。 + +### 节点类型 + +在 NEAR 生态系统中,您可以在本地运行两种主要类型的节点。 + +#### 生产节点 `neard` + +`neard` 二进制文件是生产环境中使用的标准节点实现。它适合模拟具有共识、完整区块生产和状态持久化的真实 NEAR 网络。如果您在本地运行 `neard`,您实际上是在运行与验证者相同类型的节点,使其成为在真实条件下进行集成测试的良好选择。 + +#### 开发者节点 `near-sandbox` + +`neard` 和 `near-sandbox` 来自同一个代码库,共享相同的代码。它们使用完全相同的构建过程构建,唯一的区别是 `near-sandbox` 在编译时启用了一些额外功能,这些功能暴露了额外的 RPC 方法以改善开发者体验。除此之外,这两个二进制文件是相同的——它们行为相同,使用相同的配置文件,并遵循相同的命令行界面。 + +最有用的功能之一是状态补丁 RPC 方法 `sandbox_patch_state`。它允许您直接修改区块链状态而无需发送交易。这意味着您可以通过仅几个 RPC 请求将账户、余额或合约存储设置为所需状态,从而快速准备特定的测试场景,而无需经历多个已签名交易的过程。 + +另一个关键功能是快速推进 RPC 方法 `sandbox_fast_forward`。它允许您通过增加区块高度来在时间上推进区块链。当您的合约逻辑依赖于时间或基于区块的条件时,这特别有用。例如,如果您的合约将资金锁定一定数量的区块,您可以简单地快速推进到那个未来的高度,并立即验证预期行为,而无需等待数分钟、数小时或数天。 diff --git a/zh/web3-apps/tutorials/localnet/run.mdx b/zh/web3-apps/tutorials/localnet/run.mdx new file mode 100644 index 00000000000..ac089f707c1 --- /dev/null +++ b/zh/web3-apps/tutorials/localnet/run.mdx @@ -0,0 +1,172 @@ +--- +title: 运行您自己的本地网络 +description: "了解如何在 NEAR 上运行本地网络(localnet)。" +--- + +现在我们已经了解了什么是 localnet,让我们来完整地演示运行它并使用简单的智能合约和前端应用程序对其进行测试的完整流程。 + +在本教程中,我们将使用 `near-sandbox`,因为它是最简单、最快速的入门选择。本教程的目标是设置一个本地网络,在其上创建一个钱包账户,部署合约,最后运行一个[前端应用程序](https://github.com/near-examples/hello-localnet),该应用程序通过本地区块链直接与此合约交互。 + + + +另请参阅 NEAR MPC 团队提供的 [localnet 教程](https://github.com/near/mpc/blob/main/docs/localnet/localnet.md)。 + + + +### 前提条件 + +在开始之前,请确保您已安装以下几个必要工具。 + +- [near-cli-rs](https://github.com/near/near-cli-rs)——用于与 NEAR 区块链交互的 CLI; +- [cargo-near](https://github.com/near/cargo-near)——简化 Rust 智能合约构建/部署的 CLI。 + +### 操作指南 + +#### 步骤 1. 设置并启动沙箱节点 + +首先,运行以下命令: + +```sh +npx near-sandbox --home /tmp/near-sandbox init +``` + +此命令在主目录(即上述命令中的 `/tmp/near-sandbox`)内准备一个全新的配置。完成后,您将在该目录中找到几个已创建的文件: + +- `config.json`——主节点配置,定义网络端口、存储路径、运行时设置等 +- `genesis.json`——定义初始区块链状态,包括默认账户 `near` 和验证者账户(默认为 `test.near`) +- `validator_key.json`——用于生产区块的验证者密钥对 +- `node_key.json`——用于签署网络数据包的密钥对 + +此初始化过程仅需一次。配置就绪后,您可以随时启动和停止节点,区块链状态将持久保存在此文件夹中。 + +初始化完成后,启动节点: + +```sh +npx near-sandbox --home /tmp/near-sandbox run +``` + +节点将启动并开始在本地生产区块。 + + +要确认节点正在运行,您可以使用以下命令查询其 `status` RPC 端点:`curl http://127.0.0.1:3030/status`。 + + +#### 步骤 2. 导入 `test.near` 账户 + +在此步骤中,我们将导入在 `near-sandbox` 初始化期间创建的默认 `test.near` 账户。稍后我们需要此账户来部署智能合约,所以让我们现在就做好准备。 + +首先,确保您的 `near-cli-rs` 知道如何连接到您的本地网络。如果您已经有 `localnet` 配置,可以跳过此步骤。否则,运行: + +```sh +near config add-connection --network-name localnet --connection-name localnet-sandbox --rpc-url http://localhost:3030/ --wallet-url http://localhost:3030/ --explorer-transaction-url http://localhost:3030/ +``` + +在此过程中,CLI 可能会询问您几个交互式问题。您可以对所有问题安全地回答"No"——所有必要的值已在上述命令中定义。 + + +要确认连接设置正确,您可以使用以下命令查看 `test.near` 账户的信息:`near account view-account-summary test.near network-config localnet-sandbox now`。 + + +当您初始化 `near-sandbox` 时,它在主目录中创建了一个名为 `validator_key.json` 的文件。该文件包含 `test.near` 账户的私钥,我们需要将其导入 `near-cli-rs`。 + +要查看该密钥,运行: + +```sh +cat /tmp/near-sandbox/validator_key.json +``` + +您将看到类似以下的输出: + +```json +{ + "account_id": "test.near", + "public_key": "ed25519:...", + "secret_key": "ed25519:..." +} +``` + +复制 `secret_key` 的值,并通过运行以下命令将其导入 `near-cli-rs`(将 `` 替换为实际密钥): + +```sh +near account import-account using-private-key network-config localnet-sandbox +``` + +运行上述命令时,系统将提示您输入账户 ID。输入 `test.near` 并按 Enter 键。然后系统会询问您选择存储凭据的密钥链,选择 `Legacy Keychain`,因为它直接在文件系统中存储密钥,这是 `localnet` 最简单、最方便的选项。 + +#### 步骤 3. 部署智能合约 + +在此步骤中,我们将一个简单的 [Hello World 智能合约](https://github.com/near-examples/hello-near-examples)部署到我们的 `localnet`。 + +在部署之前,我们需要创建一个用于部署合约的账户。由于我们已经导入了 `test.near` 账户,可以将其作为父账户来创建一个新的子账户: + +```sh +near account create-account fund-myself hello-world.test.near '10 NEAR' autogenerate-new-keypair save-to-legacy-keychain sign-as test.near network-config localnet-sandbox sign-with-legacy-keychain send +``` + +此命令创建一个名为 `hello-world.test.near` 的新账户,初始余额为 10 NEAR。 + +接下来,克隆 Hello World 仓库并导航到合约目录: + +```sh +git clone git@github.com:near-examples/hello-near-examples.git +cd hello-near-examples/contract-rs +``` + +进入仓库后,运行以下命令: + +```sh +cargo near deploy build-non-reproducible-wasm hello-world.test.near without-init-call network-config localnet-sandbox sign-with-legacy-keychain send +``` + +此命令编译智能合约,优化生成的 `.wasm` 文件,并将其直接部署到 `localnet` 上的 `hello-world.test.near` 账户。 + + +要验证合约是否成功部署,您可以使用以下命令检查 `hello-world.test.near` 账户的代码:`near contract inspect hello-world.test.near network-config localnet-sandbox now`。如果部署成功,您将在输出中看到合约方法的列表,例如 `get_greeting` 和 `set_greeting`。 + + +#### 步骤 4. 创建用户钱包账户 + +在此步骤中,我们将在可以连接到我们 `localnet` 的钱包内创建一个用户账户。这很重要,因为前端应用程序通常通过钱包连接与合约交互。并非每个 NEAR 钱包都支持 `localnet` 连接,但在本教程中,我们将使用完全兼容的 Intear Wallet。 + +访问 https://wallet.intear.tech。如果您还没有账户,创建一个或导入任何现有账户。使用哪个账户无关紧要,此步骤只是为了让您进入钱包界面。 + +进入钱包后,从侧边栏打开 `Settings` 面板,然后找到 `Developer` 标签。您将看到一个 `Localnet` 子部分,您可以在其中添加自己的本地网络配置。点击 `Add`,并按如下填写字段:将 RPC URL 设置为 `http://127.0.0.1:3030`,将网络 ID 设置为 `localnet`。然后点击 `Save`。 + +如果一切配置正确,您应该会看到新网络旁边出现一个绿色的 `Online` 徽标,表示已连接。以下是它应该呈现的效果: + +![Preview](/assets/docs/web3-apps/intear-wallet-localnet-connected.png) + +接下来,点击 `localnet` 条目右侧的 `Control` 按钮。找到 `Create New Account` 部分,为您的新账户输入名称(在我们的示例中为 `user.local`),然后点击 `Create`。 + +请求处理完成后,新账户将出现在您的钱包中。此时,您在 `localnet` 上有了一个有效账户,可以通过 Intear Wallet 签署交易。 + +#### 步骤 5. 启动前端应用程序并与其交互 + +首先克隆仓库: + +```sh +git clone https://github.com/near-examples/hello-localnet +cd hello-localnet +``` + +进入仓库后,使用 `pnpm` 安装依赖项: + +```sh +pnpm install --frozen-lockfile +``` + +然后,启动 Next.js 开发服务器: + +```sh +pnpm dev +``` + +访问 http://localhost:3000/hello-near,在右上角点击 `Login`。当提示选择钱包时,选择 `Intear Wallet` 的 Web 版本。确保使用之前创建的账户进行连接,在我们的示例中为 `user.local`。 + +连接后,您将看到一个输入框和一个更新问候语的按钮。在输入框中输入任何新问候语,然后点击 `Save`。您将被重定向到钱包以批准交易。批准后,交易将被发送并在 `localnet` 节点上执行。 + +返回前端,您会发现问候语已经更改。 + +--- + +您现在已经完成了整个设置本地 NEAR 环境的流程——从运行沙箱和部署合约,到连接钱包并通过前端与其交互。通过这个设置,您可以自由实验,安全测试合约,并构建端到端的去中心化应用,而无需接触主网或测试网。 diff --git a/zh/web3-apps/tutorials/mastering-near/0-intro.mdx b/zh/web3-apps/tutorials/mastering-near/0-intro.mdx new file mode 100644 index 00000000000..539b82465a0 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/0-intro.mdx @@ -0,0 +1,114 @@ +--- +title: 掌握 NEAR 的循序渐进指南 +sidebarTitle: 简介 +description: "从合约到使用索引器的前端,构建完整的 Web3 应用。" +--- + +欢迎!在本指南中,我们将帮助您了解 NEAR 技术栈,以便您能够在最短的时间内从零开始构建 Web3 应用程序。 + +我们将从一个简单的拍卖合约开始,并逐步在其基础上构建,以创建一个完整的 Web3 应用程序来进行链上拍卖。 + +当您完成本教程时,您将学习到以下几个概念,并了解如何使用沿途的许多关键原语: + +- [创建简单的智能合约](./1.1-basic#the-contracts-state) +- [为合约编写测试](./1.2-testing) +- [将合约部署到测试网](./1.3-deploy) +- [锁定合约](./1.3-deploy#locking-the-contract) +- [创建与合约交互的前端](./2.1-frontend) +- [使用索引 API 查看历史出价](./2.2-indexing) +- [进行跨合约调用](./3.1-nft#transferring-the-nft-to-the-winner) +- [使用非同质化代币](./3.1-nft) +- [使用同质化代币](./3.2-ft) +- [修改工厂合约以部署您自己的合约](./4-factory) + +--- + +## 前提条件 + +开始之前,请确保设置好您的开发环境! + + + + 请参阅我们的博客文章,获取在 Windows 上使用 NEAR 入门的逐步指南,了解如何设置 WSL 和您的环境。 + + + + + + + ```bash + # 使用 nvm 安装 Node.js(更多选项请参见:https://nodejs.org/en/download) + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + nvm install latest + + # 安装 NEAR CLI 以部署和与合约交互 + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + ``` + + + + + + ```bash + # 安装 Rust:https://www.rust-lang.org/tools/install + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + # 合约将被编译为 wasm,所以我们需要添加 wasm 目标 + rustup target add wasm32-unknown-unknown + + # 安装 NEAR CLI 以部署和与合约交互 + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + + # 安装 cargo near 以帮助构建合约 + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh + ``` + + + + + +我们将使用 [NEAR CLI](/tools/cli) 通过终端与区块链交互,您可以选择 JavaScript 或 Rust 来编写合约。 + +--- + +## 概览 + +本系列将涉及 NEAR 技术栈的不同层次。每个章节都将独立于前一个章节,因此请随时跳转到您最感兴趣的章节。 + +#### 1. 智能合约入门 +1. [拍卖合约](./1.1-basic):我们介绍一个简单的拍卖智能合约 +2. [测试合约](./1.2-testing):了解如何在真实环境中测试您的合约 +3. [部署合约](./1.3-deploy):将您的合约部署到 NEAR 区块链 + +#### 2. 前端入门 + +1. [创建前端](./2.1-frontend):学习如何将前端与智能合约连接 +2. [索引历史数据](./2.2-indexing):使用 API 追踪历史出价 + +#### 3. 使用原语 +1. [向获胜者颁发 NFT](./3.1-nft):向最高出价者颁发 NFT 以表示其获胜 +2. [集成同质化代币](./3.2-ft):允许人们使用同质化代币出价(例如稳定币) +3. [更新前端](./3.3-new-frontend):更新前端以使用合约的扩展功能 + +#### 3. 拍卖工厂 +1. [创建工厂](./4-factory):允许用户轻松部署和初始化他们自己的拍卖合约 + +--- + +## 下一步 + +准备好开始了吗?让我们跳转到[拍卖合约](./1.1-basic),开始您的学习之旅! + +--- + + +**本文的版本信息** + +- near-cli: `0.12.0` +- rustc: `1.78.0` +- cargo: `1.80.1` +- cargo-near: `0.13.2` +- rustc: `1.78.0` +- node: `21.6.1` + + diff --git a/zh/web3-apps/tutorials/mastering-near/1.1-basic.mdx b/zh/web3-apps/tutorials/mastering-near/1.1-basic.mdx new file mode 100644 index 00000000000..94e09b1f457 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/1.1-basic.mdx @@ -0,0 +1,319 @@ +--- +title: 基础拍卖 +description: "了解如何在 NEAR 上构建拍卖智能合约。" +--- + +import { Github } from '/snippets/github.jsx' + +在本节中,我们将分析一个简单的拍卖合约,该合约允许用户出价、追踪最高出价者并在拍卖结束时领取代币。之后,我们将介绍如何测试合约,以及如何在 `testnet` 上部署它。 + + +**文档** + +在本教程中,我们将依赖[智能合约文档](/smart-contracts/quickstart)及其不同章节。 + + + + +**前提条件** + +开始本教程之前,请确保阅读[前提条件](./0-intro)章节并安装必要的工具。 + + + +--- + +## 克隆合约 + +首先,我们将从 GitHub 克隆[教程仓库](https://github.com/near-examples/auctions-tutorial)。该仓库包含用 JavaScript(`./contract-ts`)和 Rust(`./contract-rs`)编写的相同智能合约。 + +导航到您偏好的语言文件夹,然后进入 `01-basic-auction` 文件夹。 + + + + + + ```bash + git clone git@github.com:near-examples/auctions-tutorial.git + + cd contract-ts/01-basic-auction + ``` + + + + + + ```bash + git clone git@github.com:near-examples/auctions-tutorial.git + + cd contract-rs/01-basic-auction + ``` + + + + + + +**前端** + +该仓库还包含一个与合约交互的前端应用程序。您可以在 `frontends` 文件夹中找到它。我们将在后续章节中介绍前端。 + + + +--- + +## 合约状态 + +该合约允许用户使用 `$NEAR` 代币出价,并追踪最高出价者。让我们先看看如何定义合约的状态,即合约将存储的数据。 + + + + + + + + #### 装饰器 + 首先要注意的是,合约的主类使用 `@NearBindgen` 装饰器标记,该装饰器还允许进一步指定合约**必须在使用前初始化**。 + + #### 存储(即状态) + 代码揭示的另一个重要信息是,合约可以存储不同类型的数据,在本例中: + + - `highest_bid` 是一个 `Bid` 实例,存储: + - `bid`:一个代表 `$NEAR` 代币金额(以 `yoctonear` 计,`1Ⓝ = 10^24 yⓃ`)的 `BigInt` + - `bidder`:一个代表哪个账户出价的 `AccountId` + - `auction_end_time` 一个代表**纳秒**级 `unix 时间戳`的 `BigInt` + - `auctioneer` 一个说明谁可以在拍卖结束时提取资金的 `AccountId` + - `claimed` 一个追踪拍卖者是否已领取资金的 `boolean` + + + + + + + + #### 宏 + 首先要注意的是使用 `#[near(contract_state)]` 宏来表示主结构体,并派生 `PanicOnDefault` 来指定合约**必须在使用前初始化**。 + + 我们还使用 `#[near(serializers = [json, borsh])]` 宏为 `Bid` 结构体启用 `borsh` 和 `JSON` 的(反)序列化。一个经验法则:对于将用作函数输入/输出的结构体使用 `json` 序列化器,对于将保存到状态的结构体使用 `borsh`。 + + #### 存储(即状态) + 代码揭示的另一个重要信息是,合约可以存储不同类型的数据。 + + - `highest_bid` 是一个 `Bid` 实例,存储: + - `bid`:一个简化处理 `$NEAR` 代币金额的 `NearToken` + - `bidder`:出价的 `AccountId` + - `auction_end_time` 是一个代表**纳秒**级 `unix 时间戳`的 `U64` + - `auctioneer` 一个说明谁可以在拍卖结束时提取资金的 `AccountId` + - `claimed` 一个追踪拍卖者是否已领取资金的 `boolean` + + + + + + +**了解更多** + +您可以在以下文档页面中阅读更多关于合约结构以及它可以存储的数据类型的信息: +- [合约基本结构](/smart-contracts/anatomy/anatomy) +- [合约状态](/smart-contracts/anatomy/storage) +- [数据类型](/smart-contracts/anatomy/types) + + + +--- + +## 初始化函数 + +现在让我们来看看初始化函数,我们需要调用它来确定拍卖结束的时间。 + + + + + + + + #### 装饰器 + 我们使用 `@initialize({ privateFunction: true })` 装饰器来表示初始化函数。`privateFunction:true` 表示该函数只能由部署合约的账户调用。 + + + + + + + + #### 宏 + 我们使用 `#[init]` 宏来表示初始化函数。请注意,初始化函数需要返回一个 `Self` 实例,即合约的结构体。 + + 同时,`#[private]` 表示该函数只能由部署合约的账户调用。 + + + + + +#### 结束时间 +结束时间使用**纳秒**级的 `unix 时间戳`表示,在调用初始化函数时需要以 `String` 形式提供。这是因为智能合约不能接收大于 `52 位`的数字,而 `unix 时间戳`以 `64 位`表示。 + +#### 初始出价 +请注意,我们使用来自 `current account id` 的 `1 yoctonear` 出价初始化合约。这意味着在合约初始化后,第一次出价将由合约以 10^-24 NEAR 进行。 + +#### 已领取 +`claimed` 字段初始化为 `false`,因为拍卖者尚未领取资金。 + +#### 拍卖者 +拍卖者由部署者在初始化时设置,是拍卖结束时可以领取资金的账户。 + + +**了解更多** + +您可以在我们的[合约函数文档](/smart-contracts/anatomy/functions)中阅读更多关于合约接口的信息,并在[数据类型文档](/smart-contracts/anatomy/types)中了解数据类型。 + + + +--- + +## 只读函数 + +合约实现了四个函数,用于访问其存储的数据,即迄今为止的最高出价(金额和出价者)、拍卖结束的时间、拍卖者,以及拍卖是否已被领取。 + + + + + + + + 不改变合约状态(即只从中读取)的函数称为 `view` 函数,并使用 `@view` 装饰器进行标记。 + + + + + + + + 不改变合约状态(即只从中读取)的函数称为 `view` 函数,并接受对 self 的非可变引用(`&self`)。 + + + + + +视图函数**免费调用**,**不需要** NEAR 账户签署交易即可调用。 + + +**了解更多** + +您可以在我们的[合约函数文档](/smart-contracts/anatomy/functions)中阅读更多关于合约接口的信息,并在[数据类型文档](/smart-contracts/anatomy/types)中了解数据类型。 + + + +--- + +## 出价函数 + +没有出价功能的拍卖不是拍卖!为此,合约包含一个 `bid` 函数,用户将调用该函数并附上一些 `$NEAR` 代币。 + +该函数相当简单:它验证拍卖是否仍然有效,并将附加的存款与当前最高出价进行比较。如果出价更高,则更新 `highest_bid` 并退还给前一个出价者。 + + + + + + + + + + + + + + + + + +#### 可支付函数 +首先要注意的是,该函数会改变状态,因此在 JS 中使用 `@call` 装饰器标记,而在 Rust 中接受对 self 的可变引用(`&mut self`)。要调用此函数,NEAR 账户需要签署交易并消耗 GAS。 + +其次,该函数被标记为 `payable`,这是因为默认情况下**函数不接受 `$NEAR` 代币**!如果用户在调用未标记为 `payable` 的函数时附加代币,交易将失败。 + +#### 执行环境 +请注意,该函数可以访问有关其运行环境的信息,例如谁调用了该函数(`前驱账户`)、他们附加了多少代币作为存款(`附加存款`),以及函数执行时的近似 `unix 时间戳`(`区块时间戳`)。 + +#### 代币转移 +该函数最后通过创建一个 `Promise` 将代币转移给前一个出价者。此代币金额将立即扣除,并在当前函数执行完毕后的下一个区块中转移。 + +请注意,在第一次出价时,合约将向自身发送 1 yoctonear,这是可以的,因为我们可以安全地假设合约将拥有可以向自身发送的最小面额 `$NEAR`。 + + + +当用户向调用附加代币时,代币会在函数执行之前存入合约的账户。但是,如果函数在执行过程中出错,代币将立即退还给用户。 + + + + +**了解更多** + +您可以在以下资料中阅读更多关于环境变量、可支付函数以及合约可执行哪些操作的内容: +- [环境变量](/smart-contracts/anatomy/environment) +- [可支付函数](/smart-contracts/anatomy/functions) +- [转账和操作](/smart-contracts/anatomy/actions) + + + +--- + +## 领取函数 + +您会注意到合约有一个名为 `claim` 的最终函数,这允许拍卖者在拍卖结束时从合约中领取资金。由于在 NEAR 上,智能合约账户和用户账户是相同的,合约在部署时仍然可以有密钥,因此用户可以通过钱包直接从合约中领取资金。但是,这带来了一个安全问题,因为拥有密钥意味着密钥持有者可以在任何时候恶意地从合约中取走资金,更改合约的状态,甚至完全删除合约。通过实现 `claim` 函数,我们可以之后通过删除所有访问密钥来锁定合约,并让拍卖者通过代码预设的条件来领取资金。 + + + + + + + + + + + + + + + + + +这个函数相当简单,它做了四件事: +1) 检查拍卖是否已结束(当前时间戳是否已过拍卖结束时间)。 +2) 检查拍卖是否尚未被领取。 +3) 将拍卖设置为已领取。 +4) 如果以上条件成立,则将等于最高出价的 `$NEAR` 转账给拍卖者。 + + +**了解更多** + +您可以在文档的以下章节中阅读更多关于锁定合约的内容:[锁定账户](/protocol/accounts-contracts/access-keys#locked-accounts) + + + +--- + +## 结论 + +在本教程的这一部分,我们已经了解了智能合约如何存储数据、变更存储的数据以及查看数据。在[下一部分](./1.2-testing),我们将介绍如何测试合约,以便我们能够在将其部署到 `testnet` 之前确保它按预期工作。 diff --git a/zh/web3-apps/tutorials/mastering-near/1.2-testing.mdx b/zh/web3-apps/tutorials/mastering-near/1.2-testing.mdx new file mode 100644 index 00000000000..075ea740caf --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/1.2-testing.mdx @@ -0,0 +1,210 @@ +--- +title: 沙箱测试 +description: "在真实的沙箱环境中测试我们的合约。" +--- + +import { Github } from '/snippets/github.jsx' + +在上一节中,我们分析了合约的代码,了解了其工作原理。现在,我们需要对其进行测试,确保它按预期工作!对于合约,您可以进行两种类型的测试:单元测试和沙箱测试。 + +在这里,我们将重点关注沙箱测试,因为它允许我们在真实环境中部署合约,让我们能够创建多个账户并与合约交互,就如同它已部署在区块链上一样。 + + +**单元测试** + +单元测试内置于语言中,用于单独测试合约函数。当只需要少量上下文时,这些测试效果很好。但是,它们无法测试链上交互——例如向账户发送 `$NEAR` 代币——因为这些操作需要由网络处理。 + + + +--- + +## 创建账户 + +我们的测试首先创建多个各有 10 个 `$NEAR` 代币的账户,并将合约部署到其中一个账户上。 + + + + + + 为了部署合约,我们在 `package.json` 中将编译后的 WASM 合约路径作为参数传递给测试。确实,当执行 `npm run test` 时,命令首先会编译合约,然后运行测试。 + + + + + + 请注意,沙箱会自行编译代码,因此我们无需在运行测试之前预先编译合约。 + + + +--- + +## 合约初始化 + +为了初始化,合约账户调用自身,调用 `init` 函数,将 `end_time` 设置为未来 60 秒。 + + + + + + + + +**时间单位** + +合约以**纳秒**计量时间,因此我们需要将 `Date.now()`(以毫秒表示)的结果乘以 `10^6`。 + + + + + + + + + + +**时间单位** + +合约以**纳秒**计量时间,因此我们需要将 `Utc::now().timestamp()`(以秒表示)的结果乘以 `10^9`。 + + + + + + + +**时间是字符串** + +请注意,时间以 `String` 形式传递给合约,这是因为智能合约不能接收大于 `52 位`的数字,而我们希望传递**纳秒**级的 `unix 时间戳`。 + + + +--- + +## 出价 + +现在合约已部署并初始化,我们可以开始出价并检查合约的行为是否符合预期。 + +我们首先让 `alice` 出价 1 NEAR,并检查合约是否正确记录了该出价。然后,我们让 `bob` 出价 2 NEAR,并检查最高出价是否已更新,以及 `alice` 是否得到了 NEAR 的退款。 + + + + + + + + + + + + + + + + + +#### 检查余额 +请注意,我们如何检查 `alice` 是否已收到退款。我们在她第一次出价后查询她的余额,然后检查 `bob` 出价后余额是否增加了 1 NEAR。 + +您可能会想检查 `alice` 在获得退款后余额是否恰好为 10 NEAR,但 `alice` 的余额不可能再是 10 NEAR 了,因为当 `alice` 调用 `bid` 时,一些 `$NEAR` 已作为 **`gas` 费**被消耗。 + +#### 测试无效调用 + +在测试时,我们还应该检查合约是否不允许无效调用。下一部分检查合约不允许出价额比前一个出价少的 `$NEAR` 代币。 + + + + + + + + + + + + + + + + + +--- + +## 快进时间 +沙箱允许我们快进时间,这对于测试拍卖结束时的合约非常有用。测试推进 200 个区块以过去一分钟,从而允许领取拍卖。 + +之后,拍卖现在可以被领取。一旦领取,测试检查拍卖者是否收到了正确数量的 `$NEAR` 代币。 + + + + + + + + + + + + + + +如果您完整查看测试,您将看到我们还测试了其他无效调用,例如拍卖者在拍卖结束前尝试领取,以及用户在拍卖结束后尝试出价。 + +--- + +## 执行测试 + +现在我们了解了测试的内容,让我们运行测试吧! + + + + + + ```bash + # 如果尚未安装依赖,请先安装 + npm install + + # 运行测试 + npm run test + ``` + + + + + + ```bash + cargo test + ``` + + + + + +所有测试都应该通过,您应该在控制台中看到测试的输出。如果您看到任何错误,请在 [NEAR Discord](https://near.chat) 或通过 [Telegram](https://t.me/neardev) 联系我们,我们将帮助您解决! + +--- + +## 结论 + +在本教程的这一部分,我们已经了解了如何使用沙箱测试环境来测试合约。我们测试了合约的初始化、出价和时间推进。 + +您现在已准备好进入[下一节](./1.3-deploy),在那里我们将合约部署到 `testnet` 并通过 CLI 与其交互。 diff --git a/zh/web3-apps/tutorials/mastering-near/1.3-deploy.mdx b/zh/web3-apps/tutorials/mastering-near/1.3-deploy.mdx new file mode 100644 index 00000000000..1775a75df6e --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/1.3-deploy.mdx @@ -0,0 +1,142 @@ +--- +title: 部署到测试网 +description: "将我们的拍卖合约部署到测试网。" +--- + +import { Github } from '/snippets/github.jsx' + +在前几节中,我们了解了如何实现一个简单的拍卖智能合约,并使用沙箱测试验证了其正确性。 + +现在是时候将其发布到实际区块链上并与之交互了!在本节中,我们将向您展示如何创建一个简单的测试网账户,部署合约,并通过 CLI 与其交互。 + + +**网络** + +NEAR 有两个主要网络供您使用:`testnet` 和 `mainnet`。`testnet` 网络的行为与主网完全相同,但使用没有真实价值的测试代币。 + + + +--- + +## 测试网账户 + +要部署合约,您需要一个测试网账户。如果您还没有,可以使用以下命令创建一个: + +```bash +# 使用 near-cli 创建账户(contractId 必须以 .testnet 结尾) +near create --useFaucet +``` + +将 `` 替换为您想给账户的名称,并确保它以 `.testnet` 结尾。 + +该账户将创建时带有 **10 NEAR**(这些是测试代币)。 + + +**测试网水龙头** + +请注意,我们使用 `--useFaucet` 标志从 NEAR 水龙头自动请求测试代币。 + +水龙头仅在测试网网络上可用——这是 CLI 的默认网络。 + + + +--- + +## 部署合约 + +要部署合约,您需要将合约代码编译为 WebAssembly(WASM),然后将其部署到网络上。 + + + + + + ```bash + # 编译合约 + npm run build + + # 部署合约 + near deploy ./build/auction-contract.wasm + + # 初始化合约,2 分钟后结束 + TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000) + near call init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": ""}' --useAccount + ``` + + + + + + ```bash + # 使用 cargo-near 编译合约 + cargo near build + + # 部署合约 + near deploy ./target/near/auction-contract.wasm + + # 初始化合约,2 分钟后结束 + TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000) + near call init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": ""}' --useAccount + ``` + + + + + +将 `` 替换为另一个账户的名称,这不应该与合约账户相同,因为我们打算删除其密钥。 + +--- + +## 锁定合约 + +如前所述,我们应该通过删除密钥来锁定账户。这允许我们的用户在不必信任账户所有者的情况下与合约交互。 + +```bash +near account delete-keys +``` + +接下来,指定合约账户并点击右箭头 → 删除所有密钥。确保选择测试网。 + + +删除密钥时要格外小心,确保从正确的账户删除,因为您将永远无法再访问该账户! + + +现在合约已部署、初始化并锁定,我们可以使用 CLI 向其发送交易。 + + +**交互式 CLI** +NEAR 的 CLI 是交互式的,这意味着您可以输入 `near` 并点击浏览所有可能的选项,而无需记住特定命令。 + + +--- + +## 与合约交互 +我们现在准备好通过调用合约上的 `bid` 函数来开始出价了。我们建议您创建**两个新账户**来模拟不同的出价者。 + +```bash +# 调用合约出价 +near call bid --useAccount --deposit 1 + +# 获取最高出价 +near view get_highest_bid +``` + +请注意,我们调用 `bid` 函数时不带参数,但向交易附加了 1 NEAR。这是我们的出价金额。 + +对于 `get_highest_bid` 函数,我们不需要指定哪个用户在调用它,因为它是一个视图函数,不需要消耗 Gas 即可执行。 + +--- + +## 结论 + +我们现在已经了解了如何将合约部署到 `testnet` 并使用 NEAR CLI 与其交互。 + +在继续之前有一个建议。当人们学会使用 CLI 后,他们会变得懒惰,直接在测试网上测试新的合约功能。虽然这很诱人,但不推荐这样做。 + +不要将测试网作为测试合约的**唯一方式**。始终先在**沙箱环境**中测试您的合约,只有当您确信一切按预期工作时,才将其部署到测试网。 + + +**前端** + +通常,您只会使用 CLI 来部署和初始化合约。之后,所有交互都将通过前端进行。这就是为什么在[下一节](./2.1-frontend)中我们将转向创建与合约交互的前端。 + + diff --git a/zh/web3-apps/tutorials/mastering-near/2.1-frontend.mdx b/zh/web3-apps/tutorials/mastering-near/2.1-frontend.mdx new file mode 100644 index 00000000000..d4066e080fc --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/2.1-frontend.mdx @@ -0,0 +1,156 @@ +--- +title: 创建前端 +description: "使用 React 为我们的拍卖创建前端界面。" +--- + +import { Github } from '/snippets/github.jsx' + +现在我们已经成功创建了合约,是时候构建一个前端,为与其交互提供用户友好的界面了。到目前为止,我们一直使用 CLI 发送交易并查看合约状态。然而,前端为最终用户提供了一种更直观的方式来与合约交互。它们可以在一个地方显示所有相关信息,允许用户只需点击按钮即可进行调用,并且唯一的前提条件是拥有一个钱包。 + +--- + +## 前端结构 + +导航到拍卖前端。 + +```bash +cd frotends/01-frontend +``` + +这里有一个简单的 Next.js 前端,我们将逐步了解为 NEAR 智能合约创建前端的基础知识。 + +首先,让我们通过快速浏览重要文件来了解前端中代码的结构。 + +| 文件 | 描述 | +|----------------------------------|---------------------------------------------------------------------------------| +| **_app.js** | 负责渲染页面,初始化钱包对象并将其添加到全局上下文中 | +| **index.js** | 主页面,项目组件在此加载,包含应用程序的大部分逻辑,如查看合约状态和出价逻辑 | +| **near.js** | 包含钱包类,具有与钱包和区块链交互的方法 | +| **context.js** | 保存全局上下文——可以在任何地方访问的钱包对象和已登录账户 ID | +| **config.js** | 指定拍卖合约的账户 ID | +| **Navigation.jsx** | 包含一个按钮,供用户登录和注销钱包的组件 | +| **Bid.jsx** | 允许用户进行出价的组件 | +| **LastBid.jsx** | 显示最高出价及最高出价下次刷新时间的组件 | +| **Timer.jsx** | 显示拍卖结束前剩余时间的组件;如果已结束,则显示领取拍卖的按钮,并声明拍卖已结束 | + +--- + +## 指定合约 + +我们有一个配置文件,用于指定前端将与之交互的拍卖合约名称。示例中已指定了一个示例拍卖合约,但您可以将合约更改为您部署的拍卖合约。 + + + +--- + +## 设置钱包 + +为了能够完全与合约交互——发送出价和领取拍卖——您需要一个 `wallet` 来签署交易。钱包安全地存储您的私钥,并允许您签署交易而不将私钥暴露给前端。钱包选择器允许用户从一系列钱包中进行选择。 + +我们在 `near.js` 文件中通过公开用于完成各种任务的方法来抽象钱包选择器。欢迎[查看该文件](https://github.com/near-examples/auctions-tutorial/blob/main/frontends/01-frontend/src/wallets/near.js),以完全了解钱包选择器的实现方式。 + +钱包对象在 `app.js` 文件中初始化,并与已登录的账户一起添加到全局上下文中,使其更容易在应用程序的任何地方访问。 + + + + + +**访问密钥** + +在 NEAR 上,除了普通的全访问密钥外,我们还有 `function-call access keys`(函数调用访问密钥),它们被授予应用程序以允许其代表用户签署 `non-payable`(不涉及付款)的交易。这样,钱包就不需要为每个非关键交易弹出,从而改善了用户体验。在创建钱包对象时,您可以决定是否为应用程序创建访问密钥。但在本示例中,我们选择不创建,因为我们将调用的主要函数 `bid` 是 `payable`(可支付)的。 + +您可以在[此处](/protocol/accounts-contracts/access-keys)进一步了解 NEAR 的密钥模型。 + + + +我们在 `navigation` 组件中添加了登录和注销按钮,以调用 `near.js` 文件中相应的方法。 + + + + +--- + +## 显示最高出价 + +要从拍卖中获取最高出价及其出价者,我们调用 `get_highest_bid`。由于此函数以 `yoctoNEAR` 返回最高出价,我们除以 `10^24` 得到以 NEAR 表示的金额。 + + + + +在钱包文件中,您会看到我们向 RPC 提供商发起查询,由于我们不签署交易,所以这里不需要钱包。这里我们使用 https://rpc.testnet.near.org,但请注意[有许多不同的提供商可用](/api/rpc/providers)。我们使用乐观最终性(optimistic finality)查询 RPC,它查询节点上记录的最新区块。或者,可以使用最终最终性(final finality),其中区块已由网络上至少 66% 的验证者验证,但这会提供稍有延迟的信息(仅延迟几秒钟)。 + +然后,我们将最高出价者的信息传递给 `LastBid` 组件,以显示出价金额和出价者的账户 ID。 + + + + +--- + +## 更新最高出价 + +我们希望随时了解最高出价,因为自页面加载以来,其他人可能已经出了更高的价。为了解决这个问题,我们使用 `setInterval` 每 20 秒获取一次合约信息,如果最高出价发生变化则进行更新。在实际应用中,您可能希望更频繁地刷新出价金额,但为了节省 RPC 调用,我们每 20 秒进行一次。 + + + +--- + +## 拍卖结束时间 + +合约以自 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC)以来的纳秒数存储拍卖结束时间。在我们的前端中,我们将以天、小时、分钟和秒来显示剩余时间。 + + + +--- + +## 进行出价 + +要进行出价,我们使用 `bid` 函数调用合约。我们以 `yoctoNEAR` 指定存款金额,这将是出价金额。输入框将以 NEAR 接受出价金额,所以我们乘以 `10^24` 得到要发送的正确金额。我们还指定要附加到交易的 Gas 数量,这里我们附加 30Tgas,足够让交易顺利通过,未使用的 Gas 会退还给我们。 + +在这里,由于用户正在改变合约的状态而不仅仅是查看它,用户需要签署交易。因此,钱包将弹出显示交易详情。 + + + + +--- + +## 领取拍卖 + +拍卖结束后(当前时间大于结束时间),拍卖可以被领取。此时,计时器将被隐藏,并显示一个领取拍卖的按钮。点击后,将在拍卖合约上调用 `claim` 函数,向最高出价者发送 NFT,向拍卖者发送 FT。 + + + +--- + +## 结论 + +在本教程的这一部分,我们为 NEAR 合约实现了一个简单的前端。在此过程中,您学习了如何使用钱包选择器进行用户登录和注销、如何查看合约状态,以及如何签署和发送交易。 + +虽然我们可以看到最高出价,但我们可能想查看拍卖的出价历史。由于合约只存储最近的出价(以降低存储成本),我们需要使用索引器来获取历史数据。在本教程的[下一部分](./2.2-indexing),我们将使用 API 端点查询历史数据。 diff --git a/zh/web3-apps/tutorials/mastering-near/2.2-indexing.mdx b/zh/web3-apps/tutorials/mastering-near/2.2-indexing.mdx new file mode 100644 index 00000000000..a53caeb5b14 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/2.2-indexing.mdx @@ -0,0 +1,100 @@ +--- +title: 索引历史数据 +description: "使用数据 API 检索拍卖历史" +--- + +import { Github } from '/snippets/github.jsx' + +在我们的前端中,由于之前的出价存储在合约状态中,我们可以轻松显示它。然而,我们无法查看拍卖的之前出价记录。索引器用于从区块链中获取历史数据并将其存储在数据库中。由于索引器设置需要一些时间且运行成本较高,我们将使用 NEAR Blocks 提供的预定义 API 端点,查询他们运行的索引器,获取我们需要的数据。 + +--- + +## NEAR Blocks API 密钥 + +NEAR Blocks 提供免费套餐,允许您每分钟进行 6 次调用,这对于我们的使用场景已经足够了。要获取 API 密钥,请访问 https://dash.nearblocks.io/user/overview 并注册。登录后,转到 `API Keys`,然后点击 `Add key`,给它取一个您喜欢的名称。 + +我们将创建一个名为 `.env.local` 的新文件来存储我们的 API 密钥。 + +```env +API_KEY=YOUR_API_KEY_GOES_HERE +``` + +我们将 API 密钥放在 `.env.local` 文件中,以防止用户在浏览器中访问它并在其他地方使用我们的密钥。我们还应该将 `.env.local` 添加到我们的 `.gitignore` 文件中,以防止它被推送到 GitHub。 + +--- + +## 调用 API 端点 + +NextJS 允许我们使用 API 路由轻松创建服务器端函数。我们需要在服务器端而非客户端进行此 API 调用,以避免暴露我们的 API 密钥。我们将在 src/pages/api 中创建一个名为 `getBidHistory.js` 的新文件。在这里,我们将定义获取出价历史的函数。 + + + +在这里,我们从 API 路由调用中检索拍卖合约 ID,然后调用 NEAR Blocks API。这个特定的 API 端点允许我们检索对特定合约调用特定函数的交易。以下几点值得讨论: + +- 我们传递拍卖合约的账户 ID,在示例仓库中为 `basic-auction-example.testnet`。 +- 我们指定拍卖合约上我们想要获取其交易的函数名称,在本例中为 `bid`。 +- 我们将收到一个包含最多 25 笔交易的 JSON 对象,按最近的优先排序。 +- 我们传递 API 密钥来验证请求。 + +--- + +## 从 API 结果中检索出价 + +从我们的 API 调用中,我们收到一个 JSON 对象,其中包含对拍卖合约 bid 函数进行的最多 25 笔交易。 + + + +我们希望显示最近的 5 个有效出价。为此,我们遍历每笔交易,通过检查 `receipt_outcome.status` 是否为 `true` 来验证交易是否成功。如果成功,我们检查第一个操作(因为在本例中应该只有一个函数调用操作),并存储 `deposit`(等于出价金额)和 `predecessor account ID`(出价者的账户 ID)。 + +一旦我们有了 5 个有效出价,就可以停止遍历交易了。 + +请注意,在我们的示例中,如果前 25 笔出价都是无效的,API 将返回一个空数组。该函数可以设置为在这种情况下再次调用 API 以获取新一页的交易。 + + +**了解更多** + +您可以在文档的以下章节中阅读更多关于交易操作的内容:[操作](/protocol/transactions/transaction-anatomy#actions) + + + +--- + +## 使用 API 路由 + +在我们的主页面中,我们将定义一个函数来调用我们刚刚创建的 API 路由。每次页面计时器归零时,将调用此函数。 + + + +然后,`pastBids` 将被传递到 `Bid` 组件以显示。 + +--- + +您可能希望进一步探索 NEAR Blocks APIs,以了解您还可以从区块链中检索哪些其他数据。您可以在以下地址找到文档:https://api.nearblocks.io/api-docs/ + +--- + +## 使用前端 + +现在我们已经实现了前端和索引器,您可以开始实际使用前端了。在前端目录的根目录中运行以下命令: + +```bash +# 安装依赖 +npm install + +# 在本地运行前端 +npm run dev +``` + +--- + +## 结论 + +在本教程的这个简短部分中,我们添加了显示对拍卖合约进行的前 5 个有效出价的功能。在此过程中,我们学习了如何与 NEAR Blocks APIs 交互以从区块链检索历史数据,以及如何在 NextJS 中进行服务器端调用以避免暴露我们的 API 密钥。现在我们有了一个相当不错的前端,显示了关于拍卖合约的所有所需信息。 + +在[教程的下一节](./3.1-nft)中,我们将通过添加 NFT 作为奖品来改进我们的合约。 diff --git a/zh/web3-apps/tutorials/mastering-near/3.1-nft.mdx b/zh/web3-apps/tutorials/mastering-near/3.1-nft.mdx new file mode 100644 index 00000000000..57d50cb41c1 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/3.1-nft.mdx @@ -0,0 +1,206 @@ +--- +title: 赢得 NFT +description: "让拍卖获胜者获得一枚 NFT。" +--- + +import { Github } from '/snippets/github.jsx' + +如果没有任何奖励,没有人会参与拍卖,所以让我们添加一个奖品。为什么不选择一个 [NFT](/primitives/nft/nft)?NFT 具有唯一可识别性,易于交换,其逻辑来自外部合约,因此奖品将独立于拍卖合约而存在。让我们开始吧! + +--- + +## 挂牌 NFT + +创建拍卖时,我们需要挂牌 NFT。要指定哪个 NFT 正在拍卖,我们需要 NFT 合约的账户 ID 和 NFT 的代币 ID。我们将在合约初始化时指定这些;修改 `init` 以添加 `nft_contract` 和 `token_id`,如下所示: + + + + + + + + + + + + + + 请注意,`token_id` 的类型为 `TokenId`,这是 NFT 标准使用的字符串类型别名,用于面向未来的设计。 + + + + + +--- + +## 将 NFT 转移给获胜者 + +当调用 `claim` 方法时,NFT 需要转移给最高出价者。关于 NFT 的操作存在于 NFT 合约上,因此我们向 NFT 合约发起跨合约调用,告诉它将 NFT 的所有者更换为最高出价者。NFT 合约上用于此操作的方法是 `nft_transfer`。 + + + + + + + + 在 near-sdk-js 中,我们不能独立地转移 NFT 和发送 `$NEAR`,所以我们将 Promise 链接在一起。 + + + + + + 我们将在源文件夹中创建一个名为 `ext.rs` 的新文件;在这里,我们将定义 `nft_transfer` 方法的接口。我们将此接口定义为一个 `trait`,并使用 `ext_contract` 宏将 NFT trait 转换为具有 `nft_transfer` 方法的模块。在单独的文件中定义外部方法有助于提高代码的可读性。 + + 然后,我们在 `lib.rs` 文件中使用此方法来转移 NFT。 + + + + + + + + +调用此方法时,我们指定 NFT 合约名称,附加 30 Tgas 到调用,附加 1 YoctoNEAR 的存款到调用,并提供参数 `receiver_id` 和 `token_id`。NFT 出于[安全原因](/smart-contracts/security/one_yocto)要求我们附加 1 YoctoNEAR。 + +--- + +## NFT 所有权问题 + +在我们的合约中,我们不进行任何检查来验证合约是否确实拥有指定的 NFT。恶意行为者可以设置一个拍卖,其中拍卖的 NFT 不属于拍卖合约,导致 `nft_transfer` 失败,获胜的出价者将损失其出价资金而一无所获。我们可以在初始化时向 NFT 合约发起跨合约调用来验证所有权,但这会变得相当复杂。相反,我们将在链下进行此检查,并在前端验证拍卖的有效性。 + +--- + +## 显示合约对象 + +由于我们现在在合约中处理更多信息,我们将创建一个函数来显示整个合约对象,而不是为每个字段实现一个显示函数。由于合约不包含像 map 这样的大型复杂数据结构,完整显示合约状态很容易实现。 + + + + + + + + + + + + + + 我们添加 `serializers` 宏以启用 json 序列化,这样整个对象就可以轻松显示给前端,而无需单独输出每个字段。 + + + + + + + +## 使用多个合约进行测试 + +在我们的测试中,我们现在将使用两个合约:拍卖合约和 NFT 合约。沙箱测试非常适合,因为它允许我们在真实环境中测试多个合约。 + +在我们的测试文件夹中,我们需要 NFT 合约的 WASM 文件。在本教程中,我们从[此仓库](https://github.com/near-examples/NFT/tree/master)编译了一个示例 NFT 合约。 + +要部署 NFT 合约,这次我们将使用 `dev deploy`,它创建一个随机 ID 的账户并通过指定 WASM 文件的路径将合约部署到该账户。部署后,我们将使用默认元数据初始化合约,并指定一个账户 ID 作为 NFT 合约的所有者(尽管 NFT 合约的所有者在本示例中无关紧要)。默认元数据将 NFT 合约的名称和符号等信息设置为默认值。 + + + + + + + + + + + + + + + + + +--- + +## 铸造 NFT + +要正式启动拍卖,拍卖合约应该拥有一个 NFT。为此,拍卖账户调用 NFT 合约铸造一个新的 NFT,提供 NFT 的图片等信息。 + + + + + + + + + + + + + + + + + +--- + +## 验证 NFT 所有权 + +调用 `claim` 后,测试应该验证拍卖获胜者现在拥有该 NFT。这通过在 NFT 合约上调用 `nft_token` 并指定代币 ID 来完成,它将返回代币所属的账户 ID。 + + + + + + + + + + + + + + + + + +--- + +## 获取 NFT + +如果您想通过 CLI 与新合约交互,可以从预部署的 NFT 合约铸造 NFT。 + +```bash +near call nft.examples.testnet nft_mint '{"token_id": "TYPE_A_UNIQUE_VALUE_HERE", "receiver_id": "", "metadata": { "title": "GO TEAM", "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "copies": 1}}' --useAccount --deposit 0.1 +``` +--- + +## 结论 + +在本教程的这一部分,我们添加了 NFT 作为奖品,这让我们学习了如何与 NFT 标准交互、进行跨合约调用,以及如何在工作空间中测试相互交互的多个合约。在[下一部分](./3.2-ft),我们将学习如何通过调整拍卖以接受 FT 出价来与同质化代币标准交互。这将允许用户以不同的代币(包括稳定币)启动拍卖。 diff --git a/zh/web3-apps/tutorials/mastering-near/3.2-ft.mdx b/zh/web3-apps/tutorials/mastering-near/3.2-ft.mdx new file mode 100644 index 00000000000..53a4c113a00 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/3.2-ft.mdx @@ -0,0 +1,413 @@ +--- +title: 使用 FT 出价 +description: "了解如何启用使用同质化代币出价" +--- + +import { Github } from '/snippets/github.jsx' + +为了进一步开发这个合约,我们将引入另一个原语:[同质化代币](/primitives/ft/ft)。出价将不再以 `$NEAR` 代币进行,而是以 FT 进行。例如,当拍卖进行时,拍卖者希望保持出价金额以美元计算不变,这时可以接受以 $USDC 等稳定币进行的出价。另一个使用场景是 Ref Finance 等项目举办自己的拍卖,并希望拍卖以其项目代币 $REF 进行。 + +--- + +## 指定 FT 合约 + +我们希望只接受一种类型的同质化代币出价;接受多种不同的 FT 将使每次出价的价值难以比较。我们还将调整合约,使拍卖者可以为拍卖指定起始出价金额。 + + + + + + + + + + + + + + + + + +--- + +## 接受 FT 出价 + +当我们以 `$NEAR` 代币出价时,我们直接调用拍卖合约并向调用附加 `$NEAR` 代币。使用同质化代币时,由于账户余额存在于单独的合约上,我们调用 FT 合约,FT 合约再调用拍卖合约并转移代币。FT 合约上用于此操作的方法名为 `ft_transfer_call`,它将始终调用目标合约中名为 `ft_on_transfer` 的方法。请查看[此处](/primitives/ft/ft#attaching-fts-to-a-call)了解更多信息。 + +![ft_transfer_call-flow](/assets/docs/web3-apps/tutorials/mastering-near/auction-ft-transfer.png) + +`ft_on_transfer` 方法始终具有相同的接口;FT 合约将向其传递 `sender`(发送者)、`amount`(正在发送的 FT 数量)和一个 `msg`(可以为空,在本例中为空),或者它可以包含该方法所需的一些信息(如果您想在 msg 中发送多个参数,最好以 JSON 形式传递,然后在合约中解析)。该方法返回要退还给用户的代币数量,在我们的情况下,我们将使用调用中附加的所有代币用于出价,除非合约发生 panic,在这种情况下,用户将自动获得其 FT 的全额退款。 + + + + + + + + + + + + + + + + + +我们需要确认用户在调用方法时正在附加同质化代币,以及他们使用的是正确的 FT,这是通过检查前驱账户的账户 ID 来完成的。由于 FT 合约是直接调用拍卖合约的,所以 `predecessor` 现在是 FT 合约的账户 ID。 + + + + + + + + + + + + + + + + + +出价者的账户 ID 现在由参数 `sender_id` 给出,出价金额作为名为 `amount` 的参数传递。 + + + + + + + + + + + + + + + + + +当我们想要将资金退还给前一个出价者时,我们现在向 FT 合约发起跨合约调用。 + + + + + + + + + 在 JavaScript 中,我们必须返回 Promise 来转移 FT,但我们也需要返回要退还给用户的金额。因此,在转移 FT 后,我们向自己的合约发起一个 `callback`(回调)以恢复合约流程。请注意,回调是私有的,只能由合约调用。我们返回 0,因为该方法使用了调用中的所有 FT。 + + + + + + + + + 然后我们返回 0,因为该方法使用了调用中的所有 FT。 + + + + + + + + 如果调用失败,FT 合约将自动退还用户的 FT。 + + + + 第一次调用此方法时,合约将尝试向自身发送 FT。大多数同质化代币合约不允许向自身发送 FT,所以跨合约调用将失败。但是,由于跨合约调用是异步且独立的,我们没有检查调用的结果,所以拍卖合约不关心调用是否失败,ft_on_transfer 将成功完成。 + + 在其他情况下,对同质化代币合约的调用只有在以下情况下才会失败:接收者不存在、FT 合约不存在、拍卖合约没有足够的同质化代币来覆盖正在发送的金额,或者接收者未在 FT 合约中注册。我们的合约设置使这些错误不会发生:接收者必须存在,因为他们提交了上一次出价;FT 合约存在,因为它被用于提交出价;拍卖合约有足够的 FT 来覆盖金额,因为它在上一次出价时收到了该金额;接收者必须在 FT 合约中注册,因为他们需要持有该代币才能出价。 + + + +--- + +## 领取 FT + +拍卖完成后,我们需要在向获胜出价者发送 NFT 的同时向拍卖者发送同质化代币,我们实现与退还资金时类似的调用,只是更改参数。 + + + + + + + + 在 JavaScript 中,由于我们需要返回每个跨合约调用,我们将 NFT 和 FT 转移链接在一起。 + + + + + + + + + + + +--- + +## 创建新 FT + +与 NFT 合约一样,我们将在沙箱测试中使用从[此仓库](https://github.com/near-examples/FT)编译的 WASM 文件部署 FT 合约。 + +部署合约时,使用 `new_default_meta` 进行初始化,该方法将代币的元数据(包括名称和符号等)设置为默认值,同时需要指定所有者(代币供应量将发送到该账户)和代币的总供应量。 + + + + + + + + + + + + + + + + + +--- + +## 在 FT 合约中注册用户 + +要接收同质化代币,首先需要在 FT 合约中[注册](/primitives/ft/ft#registering-a-user)其账户 ID。用户必须在 FT 合约中注册,以支付追踪其代币数量所需的存储费用。默认情况下,合约为自己的存储付费,但如果不要求用户注册并支付存储费用,将会耗尽合约的 `$NEAR` 代币。当合约上线时,我们不需要注册我们退还代币的账户,因为出价者首先需要已注册才能出价,但我们确实需要在 FT 合约中注册拍卖合约以接收出价,以及注册拍卖者以在拍卖结束时接收资金。从前端注册用户比从合约中注册更方便。 + +在我们的测试中,由于我们正在创建新的同质化代币和新账户,实际上必须注册每个将与 FT 交互的账户。 + + + + + + + + + + + + + + + + + +--- + +## 简单 FT 转账给出价者 + +然后,我们将 FT 转账给出价者,以便他们可以用于出价。简单的 FT 转账使用 FT 合约上的 `ft_transfer` 方法完成。 + + + + + + + + + + + + + + + + + + +--- + +## FT 转账调用 + +如前所述,要在拍卖中出价,出价者现在调用 FT 合约上的 `ft_transfer_call`,FT 合约随后调用拍卖合约的 `ft_on_transfer` 方法并附上同质化代币。 + + + + + + + + + + + + + + + + + + +--- + +## 检查用户的 FT 余额 + +以前,要检查用户的 `$NEAR` 余额,我们从其账户中获取详情。现在我们使用 FT,我们在 FT 合约上使用 `ft_balance_of` 查询余额,让我们检查合约的余额是否增加了出价金额,以及用户的余额是否减少了出价金额。 + + + + + + + + + + + + + + + + + + +--- + +## 无效 FT 转账调用 + +如果我们的出价低于前一次,这将导致拍卖合约 panic。人们可能期望 `ft_transfer_call` 会失败,但实际上不会。`ft_on_transfer` 会失败,FT 合约会识别到这一点并撤销代币转移。因此,进行无效出价后,我们应该检查调用是否成功,但参与交易的各方(出价者和合约)的同质化代币余额与调用前相同。 + +在此之前,Bob 出价 60,000,Alice 的出价被退还,使其余额恢复到 150,000。现在当 Alice 进行 50,000 的无效出价时,Alice 的余额应该保持在 150,000,合约应该保持 60,000 的余额。 + + + + + + + + + + + + + + + + + +--- + +## 使用 CLI 操作 FT + +如果您想与拍卖合约交互,您将需要 FT。在本例中,我们将使用 $DAI,其合约地址为 `dai.fakes.testnet`。可以通过[测试网水龙头](https://near-faucet.io/)轻松获取 FT。选择 DAI 并提现到您将用于出价的账户。如果您查看交易详情,可以看到水龙头在 FT 合约中注册您的账户,然后从水龙头账户向您发送 DAI。 + +部署合约时,请确保指定 FT 合约 `dai.fakes.testnet`。 + +拍卖合约也需要注册,您可以通过从水龙头向其发送任意数量的 $DAI 来完成,或者直接注册,因为它不需要任何 FT。您还应该注册拍卖者, + +```bash +near call dai.fakes.testnet storage_deposit '{"account_id": ""}' --useAccount --deposit 0.1 +``` + +现在您可以进行出价了。DAI 有 18 位小数,这意味着 1 $DAI 由 10^18 个最小单位组成。要出价 2 $DAI,您可以使用以下命令: + +```bash +near call dai.fakes.testnet ft_transfer_call '{"receiver_id": "", "amount": "2000000000000000000", "msg": ""}' --useAccount --depositYocto 1 +``` + +## 拍卖架构 + +在创建应用程序时,有许多方式可以组织结构。在这里,我们每次拍卖使用一个合约,这意味着我们每次举办新拍卖时都必须部署一个新合约。为了使这更容易,我们将利用工厂合约来为拍卖者部署拍卖合约。为每次拍卖部署代码变得昂贵,因为 NEAR 上 100kb 存储费用为 1 `$NEAR`,由于每次拍卖存储相同类型的信息并实现相同的方法,人们可以改为决定在每个合约中包含多次拍卖。 + +在这种情况下,合约结构体将是一个拍卖的映射。我们将实现一个方法,通过向映射中添加具有该特定拍卖详情的条目来创建新拍卖。 + + + + + + ```javascript + class Contract { + auctions: UnorderedMap + ``` + + + + + + ```rust + pub struct Contract { + auctions: IterableMap + ``` + + + + + +然而,这种架构可能被认为安全性较低,因为如果恶意行为者获得了对合约的访问权,他们将可以访问每次拍卖,而不仅仅是一次。 + +--- + +## 结论 + +在本节中,您学习了很多关于同质化代币的知识:如何在智能合约中发送和接收 FT,然后在沙箱测试中如何部署和初始化 FT 合约、如何在 FT 合约中注册用户并向其发送一些代币、如何将 FT 附加到智能合约调用,以及最后如何查看用户的 FT 余额。至此,我们的完整拍卖智能合约已经完成! + +从更宏观的角度来看,我们从一个非常简单的拍卖合约开始,并将其转变为一个具有全面测试的更成熟的生产合约。为了改进拍卖,我们学习了如何通过引入 NFT 添加奖品,并使拍卖者能够以 FT 举办拍卖。 + +在[教程的下一部分](./3.3-new-frontend),我们将更新前端以与合约的新功能交互。 diff --git a/zh/web3-apps/tutorials/mastering-near/3.3-new-frontend.mdx b/zh/web3-apps/tutorials/mastering-near/3.3-new-frontend.mdx new file mode 100644 index 00000000000..82868c370c5 --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/3.3-new-frontend.mdx @@ -0,0 +1,84 @@ +--- +title: 更新前端 +description: "更新前端以显示新的代币信息。" +--- + +import { Github } from '/snippets/github.jsx' + +现在我们已经更新了合约以将 NFT 作为奖励,并将合约更改为接受以同质化代币进行的出价,我们需要相应地更新前端。 + +## 从合约获取数据 + +现在我们有了一个用于输出整个合约状态的函数,我们将在前端调用此函数。 + + + +这个调用将为我们提供 FT 和 NFT 合约的合约 ID,以及 NFT 的代币 ID。然后,我们将使用这些信息分别在 FT 和 NFT 合约上调用 `ft_metadata` 和 `nft_token` 方法,以获取 FT 和 NFT 的相关信息。 + +--- + +## 显示 NFT + +我们希望显示正在拍卖的 NFT。为此,我们将在 NFT 合约上调用 `nft_token` 来获取 NFT 元数据。要调用此方法,我们需要指定 NFT `contractId` 和 `token_id`,这些信息可以在拍卖信息中找到。`nft_token` 还返回 NFT 的所有者,因此我们将其与合约账户进行比较,以验证拍卖的有效性。 + + + +请注意,此 effect 只会在 `auctionInfo` 更新后运行,因为我们首先需要从 `auctionInfo` 中获取 NFT 合约 ID 和代币 ID,才能向 `nft_token` 发起有效调用。 + +在一个名为 `AuctionItem` 的新组件中,我们显示 NFT 图片、名称和描述。 + + + +请注意,使用了图片缓存服务来显示 NFT 图片,以提高性能。 + +--- + +## 获取 FT 信息 + +使用拍卖信息中的 FT 合约 ID,我们可以在 FT 合约上调用 `ft_metadata` 方法,以获取正在用于拍卖的同质化代币的相关信息。 + + + +我们在状态中设置 FT 图片、符号、图标和精度。我们使用精度来格式化出价的代币数量。以 DAI 为例,将金额除以 10^18。进行出价时使用相反的过程,出价金额在发送到合约之前乘以 10^18。 + +--- + +## 使用 FT 出价 + +我们现在不再调用合约上的 `bid` 函数,而是在 FT 合约上调用 `ft_transfer_call` 函数。此函数将 FT 转移到拍卖合约,并在拍卖合约上调用 `ft_on_transfer`。 + + + +--- + +## 更新索引 API 调用 + +我们需要更新获取历史出价的 API 调用,以索引每次从 FT 合约调用拍卖合约的 `ft_on_transfer` 的情况。 + + + +现在,出价金额不再从存款中获取,而是从调用参数的 `amount` 中获取。出价者的账户 ID 情况相同,从 `sender_id` 中获取。 + + + +--- + +## 结论 + +好的,这没花多长时间。回顾一下,我们更新了前端以显示正在拍卖的 NFT,以正在使用的 FT 显示出价金额——包括当前出价和历史出价——并将出价流程更改为使用 FT。 + +在这个超级教程的[最后一节](./4-factory)中,我们将创建一个拍卖工厂合约,用于部署和初始化新的拍卖合约。 diff --git a/zh/web3-apps/tutorials/mastering-near/4-factory.mdx b/zh/web3-apps/tutorials/mastering-near/4-factory.mdx new file mode 100644 index 00000000000..d3681e43a7a --- /dev/null +++ b/zh/web3-apps/tutorials/mastering-near/4-factory.mdx @@ -0,0 +1,89 @@ +--- +title: 拍卖工厂 +sidebarTitle: 拍卖工厂 +description: "通过工厂创建新拍卖。" +--- + +import { Github } from '/snippets/github.jsx' + +由于拍卖合约每次只举办一场拍卖,因此每次您想举办新拍卖时,都需要部署一个新合约。为了避免每次都需要查找编译后的 WASM 文件、创建新账户、部署合约并初始化它,您可以使用工厂合约来为您完成这些工作。 + +幸运的是,我们已经有一个[工厂合约示例](https://github.com/near-examples/factory-rust)!我们将 fork 这个示例并对其进行少量修改以适应我们的使用场景。如果您想了解更多关于工厂合约工作原理的信息,可以查看[相关文档](/smart-contracts/tutorials/factories/factory#generic-factory)。 + +工厂示例目前仅提供 Rust 版本,因为目前 JavaScript SDK 不允许在合约中嵌入 WASM 文件。这是 SDK 的限制,而非区块链本身的限制。 + +--- + +## 更改默认合约 + +在当前示例中,工厂合约部署的是捐款合约示例。我们将把它改为部署我们的拍卖合约。 + +首先,我们需要编译后的拍卖合约 WASM 文件。您可以在 `contract-rs` 的 [03-bid-with-fts](https://github.com/near-examples/auctions-tutorial/tree/main/contract-rs/03-bid-with-fts) 中运行以下命令来获取它: + +```bash +cargo near build +``` + +您将在 `target/near` 中找到生成的 WASM 文件;复制此文件并用它替换工厂合约源文件夹中捐款合约的 WASM。现在编辑拍卖合约,更改拍卖合约的路径。 + + + + +初始化时,工厂将把拍卖合约的 WASM(以字节形式)添加到工厂状态中。不将 WASM 存储在工厂状态中效率更高,但是,如果我们发现了一个 bug 或想要添加新功能,我们可能需要更新拍卖合约。工厂实现了一个更新拍卖合约的方法——我们将其名称更改为 `update_auction_contract`,因为此工厂只部署拍卖合约。 + + + +--- + +## 修改部署方法 + +部署新合约的方法与被部署的合约相关(如果合约具有自定义初始化参数)。我们将修改该方法以接受拍卖合约的初始化参数。 + + + +在此 fork 中,我们还移除了向合约账户添加访问密钥的选项,因为如[前面](./1.3-deploy#locking-the-contract)所述,我们希望拍卖处于锁定状态。 + +--- + +## 使用工厂 + +像其他合约一样构建和部署工厂,这次没有任何初始化参数。 + +```bash +# 使用 cargo-near 编译合约 +cargo near build + +# 部署合约 +near deploy ./target/near/contract.wasm +``` + +您现在可以使用工厂部署新的拍卖合约,以下是一个示例命令。 + +```bash +near call auction-factory.testnet deploy_new_auction '{"name": "new-auction", "end_time": "3000000000000000000", "auctioneer": "pivortex.testnet", "ft_contract": "dai.fakes.testnet", "nft_contract": "nft.examples.testnet", "token_id": "7777", "starting_price": "1000000000000000000"}' --useAccount pivortex.testnet --deposit 1.6 --gas 100000000000000 +``` + + +**存款和存储成本** +请注意,我们向调用附加 1.6 `$NEAR` 以覆盖部署新拍卖的存储成本。NEAR 上的存储成本为每 100 kb 1 `$NEAR`,我们的拍卖合约大约为 140 kb,但我们会多加一些以覆盖初始化时使用的存储。 + + +该命令的结果是一个新的拍卖合约被部署并初始化在 `new-auction.auction-factory.testnet`。 + +--- + +## 结论 + +在本教程的这一部分,您学习了如何 fork 和修改工厂合约示例来部署我们的拍卖合约。您还学习了如何使用工厂来部署新的拍卖合约。如果您喜欢挑战,可以创建一个与工厂合约交互的前端,使部署新拍卖更加容易。如果您这样做了,欢迎在我们的开发者 [Telegram](https://t.me/neardev) 或 [Discord](https://discord.gg/vMGH5QywTH) 频道分享! + +至此,本教程系列结束,恭喜您!在本教程中,我们构建了一个拍卖合约并对其进行了迭代,添加了改进并扩展了其功能;创建了一个与拍卖交互的前端;使用 API 对历史出价进行索引;并部署了一个工厂合约,使部署新拍卖变得更容易。在这个过程中,我们学到了大量关于 NEAR 的知识:智能合约的结构、如何锁定合约以提高其安全性、如何使用 NFT 和 FT 等原语、如何进行跨合约调用、如何从前端使用钱包与区块链交互并显示智能合约的数据、如何使用 API 从区块链中获取历史数据、如何从其他合约部署合约,以及许多其他将对您未来有所帮助的知识。 + +这真是太多了,再次恭喜您! diff --git a/zh/web3-apps/tutorials/meta-transactions.mdx b/zh/web3-apps/tutorials/meta-transactions.mdx new file mode 100644 index 00000000000..fcd753b7177 --- /dev/null +++ b/zh/web3-apps/tutorials/meta-transactions.mdx @@ -0,0 +1,300 @@ +--- +title: 构建元交易中继器 +sidebarTitle: 构建中继器 +description: "了解如何构建元交易中继器,让用户无需支付 Gas 费即可在 NEAR 上进行交易,同时通过签名委托保障交易安全。" +--- + +import { Github } from '/snippets/github.jsx' + +中继器的作用是将 Gas 费委托给 Web 服务,允许用户在无需自行获取代币的情况下在 NEAR 上进行交易,同时仍保留自行签署交易的安全性。本指南将引导您了解构建能够处理元交易的中继器所需的各个组件。 + + + +如果您已经熟悉该技术,只想运行自己的中继器,可以直接跳转到完整的开源 [Rust 中继器服务器](#rust-relayer-server)实现。 + + + +## 工作原理 + +一个基本的中继器由一个托管有已充值 NEAR 账户的 Web 服务器组成。该账户接收编码后的已签名交易,随后可以将其解码为 `SignedDelegate` 格式并在链上广播。 + +客户端随后可以生成一个 `SignedDelegateAction`(一条尚未发送的已签名消息),对其进行编码并传输到该服务器,在那里它将被中继至区块链。 + +![relayer-overview-technical](/assets/docs/web3-apps/tutorials/meta-transactions/relayer-overview-technical.png) + +## 中继器(服务器端) + + + + + +以下是一个简单的 Express 端点示例,它对请求体进行反序列化,实例化中继器账户,然后发送交易。 + + + +您可以使用此代码片段,通过私钥轻松获取用于发送交易的账户对象。 + + + + + + 示例中的代码仅适用于以下版本及以上: + +```text +"near-api-js": "3.0.4" +"@near-js/transactions": "1.1.2", +"@near-js/accounts": "1.0.4" +``` + + + + + + + +`@near-relay` 简化了元交易,使初学者更容易上手。 + +首先,在端点内调用 relay 方法,以自动反序列化交易并使用环境变量中定义的账户发送。 + + + +如果您还希望中继账户创建功能,这也相当简单。只需创建另一个端点并直接使用 accountId 和 publicKey 调用 createAccount 方法。使用对应的客户端库时,这些参数会自动包含在请求体中。 + + + + + + + +## 客户端 + + + + + +在此方法中,我们创建一个任意的智能合约调用,实例化一个账户,并使用它来签署但不发送交易。然后我们可以将其序列化并发送给中继器,在那里它将通过之前创建的端点被委托至区块链。 + + + + + + + +如上述说明所述,为了能够在客户端进行中继,需要能够直接在客户端签署交易。幸运的是,借助 NEAR 生物识别库,可以以非托管方式实现这一点。 + +通过调用此方法并传入账户创建端点的 URL(在服务器端部分提到)以及 `accountId`,所有事情都将在底层自动处理,成功创建账户。 + + + +在客户端,您只需创建一个 `Action` 并将其与服务器端讨论的中继器端点 URL 以及 `receiverId` 的 ID 一起传入 `relayTransaction` 方法。 + + + + + + + + + +目前,钱包选择器标准不支持在不立即发送交易的情况下签署交易。这一功能对于在客户端将交易路由到中继器至关重要。因此,为了在客户端顺畅集成中继,必须能够在不依赖钱包的情况下签署交易。 +目前正在努力在未来实现这一功能。 + + + +### 高并发并行处理 + +当运行处理大量交易的中继器时,您将很快遇到 `nonce` 冲突问题。在协议层面,交易有一个唯一编号来标识它们(nonce),有助于缓解重放攻击。账户上的每个密钥都有自己的 nonce,并且 nonce 预期随着密钥的每次签名而递增。 + +当同一访问密钥同时发送多笔交易时,它们的 nonce 可能发生冲突。设想中继器创建了 3 笔交易 `Tx1`、`Tx2`、`Tx3`,并在极短的时间间隔内发送它们,假设 `Tx3` 具有最大的 nonce。如果 `Tx3` 在 `Tx1` 之前被处理(由于网络延迟,或某个节点先选取了 `Tx3`),那么 `Tx3` 将执行,但 `Tx1` 和 `Tx2` 将失败,因为它们具有更小的 nonce! + +缓解此问题的一种方法是使用不同的密钥签署每笔交易。向用于中继的 NEAR 账户添加多个全访问密钥(最多 20 个密钥可以产生显著效果)将有所帮助。 + + + +```js +const keyPair = nearAPI.KeyPair.fromRandom("ed25519"); + +const receipt = await account.addKey(keyPair.getPublicKey().toString()) +``` + +保存这些密钥后,可以在中继前实例化账户时随机轮换私钥,从而确保不会产生 nonce 冲突。 + + + +### 限制中继器访问 + +在大多数生产应用中,预计您希望能够限制中继器,使其仅在特定情况下使用。 +这可以通过在 `SignedDelegate.delegateAction` 对象中指定约束来轻松实现。 + +```typescript +export declare class DelegateAction extends Assignable { + senderId: string; + receiverId: string; + actions: Array; + nonce: BN; + maxBlockHeight: BN; + publicKey: PublicKey; +} +``` + +例如,您可以通过特定用户或合约进行限制: + +```typescript + const serializedTx: Buffer = req.body; + const deserializedTx: SignedDelegate = deserialize(SCHEMA.SignedDelegate, Buffer.from(serializedTx)) as SignedDelegate; + const relayerAccount: Account = await getAccount(NETWORK_ID, RELAYER_ID, RELAYER_PRIVATE_KEY); + const delegateAction = deserializedTx?.delegateAction + + if(delegateAction.senderId == 'someUserId' || delegateAction.receiverId == 'someContractId' ){ + const receipt = await relayerAccount.signAndSendTransaction({ + actions: [actionCreators.signedDelegate(deserializedTx)], + receiverId: deserializedTx.delegateAction.senderId + }); + } +``` + +其他示例还包括检查操作,看是否有存款或 Gas 并对其进行限制,通过特定的智能合约方法甚至参数进行限制。 + +您可以使用以下方式解码参数: + +```text +JSON.parse(Buffer.from(args_base64 || "", "base64").toString()) +``` + +--- + +## Rust 中继器服务器 + +开源的 Rust [中继器服务器参考实现](https://github.com/near/pagoda-relayer-rs/)提供以下功能: + + +各功能可以按需组合使用。使用某一功能不会排除使用其他任何功能,除非有特别说明。 + + +1. 签署并发送元交易到 RPC 以覆盖最终用户的 Gas 费,同时允许他们保管自己的资金并批准交易(`/relay`、`/send_meta_tx`、`/send_meta_tx_async`、`/send_meta_tx_nopoll`) +2. 签署元交易并返回已签名的元交易,稍后发送到 RPC——(`/sign_meta_tx`、`/sign_meta_tx_no_filter`) +3. 通过将合约地址列入白名单,仅为与特定合约交互的用户支付费用(`config.toml` 中的 `whitelisted_contracts`) +4. 为所有账户(`/update_all_allowances`)或按用户账户(`/create_account_atomic`、`/register_account`、`/update_allowance`)指定 Gas 费用额度,并跟踪额度(`/get_allowance`) +5. 指定中继器将为其承担 Gas 费的账户(`config.toml` 中的 `whitelisted_delegate_action_receiver_ids`) +6. 仅允许拥有唯一 Oauth Token 的用户注册(`/create_account_atomic`、`/register_account`) +7. 中继器密钥轮换:`config.toml` 中的 `keys_filenames` +8. 与 [FastAuth SDK](/chain-abstraction/fastauth-sdk) 集成 +9. 混合和匹配配置选项 + + +查看[使用案例部分](#use-cases),了解与不同使用场景对应的示例配置文件。 + + +### 基本设置 + +您可以按照以下步骤设置本地中继器服务器开发环境: + +1. [为 NEAR 开发安装 Rust](/smart-contracts/quickstart#prerequisites) +2. 如果您没有 NEAR 账户,请[创建一个](/protocol/accounts-contracts/account-model) +3. 使用步骤 2 中的账户,在此目录中创建以下格式的 JSON 文件: + ```js + [{"account_id":"example.testnet","public_key":"ed25519:98GtfFzez3opomVpwa7i4m3nptHtc7Ha514XHMWszLtQ","private_key":"ed25519:YWuyKVQHE3rJQYRC3pRGV56o1qEtA1PnMYPDEtroc5kX4A4mWrJwF7XkzGe7JWNMABbtY4XFDBJEzgLyfPkwpzC"}] + ``` + 使用来自有足够 NEAR 余额以覆盖服务器将中继的交易 Gas 费的账户的[全访问密钥](/protocol/accounts-contracts/access-keys#full-access-keys)。通常,这将是 `.near-credentials` 目录中找到的 JSON 文件的副本。 +4. 更新 `config.toml` 中的值 +5. 在您机器的网络设置中开放 `config.toml` 中的 `port` 端口 +6. 使用 `cargo run` 运行服务器。 + > **(可选)** 要在启用日志(tracing)的情况下运行,请执行 `RUST_LOG=tower_http=debug cargo run` + + +可选设置 + +如果您要与 [FastAuth](/chain-abstraction/fastauth-sdk) 集成,请确保启用功能标志: +```text +cargo build --features fastauth_features,shared_storage +``` +如果您使用共享存储,请确保启用功能标志: +```text +cargo build --features shared_storage +``` + + + +### Redis 设置 + + +仅当您打算使用白名单、额度和 OAuth 功能时才需要此步骤。 + + +1. [安装 Redis](https://redis.io/docs/latest/get-started/)。 + > 步骤 2 和 3 假设 Redis 安装在机器上而非 Docker 设置中。如果您要连接在 GCP 中运行的 Redis 实例,请按照上述步骤连接到一台 VM,该 VM 将从本地中继器服务器转发请求到 [GCP 中运行的 Redis](https://docs.cloud.google.com/memorystore/docs/redis/connect-redis-instance#connect_from_a_local_machine_by_using_port_forwarding)。 +2. 运行 `redis-server --bind 127.0.0.1 --port 6379`——确保端口与 `config.toml` 中的 `redis_url` 匹配。 +3. 运行 `redis-cli -h 127.0.0.1 -p 6379` + +### 高级设置 + +- [多密钥生成](https://github.com/near/pagoda-relayer-rs/tree/main?tab=readme-ov-file#multiple-key-generation---optional-but-recommended-for-high-throughput-to-prevent-nonce-race-conditions):这是可选的,但推荐用于高吞吐量以防止 nonce 竞争条件。 +- [Docker 部署](https://github.com/near/pagoda-relayer-rs/tree/main?tab=readme-ov-file#docker-deployment):使用 Docker 部署的说明 +- [云部署](https://github.com/near/pagoda-relayer-rs/tree/main?tab=readme-ov-file#cloud-deployment):在云服务提供商上部署的说明 + +### API 规范 + +您可以在 [GitHub 存储库](https://github.com/near/pagoda-relayer-rs/tree/main?tab=readme-ov-file#api-spec-)中找到完整的中继器服务器 API 规范。 + +### 使用案例 + +GitHub 存储库中的[示例文件夹](https://github.com/near/pagoda-relayer-rs/tree/main/examples)包含与不同使用案例对应的示例配置文件。 + + +这些文件仅供参考,在您的开发环境中使用之前,应更新 `config.toml` 中的值。 + + +#### 无过滤器 + +这是一个中继器配置,覆盖所有用户对所有合约的 Gas 费,不设过滤器。为防止滥用,仅应在只有安全后端调用中继器的情况下使用此配置。 +- [`no_filters.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/no_filters.toml) + +#### 基本白名单 + +这是一个基本中继器配置,覆盖用户与白名单合约集交互的 Gas 费。 +- [`basic_whitelist.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/basic_whitelist.toml) + +#### Redis + +这是一个中继器配置,覆盖用户与白名单合约集交互的 Gas 费,额度在 Redis 中指定。 +- 额度基于账户 ID,注册时(在 Redis 和链上创建账户)需要 OAuth 令牌以帮助防止女巫攻击。 +- [`redis.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/redis.toml) + +#### FastAuth + + +**封闭访问测试版** + +[FastAuth](/chain-abstraction/fastauth-sdk) 目前处于私有测试阶段。如果您想在这个早期开发阶段试用,请[通过 Telegram 联系我们](https://t.me/neardev)。 + + + +这是一个如果您打算与 [FastAuth SDK](/chain-abstraction/fastauth-sdk) 集成时使用的配置。 +- 它覆盖用户与白名单合约集交互的 Gas 费,额度在 Redis 中指定。 +- 额度基于账户 ID,注册时(在 Redis 和链上创建账户)需要 OAuth 令牌以帮助防止女巫攻击。 +- 这还利用了 Near Social DB 合约上的共享存储功能 +- 以及白名单发送者(`whitelisted_delegate_action_receiver_ids`) +- [`fastauth.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/fastauth.toml) + +#### 使用同质化代币支付 + +这是一个中继器配置,确保将 FT 发送到销毁地址,用于覆盖用户与白名单合约集交互的等值 Gas 费。 +- [`pay_with_ft.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/pay_with_ft.toml) + +#### 发送者白名单 + +这是一个中继器配置,覆盖白名单用户与白名单合约集交互的 Gas 费。 +- [`whitelist_senders.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/whitelist_senders.toml)(`whitelisted_delegate_action_receiver_ids`) + +#### 共享存储 + +这是一个中继器配置,同时覆盖用户与白名单合约集交互的 Gas 费和存储费。 + +- 请确保在您被列入白名单的合约中包含基于 [`shared_storage.rs`](https://github.com/NearSocial/social-db/blob/master/contract/src/shared_storage.rs) 的共享存储逻辑 +- [`shared_storage.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/shared_storage.toml) + +#### 交易所提款 + +这是一个中继器配置,适用于运行中继器的交易所在用户提取 NEAR 上的稳定币(例如 `USDT` 或 `USDC`)时覆盖用户的提款费用。 + +- [`exchange_withdraw.toml`](https://github.com/near/pagoda-relayer-rs/blob/main/examples/configs/exchange_withdraw.toml) diff --git a/zh/web3-apps/tutorials/wallet-login.mdx b/zh/web3-apps/tutorials/wallet-login.mdx new file mode 100644 index 00000000000..83b6d8c4c82 --- /dev/null +++ b/zh/web3-apps/tutorials/wallet-login.mdx @@ -0,0 +1,378 @@ +--- +title: 钱包登录 +description: "使用安全的沙箱连接器库将用户连接到 NEAR 钱包" +--- + +import { Github } from '/snippets/github.jsx' + +`@hot-labs/near-connect` 库为 NEAR 区块链提供了一个安全的零依赖钱包连接器,采用独特的基于沙箱的架构。 + +![Preview](https://github.com/user-attachments/assets/c4422057-38bb-4cd9-8bd0-568e29f46280) + + + +**示例** + +我们有一个[工作示例](https://github.com/near-examples/hello-near-connector),您可以通过 `create-near-app` 轻松获取: + +```bash +npx create-near-app@latest +``` + + + +--- + +## 为什么选择 NEAR Connect? + +与传统钱包选择器不同,它提供了一个动态清单系统,允许在不要求开发者更新依赖项的情况下添加和更新钱包。 + +- **安全执行**:钱包脚本在隔离的沙箱 iframe 中运行,确保最高安全性 +- **动态钱包**:钱包从清单中加载,无需代码更改即可更新 +- **零依赖**:轻量级库,无外部依赖 +- **自动检测**:同时支持注入式钱包(浏览器扩展)和基于清单的钱包 + +--- + +## 安装 + +您可以通过两种方式将 NEAR Connect 库添加到您的项目中: + + + + 手动安装 `@hot-labs/near-connect` 和 `near-api-js` 包: + + ```bash + npm install @hot-labs/near-connect near-api-js + ``` + + + + 如果您使用 React,我们建议安装 `near-connect-hooks` 包,它提供了方便的 hooks 用于将 NEAR Connect 集成到您的应用中: + + ```bash + npm install near-connect-hooks + ``` + + + + +--- + +## 初始化连接器 + +在您的应用程序中初始化 `NearConnector` 实例: + + + + + + + + + + + + +与捆绑钱包代码的传统钱包选择器不同,NEAR Connect 使用**基于清单的方式**: + +1. 钱包提供商在公共清单中注册其集成脚本 +2. 当用户希望连接时,连接器动态加载钱包脚本 + +这种架构消除了安装单独钱包包的需要,并确保钱包代码可以独立于您的应用进行更新。 + +```tsx +connector = new NearConnector({ + network: "testnet", // or "mainnet" + features: { + signMessage: true, // Only show wallets that support message signing + signTransaction: true, + signInWithoutAddKey: true, + signAndSendTransaction: true, + signAndSendTransactions: true + }, +}); +``` + + + +--- + +## 登录 / 注销 + + + + 连接器使用观察者模式(发布/订阅),为此我们需要订阅 `wallet:signIn` 和 `wallet:signOut` 事件。 + + + + 然后,调用 `connect` 函数打开一个模态窗口,用户可以在其中选择钱包并登录;调用 `disconnect` 进行注销。 + + + + + + `near-connect-hooks` 包提供了一个 `useNearWallet` hook,简化了登录流程,首先导入该 hook: + + + 然后,从 hook 中导入 `signIn` 和 `signOut` 方法,并在需要时调用它们: + + + + +--- + +## 调用合约方法 + + + + 要调用合约方法,首先使用 `connector.wallet()` 获取已连接的钱包实例,然后使用钱包的 `signAndSendTransaction` 方法进行函数调用: + + ```tsx + // 获取已连接的钱包 + const wallet = await connector.wallet(); + + // 调用写入方法 + const result = await wallet.signAndSendTransaction({ + receiverId: "hello.near-examples.testnet", + actions: [ + { + type: "FunctionCall", + params: { + methodName: "set_greeting", + args: { greeting: "Hello from NEAR Connect!" }, + gas: "30000000000000", // 30 TGas + deposit: "0", // No deposit + }, + }, + ], + }); + + console.log("Transaction:", result.transaction.hash); + ``` + + + + 要调用合约方法,您可以使用 `useNearWallet` hook 提供的 `callFunction` 方法: + + + + +--- + +## 调用只读方法 + + + + + `near-connector` 没有提供内置方式来调用只读(视图)方法。 + + 但是,您可以使用 `near-api-js` 包(或您首选的任何 API)创建 JSON-RPC 提供商并直接调用视图方法: + + ```tsx + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const greeting = await provider.callFunction( + "hello.near-examples.testnet", + "get_greeting", + {} + ); + ``` + + + 您可以使用 `useNearWallet` hook 提供的 `viewFunction` 方法来调用合约上的只读方法: + + + + + + +--- + +## 获取余额 + + + + + `near-connector` 没有提供内置方式来获取账户余额。 + + 但是,您可以使用 `near-api-js` 包(或您首选的任何 API)创建 JSON-RPC 提供商并直接查询账户余额: + + ```tsx + import { JsonRpcProvider } from "near-api-js"; + + const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + + const greeting = await provider.viewAccount({ accountId: "hello.near-examples.testnet" }); + ``` + + + 您可以使用 `useNearWallet` hook 提供的 `getBalance` 方法来调用合约上的只读方法: + + ```jsx + import { useNearWallet } from 'near-connect-hooks'; + import { yoctoToNear } from 'near-api-js'; + + const { getBalance } = useNearWallet(); + const balance = await getBalance(); + console.log(`Balance: ${yoctoToNear(balance.available, 2)} NEAR`); + ``` + + + + +--- + +## 发送多笔交易 + + + + + 您可以通过单次提示请求用户并行签署和发送多笔交易: + + ```tsx + const wallet = await connector.wallet(); + + const results = await wallet.signAndSendTransactions({ + transactions: [ + { + receiverId: "token.near", + actions: [ + { + type: "FunctionCall", + params: { + methodName: "ft_transfer", + args: { + receiver_id: "alice.near", + amount: "1000000", + }, + gas: "30000000000000", + deposit: "1", // 1 yoctoNEAR for security + }, + }, + ], + }, + { + receiverId: "nft.near", + actions: [ + { + type: "FunctionCall", + params: { + methodName: "nft_mint", + args: { + token_id: "token-1", + receiver_id: "alice.near", + }, + gas: "30000000000000", + deposit: "10000000000000000000000", // 0.01 NEAR + }, + }, + ], + }, + ], + }); + + console.log(`Completed ${results.length} transactions`); + ``` + + + 您可以使用 `useNearWallet` hook 提供的 `signAndSendTransactions` 方法在单次请求中发送多笔交易: + + ```tsx + import { useNearWallet } from 'near-connect-hooks'; + + ... + + const { signAndSendTransactions } = useNearWallet(); + + const results = await signAndSendTransactions({ + transactions: [ + { + receiverId: "token.near", + actions: [ + { + type: "FunctionCall", + params: { + methodName: "ft_transfer", + args: { + receiver_id: "alice.near", + amount: "1000000", + }, + gas: "30000000000000", + deposit: "1", // 1 yoctoNEAR for security + }, + }, + ], + }, + { + receiverId: "nft.near", + actions: [ + { + type: "FunctionCall", + params: { + methodName: "nft_mint", + args: { + token_id: "token-1", + receiver_id: "alice.near", + }, + gas: "30000000000000", + deposit: "10000000000000000000000", // 0.01 NEAR + }, + }, + ], + }, + ], + }); + console.log(`Completed ${results.length} transactions`); + ``` + + + +--- + +### 签署消息(NEP-413) + +在 NEAR 中,用户无需发送交易即可为身份验证目的签署消息: + + + + 您可以使用钱包的 `signMessage` 方法请求用户签署消息: + + ```tsx + const wallet = await connector.wallet(); + + const signature = await wallet.signMessage({ + message: "Please sign this message to authenticate", + recipient: "your-app.near", + nonce: Buffer.from(crypto.randomUUID()), + }); + + console.log("Signature:", signature.signature); + console.log("Public Key:", signature.publicKey); + + // 在后端验证签名 + ``` + + + 您可以使用 `useNearWallet` hook 提供的 `signMessage` 方法请求用户签署消息: + + ```tsx + import { useNearWallet } from 'near-connect-hooks'; + + ... + + const { signNEP413Message } = useNearWallet(); + + const signature = await signMessage({ + message: "Please sign this message to authenticate", + recipient: "your-app.near", + nonce: Buffer.from(crypto.randomUUID()), + }); + + console.log("Signature:", signature.signature); + console.log("Public Key:", signature.publicKey); + ``` + + diff --git a/zh/web3-apps/what-is.mdx b/zh/web3-apps/what-is.mdx new file mode 100644 index 00000000000..dff918c2753 --- /dev/null +++ b/zh/web3-apps/what-is.mdx @@ -0,0 +1,24 @@ +--- +title: 什么是 Web3 应用? +sidebarTitle: 简介 +description: "了解 Web3 应用程序(去中心化应用 dApp),它们利用智能合约和区块链数据,提供透明性、安全性,并将资产和数据的控制权归还给用户。" +--- + +Web3 应用程序——也被称为去中心化应用(dApp)——利用[智能合约](../smart-contracts/what-is)和区块链数据,提供**透明性**、**安全性**,并将用户对其资产和数据的**控制权归还**给用户。 + +![img](/assets/docs/welcome-pages/3.web3-apps.webp) + +NEAR 简化了为大众构建 Web3 应用的流程,使**与不同区块链的交互**变得简单,同时帮助**引导那些尚不熟悉加密货币的用户**。 + +--- + +### 为什么将 NEAR 协议集成到您的应用中? +任何应用程序都可以从集成 NEAR 中获益,包括游戏、金融服务、社交平台等。 + +- **轻松引导**:用户可以使用熟悉的方式创建账户,例如邮箱登录。此外,应用程序可以为用户承担所有交易费用,让用户无需担心处理加密货币。 + +- **所有权**:用户对其账户内的数字资产拥有真正的所有权。同质化代币可用作奖励系统,非同质化代币可代表持仓,钱包可代表数字身份。 + +- **快速、低价且可扩展**:NEAR 高效的共识机制和费用模型使交易对用户和开发者而言都具有成本效益。 + +- **安全性与透明性**:区块链上的所有交易和数据都是透明且可审计的,从而确保了对应用程序行为的信任。