本文作者:木头[1]
代币合约
合约参数
保存代币余额
代码语言:javascript复制struct Coin has store {
value : u128,
}
地址对印余额数据
代码语言:javascript复制struct CoinStore has key {
coin : Coin,
}
代币基础信息
代码语言:javascript复制struct CoinInfo has key {
// 名称
name: string::String,
// 符号
symbol: string::String,
// 精度
decimals: u8,
// 总发行量
supply: u128,
// 已发行量
cap: u128,
}
assert 错误信息定义
assert
只能给定 u64 错误信息,全局错误定义
// 账户已经注册
const THE_ACCOUNT_HAS_BEEN_REGISTERED : u64 = 1;
// 无效的令牌所有者
const INVALID_TOKEN_OWNER : u64 = 2;
// 账户未注册
const THE_ACCOUNT_IS_NOT_REGISTERED : u64 = 3;
// 余额不足
const INSUFFICIENT_BALANCE : u64 = 4;
// 代币信息已发布
const ECOIN_INFO_ALREADY_PUBLISHED : u64 = 5;
// 超过总供应量
const EXCEEDING_THE_TOTAL_SUPPLY : u64 = 6;
合约查询方法
查询代币余额
代码语言:javascript复制public fun getBalance(owner: address) : u128 acquires CoinStore{
// 确定帐户是否已注册
assert!(is_account_registered(owner), THE_ACCOUNT_IS_NOT_REGISTERED);
// 返回余额
borrow_global<CoinStore>(owner).coin.value
}
查询帐户是否已注册
代码语言:javascript复制public fun is_account_registered(account_addr : address) : bool{
exists<CoinStore>(account_addr)
}
合约公共方法
增加地址余额
代码语言:javascript复制fun deposit(account_addr : address, coin : Coin) acquires CoinStore {
// 确定该帐户是否已注册
assert!(is_account_registered(account_addr), THE_ACCOUNT_IS_NOT_REGISTERED);
// 转账之前余额
let balance = getBalance(account_addr);
// 获取可变资源余额
let balance_ref = &mut borrow_global_mut<CoinStore>(account_addr).coin.value;
// 更新余额
*balance_ref = balance coin.value;
// 销毁资源
let Coin { value:_ } = coin;
}
减少地址余额
代码语言:javascript复制fun withdraw(account_addr : address, amount : u128) : Coin acquires CoinStore {
// 确定该帐户是否已注册
assert!(is_account_registered(account_addr), THE_ACCOUNT_IS_NOT_REGISTERED);
// 转账之前余额
let balance = getBalance(account_addr);
// 余额是否足够
assert!(balance >= amount, INSUFFICIENT_BALANCE);
// 获取可变资源余额
let balance_ref = &mut borrow_global_mut<CoinStore>(account_addr).coin.value;
// 更新余额
*balance_ref = balance - amount;
// 返回余额资源
Coin { value: amount }
}
合约执行函数
初始化代币信息
代码语言:javascript复制public entry fun initialize(address : &signer, name : vector<u8>, symbol : vector<u8>, decimals : u8, supply : u128) {
// 是否有权限初始化代币信息
assert!(signer::address_of(address) == MODULE_OWNER, INVALID_TOKEN_OWNER);
// 确定是否已初始化(防止重复初始化)
assert!(!exists<CoinInfo>(MODULE_OWNER), ECOIN_INFO_ALREADY_PUBLISHED);
// 创建令牌信息
move_to(address, CoinInfo{name : string::utf8(name), symbol : string::utf8(symbol), decimals, supply, cap : 0});
}
address
:调用用者,name
:名称,symbol
:符号,decimals
:精度,supply
:总发行量
注册账号(在交易之前必须先注册账号)
代码语言:javascript复制public entry fun register(address : &signer) {
let account = signer::address_of(address);
// 确定该帐户是否已注册(防止重复注册)
assert!(!exists<CoinStore>(account), THE_ACCOUNT_HAS_BEEN_REGISTERED);
// 初始化账号
move_to(address, CoinStore{ coin : Coin{ value : 0 } });
}
铸币
代码语言:javascript复制public entry fun mint(owner : &signer,to : address,amount : u128) acquires CoinStore,CoinInfo{
// 是否有权限铸币
assert!(signer::address_of(owner) == MODULE_OWNER, INVALID_TOKEN_OWNER);
// 是否超过总发行量
assert!(borrow_global<CoinInfo>(MODULE_OWNER).cap amount <= borrow_global<CoinInfo>(MODULE_OWNER).supply,EXCEEDING_THE_TOTAL_SUPPLY);
// 收款人增加余额
deposit(to, Coin { value : amount });
// 增加发行总量
let cap = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).cap;
*cap = *cap amount;
}
owner
:必须为模块拥有者才能有权限铸币,to
:给 to 地址铸币,amount
:铸币数量
转账
代码语言:javascript复制public entry fun transfer(from : &signer, to : address, amount : u128) acquires CoinStore {
// 先扣除账号余额
let coin = withdraw(signer::address_of(from), amount);
// 增加账号余额
deposit(to, coin);
}
销毁
代码语言:javascript复制public entry fun burn(owner : &signer, amount : u128) acquires CoinStore,CoinInfo {
// 是否有权限销毁
assert!(signer::address_of(owner) == MODULE_OWNER, INVALID_TOKEN_OWNER);
// 扣除账号余额
let coin = withdraw(signer::address_of(owner), amount);
let Coin { value: amount } = coin;
// 减少已发行量
let cap = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).cap;
*cap = *cap - amount;
// 减少总发行量
let supply = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).supply;
*supply = *supply - amount;
}
Typescript 脚本
生成账号脚本
account_script.ts
代码语言:javascript复制//节点URL REST API地址
export const NODE_URL = "https://fullnode.devnet.aptoslabs.com";
//水龙头URL
export const FAUCET_URL = "https://faucet.devnet.aptoslabs.com";
//生成账号接口
import { AptosAccount, MaybeHexString } from "aptos";
//Aptos客户端 水龙头客户端
import { AptosClient, FaucetClient } from "aptos";
//创建节点客户端
const client = new AptosClient(NODE_URL);
//查询主链币余额
export async function accountBalance(accountAddress: MaybeHexString): Promise<number | null> {
const resource = await client.getAccountResource(accountAddress, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>");
if (resource == null) {
return null;
}
return parseInt((resource.data as any)["coin"]["value"]);
}
//主方法
async function main() {
//创建帐户
const account = new AptosAccount();
console.log("n=== 地址 ===");
console.log(
`地址: ${account.address()} key种子: ${Buffer.from(account.signingKey.secretKey).toString("hex").slice(0, 64)}`,
);
//水龙头领取代币
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
await faucetClient.fundAccount(account.address(), 50000);
console.log("n=== 账户余额 ===");
console.log(`${account.address()}余额: ${await accountBalance(account.address())}`);
}
if (require.main === module) {
main();
}
调用合约脚本
coin_script.ts
代码语言:javascript复制import assert from "assert";
import fs from "fs";
import { AptosAccount, AptosClient, TxnBuilderTypes, BCS, MaybeHexString, HexString, FaucetClient } from "aptos";
//节点URL REST API地址
export const NODE_URL = "https://fullnode.devnet.aptoslabs.com";
//水龙头URL
export const FAUCET_URL = "https://faucet.devnet.aptoslabs.com";
// 创建aptos客户端
const client = new AptosClient(NODE_URL);
// 查询APT代币余额
export async function getAccountBalance(accountAddress: MaybeHexString): Promise<number | null> {
const resource = await client.getAccountResource(accountAddress, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>");
if (resource == null) {
return null;
}
return parseInt((resource.data as any)["coin"]["value"]);
}
//发布模块
export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise<string> {
const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle(
new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
moudleBundlePayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
return transactionRes.hash;
}
//注册代币账号=>初始化账号
async function registerCoin(contractAddress: HexString, accountFrom: AptosAccount): Promise<string> {
const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(`${contractAddress.toString()}::coin`, "register", [], []),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
// 初始化token信息
async function initializeCoin(contractAddress: HexString, accountFrom: AptosAccount): Promise<string> {
// 总发行量
const supply = new BCS.Serializer();
supply.serializeU128(1000);
// 精度
const decimals = new BCS.Serializer();
decimals.serializeU8(0);
const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
`${contractAddress}::coin`,
"initialize",
[],
[BCS.bcsSerializeStr("CPI Coin"), BCS.bcsSerializeStr("CPI"), decimals.getBytes(), supply.getBytes()],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
//铸币
async function mintCoin(
contractAddress: HexString,
accountFrom: AptosAccount,
receiverAddress: HexString,
amount: number,
): Promise<string> {
// 发行量
const cap = new BCS.Serializer();
cap.serializeU128(amount);
const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
`${contractAddress.toString()}::coin`,
"mint",
[],
[BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(receiverAddress.hex())), cap.getBytes()],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
//转账
async function transfer(
contractAddress: HexString,
accountFrom: AptosAccount,
to: HexString,
amount: number,
): Promise<string> {
const amo = new BCS.Serializer();
amo.serializeU128(amount);
const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
`${contractAddress.toString()}::coin`,
"transfer",
[],
[BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(to.hex())), amo.getBytes()],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
// 销毁
async function burn(contractAddress: HexString, accountFrom: AptosAccount, amount: number): Promise<string> {
const amo = new BCS.Serializer();
amo.serializeU128(amount);
const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(`${contractAddress.toString()}::coin`, "burn", [], [amo.getBytes()]),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
//查询代币余额
async function getBalance(contractAddress: HexString, accountFrom: AptosAccount): Promise<string | number | any> {
try {
const resource = await client.getAccountResource(accountFrom.address(), `${contractAddress}::coin::CoinStore`);
return parseInt((resource.data as any)["coin"]["value"]);
} catch (_) {
return 0;
}
}
//查询代币信息
async function getCoinInfo(contractAddress: HexString): Promise<string | number | any> {
try {
const resource = await client.getAccountResource(contractAddress, `${contractAddress}::coin::CoinInfo`);
return resource;
} catch (_) {
return 0;
}
}
async function main() {
assert(process.argv.length == 3, "Expecting an argument that points to the moon coin module");
//创建客户端
const client = new AptosClient(NODE_URL);
const account = new AptosAccount(
new HexString("3a64b8d0478799c570fb540fc2aabc917604b72f7cd44c75d8df4629e5af58ce").toUint8Array(),
);
console.log(`模块拥有者 ${account.address()}余额: ${await getAccountBalance(account.address())}APT`);
//创建水龙头客户端
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
// 发布模块
const modulePath = process.argv[2];
const moduleHex = fs.readFileSync(modulePath).toString("hex");
console.log("n==========发布模块========");
let txHash = await publishModule(account, moduleHex);
console.log("发布模块:" txHash);
await client.waitForTransaction(txHash);
console.log("n==========初始化token信息========");
txHash = await initializeCoin(account.address(), account);
console.log("初始化token信息Hash:" txHash);
await client.waitForTransaction(txHash);
console.log("n==========token信息========");
let coinInfo: CoinInfo = await getCoinInfo(account.address());
console.log(`模块类型:${coinInfo.type}`);
console.log(`名称:${coinInfo.data.name}`);
console.log(`符号:${coinInfo.data.symbol}`);
console.log(`精度:${coinInfo.data.decimals}`);
console.log(`总发行量:${coinInfo.data.supply}`);
console.log(`已发行量:${coinInfo.data.cap}`);
console.log("n==========创建测试账号A和B==========");
const a = new AptosAccount();
console.log(`a地址: ${a.address()}`);
const b = new AptosAccount();
console.log(`b地址: ${b.address()}`);
// 领取gas费
await faucetClient.fundAccount(a.address(), 5000);
await faucetClient.fundAccount(b.address(), 5000);
//注册代币账号=>初始化账号
console.log("n==========注册代币账号========");
txHash = await registerCoin(account.address(), a);
console.log("a注册Hash:" txHash);
await client.waitForTransaction(txHash);
txHash = await registerCoin(account.address(), b);
console.log("b注册Hash:" txHash);
await client.waitForTransaction(txHash);
txHash = await registerCoin(account.address(), account);
console.log("模块拥有者注册Hash:" txHash);
await client.waitForTransaction(txHash);
//铸币
console.log("n==========铸造100个代币给A账号========");
txHash = await mintCoin(account.address(), account, a.address(), 100);
console.log("管理员给A账号铸币Hash:" txHash);
await client.waitForTransaction(txHash);
let aBalance = await getBalance(account.address(), a);
console.log(`a余额:` aBalance);
//转账
console.log("n==========A账号转账50个代币给B账号========");
txHash = await transfer(account.address(), a, b.address(), 50);
console.log("转账Hash:" txHash);
await client.waitForTransaction(txHash);
//查询余额
aBalance = await getBalance(account.address(), a);
console.log(`a余额:` aBalance);
let bBalance = await getBalance(account.address(), b);
console.log(`b余额:` bBalance);
// 销毁
console.log("n==========B账号转账50个代币给模块拥有者========");
txHash = await transfer(account.address(), b, account.address(), 50);
console.log("转账Hash:" txHash);
await client.waitForTransaction(txHash);
//查询余额
aBalance = await getBalance(account.address(), a);
console.log(`a余额:` aBalance);
bBalance = await getBalance(account.address(), b);
console.log(`b余额:` bBalance);
let accountBalance = await getBalance(account.address(), account);
console.log(`模块拥有者余额:` accountBalance);
//开始销毁
console.log("n==========销毁50个代币========");
txHash = await burn(account.address(), account, 50);
console.log("销毁Hash:" txHash);
await client.waitForTransaction(txHash);
aBalance = await getBalance(account.address(), a);
console.log(`a余额:` aBalance);
bBalance = await getBalance(account.address(), b);
console.log(`b余额:` bBalance);
accountBalance = await getBalance(account.address(), account);
console.log(`模块拥有者余额:` accountBalance);
coinInfo = await getCoinInfo(account.address());
console.log(`总发行量:${coinInfo.data.supply}`);
console.log(`已发行量:${coinInfo.data.cap}`);
}
if (require.main === module) {
main();
}
// 代币信息
interface CoinInfo {
type: string;
data: Data;
}
interface Data {
cap: string; //已发行量
decimals: number; //精度
name: string; //名称
symbol: string; //符号
supply: string; //总发行量
}
发布并调用合约
1.调用 account_script.ts 生成合约部署账号
代码语言:javascript复制$ node --loader ts-node/esm account_script.ts
=== 地址 ===
地址: 0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93 key种子: 3a64b8d0478799c570fb540fc2aabc917604b72f7cd44c75d8df4629e5af58ce
=== 账户余额 ===
0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93余额: 50000
2.复制地址到合约项目 Move.toml 配置
代码语言:javascript复制[addresses]
std = "0x1"
CoinToken = "0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93"
生成 mv
代码语言:javascript复制$ aptos move compile --package-dir . --named-addresses CoinToken=0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93
{
"Result": [
"E13C36E921448A601F2DE9DC5341525CA6619A44E1444F302FBA37FB39C5CF93::coin"
]
}
3.复制合约项目 mv 到脚本项目根目录
4.复制账号的种子到 coin_script.ts 的 main
5.调用 coin_script.ts 脚本
代码语言:javascript复制$ node --loader ts-node/esm coin_script.ts coin.mv
模块拥有者0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93余额: 50000APT
==========发布模块========
发布模块:0x6d81ba53b5fe50d4d0a73022c8c4c2a8383d81e72768ea9e6066b425c93d5888
==========初始化token信息========
初始化token信息Hash:0x5a62958de06aa7407425c541a83c746e97a66ae427d8ac40a59ab8711c977fa4
==========token信息========
模块类型:0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93::coin::CoinInfo
名称:CPI Coin
符号:CPI
精度:0
总发行量:1000
已发行量:0
==========创建测试账号A和B==========
a地址: 0x17f086d99a1fe096c397880173ae2356b95d63423ff75a6627827b3fc760e224
b地址: 0x922164511c6046c223e90653ebc26a68b0b914114a0c382f1c113319e394826b
==========注册代币账号========
a注册Hash:0x0256d8b19cd7551f582573ab8e4968ea7a0a25fed2ca901619187f1830d5966e
b注册Hash:0x837d60bc99e8143eb21370c6f1eb6cf9d7b0cbfddb3405c18697072cb4e8442e
模块拥有者注册Hash:0xdd93a94a785f0f85d38b143305880114314ef3ca30ad4d0e7e78a2063f9f514e
==========铸造100个代币给A账号========
管理员给A账号铸币Hash:0x60dbc50c4c61626a32c632a9a997ed9a147101c89c0387b3a57d93fc7ec31ddf
a余额:100
==========A账号转账50个代币给B账号========
转账Hash:0xa3a477466c687ddb86d80cfbff36b645f08edb31175ff9d85df80ab6719d1749
a余额:50
b余额:50
==========B账号转账50个代币给模块拥有者========
转账Hash:0x85205f4c94f922bd8b62c8a4cd68bbe4ea3a7581ffa05e9c81e2a12401028357
a余额:50
b余额:0
模块拥有者余额:50
==========销毁50个代币========
销毁Hash:0x529be7b0f235d8af769d7c00a8b78455ccdbee9e6d9b758a9538e349b0a1ce6f
a余额:50
b余额:0
模块拥有者余额:0
总发行量:950
已发行量:50
区块浏览器:https://explorer.devnet.aptos.dev/account/0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93[2]
参考资料
[1]
木头: https://learnblockchain.cn/people/3015
[2]
https://explorer.devnet.aptos.dev/account/0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93: https://explorer.devnet.aptos.dev/account/0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93