Masih Nabizadeh
خواندن ۱۰ دقیقه·۱ ماه پیش

روش ساخت یک توکن ارز دیجیتال (در شبکه اتریوم)

دنیای ارزهای دیجیتال فضای بسیار جدیدی را ایجاد کرده که افراد و شرکت ها بتوانند توکن های خود را ایجاد کرده و از آن برای مقاصد داخلی یا حتی انتقال وجه استفاده کنند.

بسترهای زیادی برای ساخت توکن وجود دارد. در این مقاله بستر اتریوم که در حال حاضر معروف ترین و متداول ترین بستر برای ساخت توکن هست رو انتخاب میکنیم.
دقت داشته باشید هدف ما فعلا ساخت یک توکن هست و برای این کار نیاز به یک بستر مانند اتریوم، سولانا یا غیره داریم. اکثر شتکوین ها در واقع توکن هایی هستند که محبوبیت زیادی بین کاربران پیدا کرده اند.

برای ایجاد یک توکن از زبان برنامه نویسی Solidity استفاده میکنیم. این زبان شباهت زیادی به زبان های پیشرفته مانند جاوا و C++ دارد.

در این کد ابتدا متغیرهای لازم را تعریف میکنیم. سپس داده ها رو مشخص کرده و تابع ها که در واقع همان functionهای قابل اجرا در توکن هستند را برنامه نویسی می کنیم.

این روش به ما کمک میکند هر function که مورد نظر ما نبود را به راحتی پاک کنیم و مشکلی هم در کل کد ایجاد نخواهد شد (به شریط که آن تابع در بخش های دیگر فراخوانی نشود)

برای اجرا و کامپایل کد از Remix IDE استفاده میکنیم که قبل از تست در شبکه اصلی بتونیم ایرادات احتمالی را ببینیم و تغییرات لازم را اعمال کنیم.

خب بدون اتلاف وقت به سراغ کد میرویم:

