diff --git a/docs/tutorials/examples/donation.md b/docs/tutorials/examples/donation.md index d3e32f3f15f..d4e24d9dce2 100644 --- a/docs/tutorials/examples/donation.md +++ b/docs/tutorials/examples/donation.md @@ -1,346 +1,98 @@ --- -id: donation -title: Donation -description: "Build a donation smart contract that accepts NEAR tokens, tracks donations, and distributes funds to beneficiaries with transparent accounting." +id: handling-tokens +title: How to Handle Tokens in a Smart Contract +description: "Learn how to manage native NEAR tokens in smart contracts, including receiving, tracking, and transferring them, illustrated with a donation example." --- import {CodeTabs, Language, Github} from '@site/src/components/codetabs'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import MovingForwardSupportSection from '@site/src/components/MovingForwardSupportSection'; -Our donation example enables to forward NEAR Tokens to an account while keeping track of it. It is one of the simplest examples on making a contract handle transfers. +## Introduction to Handling Tokens in Smart Contracts -![img](/docs/assets/examples/donation.png) -_Frontend of the Donation App_ +Smart contracts on NEAR can handle native NEAR tokens directly, as they are built into the protocol (unlike fungible tokens which follow NEP-141 standards). Key concepts include: ---- - -## Obtaining the Donation Example - -You have two options to start the Donation Example. - -1. You can use the app through `Github Codespaces`, which will open a web-based interactive environment. -2. Clone the repository locally and use it from your computer. - -| Codespaces | Clone locally | -| ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/near-examples/donation-examples) | 🌐 `https://github.com/near-examples/donation-examples.git` | - ---- - -## Structure of the Example - -The example is divided in two main components: - -1. The smart contract, available in two flavors: rust and javascript -2. The frontend, that interacts with an already deployed contract. - - - - - -```bash -┌── sandbox-ts # sandbox testing -│ ├── src -│ │ └── main.ava.ts -│ ├── ava.config.cjs -│ └── package.json -├── src # contract's code -│ ├── contract.ts -│ ├── model.ts -│ └── utils.ts -├── package.json # package manager -├── README.md -└── tsconfig.json # test script -``` - - - - - -```bash -┌── tests # workspaces testing -│ ├── workspaces.rs -├── src # contract's code -│ ├── donation.rs -│ └── lib.rs -├── Cargo.toml # package manager -├── README.md -└── rust-toolchain.toml -``` - - - - - ---- - -## Frontend - -The donation example includes a frontend that interacts with an already deployed smart contract, allowing user to donate NEAR tokens to a faucet service. - -
- -### Running the Frontend - -To start the frontend you will need to install the dependencies and start the server. - -```bash -cd frontend -yarn -yarn dev -``` - -Go ahead and login with your NEAR account. If you don't have one, you will be able to create one in the moment. Once logged in, input the amount of NEAR you want to donate and press the donate button. You will be redirected to the NEAR Wallet to confirm the transaction. After confirming it, the donation will be listed in the "Latest Donations". +- **Payable Methods**: Methods that accept attached deposits (tokens sent with the call). Mark them with decorators like `@payable` in AssemblyScript or `#[payable]` in Rust. +- **Attached Deposit**: Access the sent amount via environment functions (e.g., `env.attached_deposit()` in AssemblyScript or `attached_deposit()` in Rust). +- **Transfers**: Use promises to send tokens to other accounts. This is asynchronous to handle potential failures. +- **Tracking**: Store donation/transfer data in contract storage (e.g., maps or sets) for transparency and querying. -
+In this tutorial, we use a donation contract as an example: users donate NEAR tokens, which are forwarded to a beneficiary while tracking totals per donor. -### Understanding the Frontend +## Explaining the Concept: Receiving and Transferring Tokens -The frontend is a [Next.JS](https://nextjs.org/) project generated by [create-near-app](https://github.com/near/create-near-app). Check `DonationsTable.jsx` and `DonationsForm.jsx` to understand how components are displayed and interacting with the contract. - - - - - - -An interesting aspect of the donation example is that it showcases how to retrieve a result after being redirected to the -NEAR wallet to accept a transaction. - ---- - -## Smart Contract - -The contract exposes methods to donate tokens (`donate`), and methods to retrieve the recorded donations (e.g. `get_donation_for_account`). +To receive tokens, declare a payable method. In the donation example, the `donate` method receives the attached deposit and records it: + start="16" end="20" /> + start="17" end="25" /> -
- -### Testing the Contract - -The contract readily includes a set of unit and sandbox testing to validate its functionality. To execute the tests, run the following commands: - - - - - ```bash - cd contract-ts - yarn - yarn test - ``` - - - - - ```bash - cd contract-rs - cargo test - ``` - - - - - -:::tip -The `integration tests` use a sandbox to create NEAR users and simulate interactions with the contract. -::: - -
- -### Deploying the Contract to the NEAR network - -In order to deploy the contract you will need to create a NEAR account. - - - - - ```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 - ``` - - - -Go into the directory containing the smart contract (`cd contract-ts` or `cd contract-rs`), build and deploy it: - - - - - - ```bash - npm run build - near deploy ./build/donation.wasm - ``` - - - - - ```bash - cargo near deploy build-non-reproducible-wasm - ``` +Here, the method checks the attached deposit and stores it in a map keyed by donor account ID. - +To transfer tokens, create a promise: - - -:::tip -To interact with your contract from the [frontend](#frontend), simply replace the variable `CONTRACT_NAME` in the `index.js` file. -::: - -
- -### CLI: Interacting with the Contract - -To interact with the contract through the console, you can use the following commands - -#### Get donations - - - - - ```bash - near view donation.near-examples.testnet get_donations '{"from_index": "0","limit": "10"}' - ``` - - - - - ```bash - near contract call-function as-read-only donation.near-examples.testnet get_donations json-args '{"from_index": "0","limit": "10"}' network-config testnet now - ``` - - - -
- -#### Get beneficiary - - - - - ```bash - near view donation.near-examples.testnet get_beneficiary - ``` - - - - - ```bash - near contract call-function as-read-only donation.near-examples.testnet get_beneficiary json-args {} network-config testnet now - ``` - - - -
- -#### Get number of donors - - - - - ```bash - near view donation.near-examples.testnet number_of_donors - ``` - - - - - ```bash - near contract call-function as-read-only donation.near-examples.testnet number_of_donors json-args {} network-config testnet now - ``` - - - -
- -#### Get donation for an account - - - - - ```bash - # Require accountId - near view donation.near-examples.testnet get_donation_for_account '{"account_id":}' - ``` - - - - - ```bash - # Require accountId - near contract call-function as-read-only donation.near-examples.testnet get_donation_for_account json-args '{"account_id":}' network-config testnet now - ``` - - - -
- -#### Donate to the contract - - - + + + + + + + + - ```bash - # Replace with your account ID - # Require deposit - near call donation.near-examples.testnet donate --accountId --deposit 0.1 - ``` - +This forwards the tokens immediately to the beneficiary. Promises ensure the transfer happens after the current execution, allowing for callbacks if needed (e.g., on failure). - +Query methods (non-payable) allow viewing donations without changing state: - ```bash - # Replace with your account ID - # Require deposit - near contract call-function as-transaction donation.near-examples.testnet donate json-args {} prepaid-gas '30.0 Tgas' attached-deposit '0.1 NEAR' sign-as network-config testnet sign-with-keychain send - ``` - - + + + + + + + + -
+## Trade-offs and Alternatives -:::tip -If you're using your own account, replace `donation.near-examples.testnet` with your `accountId`. -::: +- **Immediate vs. Accumulated Transfers**: + - Immediate (as in this example): Ensures quick distribution but incurs gas for each donation. Trade-off: Higher costs for frequent small donations; simpler accounting. + - Accumulated: Store tokens in the contract and add a withdrawal method (e.g., only beneficiary can call). Trade-off: Lower per-donation gas but introduces security risks (e.g., contract hacks) and requires access controls. ---- +- **Native Tokens vs. Fungible Tokens (NEP-141)**: + - Native: Simpler for basic transfers, no need for separate token contracts. Alternative: Use for protocol-level payments. + - NEP-141: For custom tokens, use `ft_transfer` or `ft_transfer_call`. Trade-off: More flexible (e.g., callbacks) but requires approving transfers and handling storage deposits. -## Moving Forward +- **Error Handling**: Always check `attached_deposit > 0` to prevent zero-value calls. Use promise callbacks for revert-on-failure logic. -A nice way to learn is by trying to expand a contract. Modify the donation example so it accumulates the tokens in the contract -instead of sending it immediately. Then, make a method that only the `beneficiary` can call to retrieve the tokens. +## How to Test Token Handling - +- **Unit Tests**: Mock environment to simulate deposits and transfers. Verify storage updates and promise creations. + - Example in JS: Use `near-sdk-sim` to test `donate` records the amount and initiates transfer. + - In Rust: Use `near-sdk::test_utils` to assert balances post-transfer. -:::note Versioning for this article +- **Sandbox/Integration Tests**: Use NEAR Workspaces to create test accounts, send deposits, and query results. + - Simulate multiple donations, check totals match attached amounts, and verify beneficiary balance increases. -At the time of this writing, this example works with the following versions: +- **Edge Cases**: Test zero deposit (should fail), maximum deposit limits, and concurrent donations for storage consistency. -- near-cli: `4.0.13` -- node: `18.19.1` -- rustc: `1.77.0` +This focused approach ensures secure, transparent token handling in your contracts. +:::note +For full code, see the [donation example repository](https://github.com/near-examples/donation-examples). :::