Payable Solidity: Gửi ETH từ Smart Contract và Quản lý dòng tiền On-chain (P2)
4 phút đọc09/05/2025
Về tác giả
My passion is hunting down the latest attack trends—ransomware, APTs, you name it—while passing on knowledge to help businesses forge ironclad defenses. I’ve left my mark on data encryption projects and intrusion detection tools now widely used across Vietnam. I’m the shadow that strikes before the enemy does.
@#@
Đam mê của tôi là nghiên cứu các xu hướng tấn công mới nhất như ransomware và APTs, đồng thời chia sẻ kiến thức để giúp doanh nghiệp xây dựng chiến lược phòng thủ hiệu quả. Tôi từng đóng góp vào các dự án mã hóa dữ liệu và phát triển công cụ phát hiện xâm nhập được sử dụng rộng rãi tại Việt Nam.
Cập nhật thông tin mới nhấtNhận các thông tin mới nhất về mối đe dọa, báo cáo an ninh mạng từ CyStack về hòm thư điện tử của bạn
{"success":true,"head":"<title>Payable Solidity (P2): Quản lý dòng tiền On-chain</title>\n<meta name=\"description\" content=\"Làm sao để gửi ETH từ smart contract tới địa chỉ khác một cách an toàn, tối ưu và không dính lỗi thường gặp và cách quản lý dòng tiền onchain?\"/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-video-preview:-1, max-image-preview:large\"/>\n<link rel=\"canonical\" href=\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/\" />\n<meta property=\"og:locale\" content=\"en_US\" />\n<meta property=\"og:type\" content=\"article\" />\n<meta property=\"og:title\" content=\"Payable Solidity (P2): Quản lý dòng tiền On-chain\" />\n<meta property=\"og:description\" content=\"Làm sao để gửi ETH từ smart contract tới địa chỉ khác một cách an toàn, tối ưu và không dính lỗi thường gặp và cách quản lý dòng tiền onchain?\" />\n<meta property=\"og:url\" content=\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/\" />\n<meta property=\"og:site_name\" content=\"CyStack Tutorial\" />\n<meta property=\"article:tag\" content=\"vi\" />\n<meta property=\"article:section\" content=\"Blockchain\" />\n<meta property=\"og:updated_time\" content=\"2026-08-03T16:34:35+07:00\" />\n<meta property=\"og:image\" content=\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\" />\n<meta property=\"og:image:secure_url\" content=\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\" />\n<meta property=\"og:image:width\" content=\"1200\" />\n<meta property=\"og:image:height\" content=\"630\" />\n<meta property=\"og:image:alt\" content=\"payable solidity\" />\n<meta property=\"og:image:type\" content=\"image/png\" />\n<meta property=\"article:published_time\" content=\"2025-05-09T11:04:39+07:00\" />\n<meta property=\"article:modified_time\" content=\"2026-08-03T16:34:35+07:00\" />\n<meta name=\"twitter:card\" content=\"summary_large_image\" />\n<meta name=\"twitter:title\" content=\"Payable Solidity (P2): Quản lý dòng tiền On-chain\" />\n<meta name=\"twitter:description\" content=\"Làm sao để gửi ETH từ smart contract tới địa chỉ khác một cách an toàn, tối ưu và không dính lỗi thường gặp và cách quản lý dòng tiền onchain?\" />\n<meta name=\"twitter:image\" content=\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\" />\n<meta name=\"twitter:label1\" content=\"Written by\" />\n<meta name=\"twitter:data1\" content=\"Đức Hacker\" />\n<meta name=\"twitter:label2\" content=\"Time to read\" />\n<meta name=\"twitter:data2\" content=\"4 minutes\" />\n<script type=\"application/ld+json\" class=\"rank-math-schema\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https://blog.cystack.org/tutorial/#person\",\"name\":\"CyStack Tutorial\"},{\"@type\":\"WebSite\",\"@id\":\"https://blog.cystack.org/tutorial/#website\",\"url\":\"https://blog.cystack.org/tutorial\",\"name\":\"CyStack Tutorial\",\"publisher\":{\"@id\":\"https://blog.cystack.org/tutorial/#person\"},\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\",\"url\":\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\",\"width\":\"1200\",\"height\":\"630\",\"caption\":\"payable solidity\",\"inLanguage\":\"en-US\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":\"1\",\"item\":{\"@id\":\"https://blog.cystack.org/tutorial\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"position\":\"2\",\"item\":{\"@id\":\"https://blog.cystack.org/tutorial/vi/category/blockchain/\",\"name\":\"Blockchain\"}},{\"@type\":\"ListItem\",\"position\":\"3\",\"item\":{\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/\",\"name\":\"Payable Solidity: G\\u1eedi ETH t\\u1eeb Smart Contract v\\u00e0 Qu\\u1ea3n l\\u00fd d\\u00f2ng ti\\u1ec1n On-chain (P2)\"}}]},{\"@type\":\"WebPage\",\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#webpage\",\"url\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/\",\"name\":\"Payable Solidity (P2): Qu\\u1ea3n l\\u00fd d\\u00f2ng ti\\u1ec1n On-chain\",\"datePublished\":\"2025-05-09T11:04:39+07:00\",\"dateModified\":\"2026-08-03T16:34:35+07:00\",\"isPartOf\":{\"@id\":\"https://blog.cystack.org/tutorial/#website\"},\"primaryImageOfPage\":{\"@id\":\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\"},\"inLanguage\":\"en-US\",\"breadcrumb\":{\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#breadcrumb\"}},{\"@type\":\"Person\",\"@id\":\"https://blog.cystack.org/tutorial/author/duchacker/\",\"name\":\"\\u0110\\u1ee9c Hacker\",\"url\":\"https://blog.cystack.org/tutorial/author/duchacker/\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https://secure.gravatar.com/avatar/7b8b6e4bc4811342b8e2f2134f90dda5961f2d4f8e7da5ec77e52bb909a19782?s=96&d=mm&r=g\",\"url\":\"https://secure.gravatar.com/avatar/7b8b6e4bc4811342b8e2f2134f90dda5961f2d4f8e7da5ec77e52bb909a19782?s=96&d=mm&r=g\",\"caption\":\"\\u0110\\u1ee9c Hacker\",\"inLanguage\":\"en-US\"}},{\"@type\":\"BlogPosting\",\"headline\":\"Payable Solidity (P2): Qu\\u1ea3n l\\u00fd d\\u00f2ng ti\\u1ec1n On-chain\",\"keywords\":\"Payable Solidity\",\"datePublished\":\"2025-05-09T11:04:39+07:00\",\"dateModified\":\"2026-08-03T16:34:35+07:00\",\"articleSection\":\"Blockchain, Solidity\",\"author\":{\"@id\":\"https://blog.cystack.org/tutorial/author/duchacker/\",\"name\":\"\\u0110\\u1ee9c Hacker\"},\"publisher\":{\"@id\":\"https://blog.cystack.org/tutorial/#person\"},\"description\":\"L\\u00e0m sao \\u0111\\u1ec3 g\\u1eedi ETH t\\u1eeb smart contract t\\u1edbi \\u0111\\u1ecba ch\\u1ec9 kh\\u00e1c m\\u1ed9t c\\u00e1ch an to\\u00e0n, t\\u1ed1i \\u01b0u v\\u00e0 kh\\u00f4ng d\\u00ednh l\\u1ed7i th\\u01b0\\u1eddng g\\u1eb7p v\\u00e0 c\\u00e1ch qu\\u1ea3n l\\u00fd d\\u00f2ng ti\\u1ec1n onchain?\",\"name\":\"Payable Solidity (P2): Qu\\u1ea3n l\\u00fd d\\u00f2ng ti\\u1ec1n On-chain\",\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#richSnippet\",\"isPartOf\":{\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#webpage\"},\"image\":{\"@id\":\"https://s2.cystack.net/tutorial/10102327/payable-solidity.png\"},\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https://blog.cystack.org/tutorial/vi/2025/05/09/payable-solidity/#webpage\"}}]}</script>\n"}
Trong phần trước, mình đã chia sẻ chi tiết về payable function trong Solidity — cách cho phép smart contract nhận ETH, và cách EVM xử lý transaction gửi kèm value. Mình sẽ để link bài viết trước ở đây cho bạn nào chưa đọc: Payable Function Solidity là gì? Cơ chế xử lý ETH tới smart contract (P1). Còn trong phần này, chúng ta sẽ đi sâu vào mặt còn lại của payable trong Solidity: làm thế nào để gửi ETH từ smart contract tới địa chỉ khác một cách an toàn, tối ưu và không dính lỗi thường gặp.
Nếu bạn đang viết các contract có xử lý dòng tiền — như treasury, refund, tipping, profit-sharing, NFT claim hoặc reward payout — thì đây chính là phần bạn cần.