pragma solidity ^0.8.0;
interface ERC20 {
function totalSupply() external pure returns (uint256 _totalSupply);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _sender) external view returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
contract MyERC20Token is ERC20 {
string public constant symbol = &quotMET&quot
string public constant name = &quotMy ERC20 Token&quot
uint8 public constant decimals = 18;
// 1,000,000 + 18 zeros
uint256 private constant __totalSupply = 1000000000000000000000000;
// this mapping is where we store the balances of an address
mapping(address => uint256) private __balanceOf;
// This is for the approval function to determine how much an address can spend
mapping(address => mapping(address => uint256)) private __allowances;
constructor() {
__balanceOf[msg.sender] = __totalSupply; //the creator of the contract has the total supply and no one can create tokens
}
// constant value that does not change/ returns the amount of initial tokens to display
function totalSupply() public pure override returns (uint256) {
return __totalSupply;
}
// returns the balance of a specific address
function balanceOf(address _address) public view override returns (uint256) {
return __balanceOf[_address];
}
// Transfer an amount of tokens to another address.
// Pre-checks:
// - The transfer needs to be > 0
// - does the msg.sender have enough tokens to forfill the transfer
// Output:
// - decrease the balance of the sender
// - increase the balance of the to address
// - Emit transfer event
function transfer(address _to, uint256 _value) public override returns (bool) {
if (_value > 0 && _value <= balanceOf(msg.sender)) {
__balanceOf[msg.sender] -= _value;
__balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
return false;
}
// this allows someone else (a 3rd party) to transfer from my wallet to someone elses wallet
// Pre-checks:
// - The transfer needs to be > 0
// - and the 3rd party has an allowance of > 0
// - and the allowance is >= the value of the transfer
// - and it is not a contract
// Output:
// - decrease the balance of the from account
// - increase the balance of the to account
// - Emit transfer event
function transferFrom(
address _from,
address _to,
uint256 _value
) public override returns (bool) {
if (
_value > 0 &&
__allowances[_from][msg.sender] > 0 &&
__allowances[_from][msg.sender] >= _value &&
!isContract(_to)
) {
__balanceOf[_from] -= _value;
__balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
}
return false;
}
// This check is to determine if we are sending to a contract?
// Is there code at this address? If the code size is greater then 0 then it is a contract.
function isContract(address _address) public view returns (bool) {
uint256 codeSize;
assembly {
codeSize := extcodesize(_address)
}
return codeSize > 0;
}
// allows a spender address to spend a specific amount of value
function approve(address _spender, uint256 _value) external override returns (bool) {
__allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
// shows how much a spender has the approval to spend to a specific address
function allowance(address _owner, address _spender) external view returns (uint256 remaining) {
return __allowances[_owner][_spender];
} }


خب نیازی نیست بترسین. قدم به قدم این کد رو توضیح خواهیم داد.
اما اول بدونین که این کد رو میتونین در remix IDE اپلود کنید.

مطابق با تصویر بالا، اول start coding و بعد در بخش contract یک فایل جدید ایجاد کرده و اسم دلخواه خودتون رو بنویسید. سپس enter بزنید تا وارد صفحه کدنویسی بشید و کد بالا رو اونجا قرار بدید

این یک قرارداد هوشمند در زبان Solidity است که یک توکن ساده مبتنی بر استاندارد ERC-20 (شبکه اتریوم)پیاده‌سازی می‌کند. حالا بریم سراغ توضیح خط به خط که هرجا لازم داشتید، کد را تغییر داده و توکن مناسب خود را بسازید.


1. نسخه‌ی کامپایلر

pragma solidity ^0.8.0;

این دستور به کامپایلر می‌گوید که نسخه 0.8.0 یا بالاتر از Solidity برای کامپایل این قرارداد استفاده شود.

۳. تعریف Interface برای ERC-20

interface ERC20 { ... }

اینجا یک اینترفیس برای توکن ERC-20 تعریف شده که مجموعه‌ای از فانکشن‌های استاندارد مثل totalSupply، transfer و approve دارد.

توابع موجود:
  • totalSupply(): مقدار کل توکن‌های موجود را برمی‌گرداند.
  • balanceOf(address _owner): مقدار توکن‌های یک آدرس خاص را برمی‌گرداند.
  • transfer(address _to, uint256 _value): مقدار _value توکن را به آدرس _to ارسال می‌کند.
  • transferFrom(address _from, address _to, uint256 _value): انتقال توکن توسط شخص ثالث.
  • approve(address _spender, uint256 _value): اجازه می‌دهد که _spender مقدار _value از حساب شما خرج کند.
  • allowance(address _owner, address _spender): مقدار توکنی که یک آدرس اجازه دارد خرج کند را برمی‌گرداند.
  • رویدادها (Events):Transfer: هنگام انتقال توکن فعال می‌شود.
    Approval: هنگام تأیید مقدار قابل خرج توسط شخص ثالث فعال می‌شود.

۴. پیاده‌سازی قرارداد MyERC20Token

contract MyERC20Token is ERC20 {

اینجا قرارداد MyERC20Token بر اساس ERC20 ساخته شده و شامل اطلاعات مربوط به یک توکن است.

اطلاعات ثابت توکن:
string public constant symbol = &quotMET&quot string public constant name = &quotMy ERC20 Token&quot uint8 public constant decimals = 18;
  • symbol: نماد توکن (MET).
  • name: نام توکن (My ERC20 Token).
  • decimals: تعداد رقم‌های اعشار (18 رقم، مثل ETH).
مقدار کل عرضه‌ی توکن:
uint256 private constant __totalSupply = 1000000000000000000000000;
  • مقدار کل توکن‌ها ۱ میلیون توکن (با 18 رقم اعشار).
تعریف موجودی کاربران:
mapping(address => uint256) private __balanceOf;
  • این mapping موجودی هر کاربر را نگه می‌دارد.
مجوز خرج کردن توکن توسط شخص ثالث:
mapping(address => mapping(address => uint256)) private __allowances;
  • این mapping مشخص می‌کند که هر آدرس چه مقدار توکن به آدرس دیگری اجازه خرج کردن داده است.

۵. مقداردهی اولیه در Constructor

constructor() { __balanceOf[msg.sender] = __totalSupply; }
  • در constructor تمام توکن‌ها به آدرسی که قرارداد را ایجاد کرده، اختصاص داده می‌شود.

۶. توابع ERC-20 پیاده‌سازی شده

۱.تابع totalSupply: مقدار کل توکن‌های در گردش را برمی‌گرداند.

function totalSupply() public pure override returns (uint256) { return __totalSupply; }
  • مقدار کل عرضه توکن را بازمی‌گرداند.

۲. تابع balanceOf: مقدار توکن‌های یک آدرس را نشان می‌دهد.

function balanceOf(address _address) public view override returns (uint256) { return __balanceOf[_address]; }
  • مقدار توکن‌های در دسترس یک آدرس خاص را برمی‌گرداند.

۳. تابع transfer: ارسال توکن از حساب کاربر به آدرس دیگر

function transfer(address _to, uint256 _value) public override returns (bool) { if (_value > 0 && _value <= balanceOf(msg.sender)) { __balanceOf[msg.sender] -= _value; __balanceOf[_to] += _value; emit Transfer(msg.sender, _to, _value); return true; } return false; }

توضیح بیشتر:

  • مقدار _value باید بیشتر از صفر باشد.
  • موجودی حساب فرستنده باید بیشتر یا مساوی مقدار انتقال باشد.
  • اگر شرط های بالا درست باشد با کمک emit عملیات انتقال انجام می شود

عملیات انتقال:

  • مقدار _value از حساب فرستنده کم می‌شود.
  • مقدار _value به حساب گیرنده اضافه می‌شود.
  • رویداد Transfer اجرا می‌شود.

۴. تابع transferFrom: ارسال توکن از طرف شخص دیگر.

function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) { if ( _value > 0 && __allowances[_from][msg.sender] > 0 && __allowances[_from][msg.sender] >= _value && !isContract(_to) ) { __balanceOf[_from] -= _value; __balanceOf[_to] += _value; emit Transfer(_from, _to, _value); return true; } return false; }

بررسی بیشتر:

  • مقدار _value باید بیشتر از صفر باشد.
  • فرستنده (msg.sender) باید اجازه‌ی کافی داشته باشد.
  • مقصد نباید یک قرارداد هوشمند باشد (بررسی توسط تابع isContract) (جلوتر توضیح می دهم)

عملیات انتقال:

  • مقدار _value از حساب _from کم می‌شود.
  • مقدار _value به حساب _to اضافه می‌شود.
  • رویداد Transfer اجرا می‌شود.

۵.تابع isContract: بررسی می‌کند که آیا یک آدرس، قرارداد هوشمند است یا نه.

function isContract(address _address) public view returns (bool) { uint256 codeSize; assembly { codeSize := extcodesize(_address) } return codeSize > 0; }
  • از Assembly برای بررسی کد ذخیره‌شده در یک آدرس استفاده می‌شود.
  • اگر اندازه‌ی کد بزرگتر از صفر باشد، یعنی آدرس یک قرارداد هوشمند است.

۶.تابع approve: اعطای اجازه خرج کردن توکن به یک آدرس دیگر.

function approve(address _spender, uint256 _value) external override returns (bool) { __allowances[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; }
  • به _spender اجازه می‌دهد که مقدار _value از حساب کاربر خرج کند.
  • رویداد Approval اجرا می‌شود.

۷. تابع allowance: مقدار مجوزی که یک آدرس می‌تواند خرج کند.

function allowance(address _owner, address _spender) external view returns (uint256 remaining) { return __allowances[_owner][_spender]; }
  • مقدار توکنی که _spender اجازه دارد از _owner خرج کند را برمی‌گرداند.

کد بالا دقیقا چه توکنی ایجاد میکند؟

به زبان ساده کد بالا یک توکن ERC-20 می باشد که به نام MET ساختیم و عملیات های ابتدایی برای یک توکن را برای شما انجام می دهد:

  • قابلیت انتقال توکن (transfer، transferFrom) را فراهم می‌کند.
  • مدیریت مجوز خرج کردن (approve، allowance) را فراهم می‌کند.
  • از ارسال توکن به قراردادهای هوشمند جلوگیری می‌کند.



خب قدم بعدی تست این کد در بستر Remix IDE می باشد.


مطابق با عکس بالا در منوی سمت چپ وارد solidity compiler شده و دکمه مشخص شده را کلیک می کنید.

در صورتی که کامپایل با موفقیت انجام شود کنار تب compiler یک تیک سبز ایجاد می شود. حالا کد شما اماده اجرا شدن در بستر تست می باشد.

در تب پایین کامپایل Deploy & run transactions گزینه مشخص شده رو کلیک میکنید. در صورتی که ادرس را در بخش owner وارد کنید اطلاعات توکن به شرح زیر نشان داده می شود:

طبیعی است که در محیط Remix چون یک اکانت داریم نمیتوانیم انتقال و تست را به درستی انجام دهیم. برای این کار باید به یک کیف پول مانند metamask متصل شویم.

بعد از ایجاد یک کیف پول متاماسک، از طریق import token کد را در متاماسک اجرا کرده و ذخیره می کنیم. دقت کنید که اسم و تعداد اعشاری که در متاماسک اعلام میکنید با آنچه در کد نوشته شده تطابق داشته باشد


برای اینکه یک توکن واقعا ارزش معامله داشته باشد باید در شبکه اصلی اتریوم (mainnet) اجرا شده و یا در صرافی های غیر متمرکز مانند Uniswap لیست شود.

برای اینکار شما نیاز به یک استخر پول (liquidity pool) دارید

بعد از ایجاد استخر نقدینگی در uniswap امکان معامله توکن توسط سایرین وجود خواهد داشت.


فارغ التحصیل MBAدانشگاه تهران که بعد از چند سال بازاریابی به دنیای مالی وارد شده!
شاید از این پست‌ها خوشتان بیاید