Gửi ETH từ Smart Contract không đơn giản như bạn nghĩ
Được rồi, bạn đã nhận ETH vào contract bằng payable function. Nhưng giờ làm sao để gửi ETH từ smart contract đó ra ví người dùng, dev, treasury hay DAO multisig?
Trong Solidity, bạn có thể send ether from smart contract to address bằng 3 cách chính:
Cách 1: .transfer()
Ví dụ:
payable(recipient).transfer(1 ether);
- Cấp mặc định 2,300 gas cho bên nhận
- Tự động revert nếu gửi thất bại
- An toàn với ví cá nhân, nhưng có thể fail nếu recipient là smart contract có fallback function phức tạp
Cách 2: .send()
bool success = payable(recipient).send(1 ether); require(success, “Transfer failed”);
- Cũng cấp 2,300 gas như transfer()
- Trả về true/false thay vì tự revert → dễ bị bỏ sót nếu dev quên check
Cách 3: .call()
(bool sent, ) = payable(recipient).call{value: 1 ether}(“”); require(sent, “Transfer failed”);
- Không giới hạn gas
- Tương thích tốt với contract có fallback logic
- Được khuyến nghị sử dụng trong Solidity hiện đại
Tóm lại: nếu bạn chỉ gửi ETH đến ví thường → dùng transfer() là đủ. Còn nếu người nhận có thể là smart contract, tốt nhất nên dùng call() để tránh các lỗi “gas trap”.
So Sánh Cụ Thể: transfer() vs send() vs call()
Mình từng deploy một NFT claim contract, dùng transfer() để gửi ETH refund cho người không mint được. Trên testnet thì ổn, nhưng lên mainnet gặp một user dùng ví smart contract có fallback → refund fail. Cuối cùng phải refactor lại dùng call().
Xem thêm: Cách airdrop NFT
Rút toàn bộ số dư ETH về ví chủ
Một pattern phổ biến là contract nhận ETH và chủ sở hữu có thể rút tiền về ví cá nhân hoặc treasury.
contract Vault { address public owner;
constructor() {
owner = msg.sender;
}
receive() external payable {}
function withdrawAll() external {
require(msg.sender == owner, "Not owner");
(bool sent, ) = payable(owner).call{value: address(this).balance}("");
require(sent, "Withdraw failed");
}
}
- receive() giúp contract nhận ETH từ bất kỳ ai
- withdrawAll() gửi toàn bộ ETH đến ví chủ bằng call()
- Có check đầy đủ quyền và kết quả
Bạn cũng có thể tách withdraw từng phần, hoặc cho phép nhiều người cùng withdraw theo balance nội bộ.
Hãy cẩn thận với Gas Trap và Fallback Function
Như đã nói, transfer() và send() chỉ cấp 2,300 gas. Nếu ví người nhận là một smart contract có logic trong fallback (ví dụ emit event, ghi log…), nó có thể không đủ gas để chạy → transfer thất bại.
contract Receiver { fallback() external payable { // logic dùng nhiều gas hơn 2,300 doSomething(); } }
→ Gửi ETH bằng transfer đến contract này sẽ luôn fail. Đây gọi là gas trap.
Cách tránh:
- Dùng call() thay cho transfer()/send()
- Nếu cần tính toán chi tiết hơn, bạn có thể truyền gas thủ công: call{value: x, gas: y}()
Reentrancy Attack – Khi gửi ETH bị gọi ngược
Một trong những lỗ hổng kinh điển khi gửi ETH từ contract là reentrancy.
Ví dụ:
function withdraw() public { uint amount = balances[msg.sender]; require(amount > 0, “No balance”);
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Withdraw failed");
balances[msg.sender] = 0; // cập nhật sau — DỄ DÍNH REENTRANCY!
}
Kẻ tấn công có thể dùng fallback() trong contract của họ để gọi lại withdraw() nhiều lần trước khi balance được reset.
Cách phòng chống:
- Dùng Checks-Effects-Interactions pattern (cập nhật balance trước khi gửi ETH)
- Hoặc dùng modifier nonReentrant (OpenZeppelin ReentrancyGuard)
Có thể bạn quan tâm: Cách deploy contract trên polygon
Logging & Event khi gửi ETH
Luôn emit event khi bạn gửi ETH:
event Payout(address indexed to, uint amount);
function payout(address to, uint amount) external onlyOwner { (bool sent, ) = payable(to).call{value: amount}(“”); require(sent, “Transfer failed”); emit Payout(to, amount); }
Việc này giúp:
- Frontend dễ tracking giao dịch
- Indexer (như The Graph) có thể lấy dữ liệu payout
- Audit log rõ ràng hơn
Những lỗi thường gặp khi gửi ETH trong Solidity
Hy vọng những nội dung này hữu ích với các bạn!
Bài viết liên quan: