hello shaokun

code is law


  • 首页

  • 归档

一步一步教你打造自己的defi-xswap(1)

发表于 2020-10-24

welcome

uniswap合约源码 etherscan
uniswap合约源码 github
由于在工作中已经基于uniswap实现了更多玩法的defi,在此把其中的流程再梳理一下
本系列文章仅作为uniswap的源码理解以及带你如何实现一个自己的xswap,其他玩法请各位同学自己发挥了

准备工作

  1. 编程语言
    • solidity(写以太坊合约的语言),
    • nodejs (开发环境)
    • react(写web前端)
  2. 工具
    • metamask(以太坊钱包)
    • 以太坊账号,由于计划合约部署在rinkeby上,所以得去领一些ether
    • 梯子,这个你懂的

搭建以太坊开发环境

  1. remix,
  2. truffle,
  3. waffle,
  4. Hardhat
  5. 演示用的 (主要是将官方demo改为ts版本)

至于怎么选择,合适自己的就是最好的

代码实现

此部分代码为demo代码,如果你和我选用的是同一个框架,应该和下面是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.0;

import "hardhat/console.sol";


contract Greeter {
string greeting;

constructor(string memory _greeting) {
// 这里可以打印console,所以选择了这个框架进行开发
console.log("Deploying a Greeter with greeting:", _greeting);
greeting = _greeting;
}

function greet() public view returns (string memory) {
return greeting;
}

function setGreeting(string memory _greeting) public {
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
greeting = _greeting;
}
}

测试脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const {expect} = require("chai");
//@ts-ignore
import {ethers} from "hardhat";
import chai from "chai";
import {Wallet} from "ethers";
import {deployContract, solidity} from "ethereum-waffle";

describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");

await greeter.deployed();
expect(await greeter.greet()).to.equal("Hello, world!");

await greeter.setGreeting("Hola, mundo!");
expect(await greeter.greet()).to.equal("Hola, mundo!");
});
});

环境搭建结果演示

  1. 本地开发启动节点
  2. 编写测试脚本
    demo

    关于我

    区块链技术痴迷的程序猿一枚

koa + graphql的使用

发表于 2020-08-16

目标

完成http请求到graphql的请求改造

实现

内容大部分来自己graphql与apollo-server官网, 内部加上自己的一些理解,至于改造嘛,就自己动手咯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const Koa = require("koa");
const { ApolloServer, gql } = require("apollo-server-koa");
const GraphQLJSON = require("graphql-type-json");
const { GraphQLScalarType } = require("graphql");
const { Kind } = require("graphql/language");
const books = [
{
title: "Harry Potter and the Chamber of Secrets",
author: "J.K. Rowling",
},
{
title: "Jurassic Park",
author: "Michael Crichton",
},
];

const users = [
{
id: "1",
name: "Elizabeth Bennet",
},
{
id: "2",
name: "Fitzwilliam Darcy",
},
];

const typeDefs = gql`
type Query {
# 返回字符串
hello: String
# 返回数组
books: [Book]
# 通过参数查询
user(id: ID): User
info(date: Date): String
# 读取数据
foo: Foo
}
#使用第三方定义的标量 json类型
scalar JSON
# 使用自定义的date类型
scalar Date
type Foo {
user: JSON
created: Date
}

type Book {
title: String
author: String
}

type User {
id: ID
name: String
}
`;

const resolvers = {
JSON: GraphQLJSON, // 使用第三方的json标量
Date: new GraphQLScalarType({
// 自定义标量
name: "Date",
description: "Date custom scalar type",
parseValue(value) {
// variables 参数解析路径
return new Date(value);
},
serialize(value) {
return value.getTime();
},
parseLiteral(ast) {
// 字面量参数路径
if (ast.kind === Kind.INT) {
// ast.value 永远是 字符串类型 所以需要转换
return new Date(+ast.value);
}
return null;
},
}),
Query: {
hello: () => {
return "Hello shaokun!";
},
books: () => books, // 直接返回数据库中的books
user(parent, args, context) {
// parent 连续查询 中 上一次查询的结果
// args 查询参数 如上面的id
// context 全局数据的拿取
console.log(parent, args, context.name, context.age);
return users.find((user) => user.id === args.id);
},
foo: () => {
return {
user: { name: "shaokum" }, // 如果返回的不是json类型 则为null
created: new Date(), // Date类型 实际返回给前端的是 定义的scalar中的serialize方法
};
},
info: (_, args) => {
// 查询方式 字面常量方式查询
// {
// info(data:1597552212000)
// }
console.log("---info-", args); // 通过Date.parseLiteral 方法拿到解析的数据
return "this date is " + args.date.toString();
},
},
};

const server = new ApolloServer({
typeDefs,
resolvers,
context: async (request) => {
// 可获取到rquest请求
// 可以全局挂载,如授权token db实例
return {
name: "shaokun",
age: 18,
};
},
});

const app = new Koa();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
结果演示

graphql query demo

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

在typescript中使用eosjs-ecc

发表于 2020-02-02

前言

  • ts开发后端项目,考虑到维护的需要,还是需要ts来支持.其中涉及到的lib都能找到对应的types,但是也有例外比如eosjs-ecc

解决方法

  1. 简单快速,但是没有任何提示了,IDE基本的提示都没有了,对于直接将.js—>.ts可以使用
1
2
// tsconfig.json中将此字段设为false
"noImplicitAny": false,
  1. 自己写 **d.ts,喜欢的同学可以尝试一下
  2. 工具自动生成,推荐方式
    -这里使用微软提供的dts-gen进行生成,放到项目的任意目录即可,但是还是无法使用,这时需要在最外面包裹一层 declare module ‘eosjs-ecc’{}即可,如果使用ts-node执行项目,需要在导入的时候添加ts-ignore,直接使用tsc编译则可以不用

    1
    2
    3
    4
    5
    // @ts-ignore
    import ecc from 'eosjs-ecc'
    ecc.randomKey(0).then((privateKey:string) => {
    console.log(privateKey )
    });
  3. 优化

    • 细心的同学发现,里面的类型全是any,一点用处都没有啊?
      但是这样可以有IDE的提示了,ts可以正常写代码了呢
    • 由于一个lib我们不会完全用它的方法,那么我们只需要去修改一下我们需要使用的类型即可.比如randomKey这个方法,我们将它的declare中的按照如下更改即可,再次调用就达到了正常使用lib的目的啦

      1
      2
      // export function randomKey(cpuEntropyBits: number ): Promise<string>;
      export function randomKey(cpuEntropyBits?: number ): Promise<string>;

优化(2020-02-21 15:14:56)

  • 上述第二点中,使用ts-node需要加上ts-ignore,而直接使用tsc编译为什么能过呢?
  • 原来在tsconfig.json中,我使用include属性将其声明文件包含到编译中了,故可使用.查看ts-node文档,只需要在执行ts-node 加上–files选项,这样将会加载tcconfig.json的include属性,这样就移除ts-ignore这个选项了

总结

最开始自己写*.d.ts,后续发现了居然还有dts-gen这样的工具.回头想想,利用这些工具,真香 ^0^,

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

create-react-app + electron开发桌面版应用

发表于 2019-09-07
目标

使用create-react-app 来配置electron 并打包,当然不想折腾环境的可以直接使用
模板来搭建环境呢.我选择了折腾,这里记录其中的关键点

步骤

  1. 使用create-react-app 创建项目
1
2
3
4
5
6
7
npx create-react-app react-electron
cd react-electron
npm install electron electron-builder image-size url --save-dev
// electron基础包
// electron-builder打包工具
// image-size 提供桌面图标的lib
// url electron的文件路径需要的东西
  1. 在public里创建electron.js文件(可以copy官网的,下面是我的最终版本),一定要放在public目录下,因为cra会把这个文件夹的文件复制到build目录下的呢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const electron = require('electron');
const platform = require('os').platform(); // 获取平台:https://nodejs.org/api/os.html#os_os_platform
// 控制app生命周期.
const app = electron.app;
// 浏览器窗口.
const BrowserWindow = electron.BrowserWindow;
const Menu = electron.Menu;

const path = require('path');
const url = require('url');
const ipc = electron.ipcMain
let mainWindow;

ipc.on('app close window', (sys, msg) => {
console.log(sys, msg)
mainWindow.close()
})

console.log(platform);

const setupMenu = () => {
const menu = new Menu();
mainWindow.setMenu(menu);

const template = [{
label: "Application",
submenu: [{
label: "About Application",
selector: "orderFrontStandardAboutPanel:"
},
{
type: "separator"
},
{
label: "Quit",
accelerator: "Command+Q",
click: () => {
quit();
}
}
]
}, {
label: "Edit",
submenu: [{
label: "Undo",
accelerator: "CmdOrCtrl+Z",
selector: "undo:"
},
{
label: "Redo",
accelerator: "Shift+CmdOrCtrl+Z",
selector: "redo:"
},
{
type: "separator"
},
{
label: "Cut",
accelerator: "CmdOrCtrl+X",
selector: "cut:"
},
{
label: "Copy",
accelerator: "CmdOrCtrl+C",
selector: "copy:"
},
{
label: "Paste",
accelerator: "CmdOrCtrl+V",
selector: "paste:"
},
{
label: "Select All",
accelerator: "CmdOrCtrl+A",
selector: "selectAll:"
}
]
}];

Menu.setApplicationMenu(Menu.buildFromTemplate(template));
};

function createWindow() {
// 创建一个浏览器窗口.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true, //允许使用node的方式引入
webSecurity: false, // 允许使用本地资源
},
backgroundColor: '#B1FF9D',
});

// 这里要注意一下,这里是让浏览器窗口加载网页。
// 如果是开发环境,则url为http://localhost:3000(package.json中配置)
// 如果是生产环境,则url为build/index.html
const startUrl = process.env.ELECTRON_START_URL || url.format({
pathname: path.join(__dirname, '/../build/index.html'),
protocol: 'file:',
slashes: true
});
setupMenu()
// 加载网页之后,会创建`渲染进程`
mainWindow.loadURL(startUrl);

// 打开chrome浏览器开发者工具.
if (startUrl.startsWith('http')) {
mainWindow.webContents.openDevTools();
}
mainWindow.webContents.openDevTools();
mainWindow.on('closed', function () {
mainWindow = null
});
}

app.on('ready', createWindow);

app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', function () {
if (mainWindow === null) {
createWindow();
}
});
  1. 修改package.json main字段修改的是程序的入口, build为electron-builder的打包的一些配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"main": "./public/electron.js",
"homepage": ".",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "electron .",
"electron-dev": "ELECTRON_START_URL=http://localhost:3000/ electron .",
"packager": "npm run build && rm -rf dist && electron-builder --mac"
},
"build": {
"productName": "example",
"appId": "com.example.server",
"files": [
"build/**/*",
"package.json"
],
"directories": {
"buildResources": "public"
}
}
  1. npm start 启动react项目
  2. npm run electron-dev 启动electron项目
  3. npm run build 打包react项目
  4. npm run electron 查看react打包的结果
  5. npm run package 打包mac应用(win需要再配置一下)
结果演示

源码
material-ui self host font

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

material-ui之自定义字体

发表于 2019-08-13
因由

因近期使需项目需要构建前端页面,故用reactjs作为前端的技术框架,ui选择material-ui v4.3.1作为一个基础.使用create-react-app初始化项目,其中踩了一些坑,记录在此,以做后续查看…

问题

根据项目需求,第一步需使用自定义的字体,根据官方文档typography.按照此方式配置,皆无法使字体问题文件生效.(是本地的字体,不是Google font)在一番搜索后,也许是应为版本原因,或者其他原因,可以在此github issues看到有很多朋友均遇见了此番问题,参照之后,也没有实际解决

转机出现

按照官方使用Roboto的字体,需要在index.html引入此文件

1
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

使用Chrome浏览器看了一下,其加载了一个css文件,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
...

那我是不是也可以建立一个文件按照此种方式引入呢?

于是仿照上述,在index.css加入字体的应用,最终实现自定义的字体加入material-ui

1
2
3
4
5
6
7
@font-face {
font-family: 'Gliber';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./theme/Gilbert_Bold-Preview_1004.woff2') format('woff2');
}

总结

  • 经过摸索,如在index.css中加入了自定义字体,则在theme中无需按照官方的方法再次在theme.js中引入字体了,按照如下格式引入即可
1
2
3
const typography = {
fontFamily: ["Gliber","Roboto"].join(","),//这里引入自定义的字体名字,并放在第一
};
  • 如果需要寻找更多的字体,可在google fonts(自带梯子)寻找,按照Roboto的字体方式引入即可.经过测试,字体的使用可不用梯子
  • 顺带在stackoverflow开启了第一个解答Self-host font added in Material-UI not working?
结果演示

源码
material-ui self host font

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

我应该怎样学习eos智能合约的开发?

发表于 2019-06-12

前言

  • 最近还有许多朋友通过微信加我好友,看来越来越多的人在学习区块链了,希望文章能够帮助到你们

初衷

  • 当时eos出来的时候,我才开始学习eos的智能合约开发,那时候我找得到的唯一的学习资料就是官网的hello world了.但是eos的版本(目前1.8了,cdt 1.6的更新得非常快,而官网的文章也没来的及更新,那时候Google也找不到给新手入门的资料,所以我就准备把自己学习的一些经验和资料分享给大家,希望你们能够快速入门,少走弯路

踩过的坑

  • 至于第一篇eos的文章,是讲述scatter如何使用,刚开始写文章,怕文字描述得不够清楚,就一步一步截图(现在想想真傻).由于mac的原因,截的图片是高清的,最终导致图片上传上去太大了,基本上一张图就一个视窗了,看起来真揪心.
  • 最终想了想,这样还是不行呢,所以又去找了个gif的图片放上去,这样基本上就减少了文章的长度.但是是无声的,虽然还是不够视频来得有感觉,但是我尽量做到详细,大家如果跳过了,可以多看两遍应该比直接看图来得好
  • 工具就这样定了,然后就是eosio的版本,由于之前对版本的认知不够,也怪自己的技术弱鸡,这其中就在纠结怎么装好eos,eosio.cdt,现在总结起来其实就是梯子不够高
  • …

你应该做的

  1. 不用再看本系列的文章了,所有的文章都已经过时了
  2. 如果你是入门eos的智能合约的开发,官网的教程永远是最好的入门资料—>eosio developter
  3. 官网的例子学习完,你可以参考这位大佬–>eos智能合约开发教程的系列文章.这位大佬的文章既有源码分析,也有c++知识,Linux的讲解,还有web前端的知识,更有node后端的分析,甚至还有IPFS的分析,filecoin的玩法.所以你是不应该错过这一些的文章的
  4. 现在eos的安装也方便了,docker直接搞定,如果不喜欢走docker,可以来这里看看eosio github
  5. 小工具,可以完全代替cleos,主要是安装比较简单,如果不跑本地的网络,完全在测试网络上运行,可以一试The most flexible & powerful command line tool for any developer to interact with an EOSIO chain
  6. 网络选择,测试网国内建议kylin test,国外建议jugle2 testnet ,梯子足够高的随意选择,如果安装了eos,直接本地运行吧
  7. 开发IDE 第一个–>eos stdudio,这个是本地版本的,各个操作系统都有, 第二个–>beosin 类似以太上的remix, 我选择的是vscode
  8. 小工具,js操作eos js4eos)

总结

eos网络已经运行一年了,然而我的技术却还是没有进步,纠结ing ^_^

eosio.token transfer正确的使用方式

发表于 2019-03-02

前言

  • 额,正确使用的方式不就是转账吗?直接调用不就可以了?
  • 是的,就是转账,但是
    1. 我要在账号中的合约中执行转账(这个之前的文章已经实现了,不是本篇文章的重点)
    2. 同时执行一个greet(std::string msg) action呢?
    3. 当其他任何玩家转账给我,我要知道转账的数据,同时给我捎句话呢
  • 那就使用一个action转账,一个action greet(msg)!!!
  • 目的是到了呢,但是这个是两个action呢?那就是有时间差的,貌似不符合我的需求哦,那该怎么办呢?
  • 好的,如果你看过之前的文章,其实你已经明白怎么做了,自定义dispatcher这个宏
  • 是的,完全正确,那么本篇文章就是接着之前的文章而来,把数据记录起来

游戏设计

  • 本次的合约来玩一个游戏叫做:paymsg
  • 游戏规则:
    • 任何人均可以参加游戏,每次参加至少游戏需花费1eos
    • 花费了这个费用的玩家 合约会自动将他转账的memo储存在区块链中,但是会加上我的签名
    • 当然,当你花费超过5个eos时,将会有一次免费的机会再次更新你储存的信息

      合约设计

  • 合约的具体设计请看源码就可以了
    1. 拦截transfer方法,获取转账信息
    2. 记住反序列化的时候,需要建一个struct来承载数据的信息
    3. 设计一个对外的action用于 用户免费更新储存的信息
    4. 此合约只是demo,仅供参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[[eosio::action]]
void upmsg(name player, std::string msg) {
require_auth(player);
// 这个地方可以使用这种方式初始化table 真不知道是哪里醉了
paymessage_index paymessages(get_self(), _code.value);
auto iterator = paymessages.find(player.value);
eosio_assert(iterator != paymessages.end(), "you are not in shaokun paymsg game");
eosio_assert(iterator->uamount >= 50000, "you must send more than 5 eos that can update your msg");
paymessages.modify(iterator, player, [&]( auto& row ) {
row.player = player;
row.msg = msg;
row.uamount = 0; //置为0 ,下次更改必须花5eos以上
});
}

void transfer(){
// 反序列化得到st_transfer结构体对象
// 就可以拿到transfer的交易信息了,而且是eosio.token发过的transfer
auto transfer_data = unpack_action_data<st_transfer>();
// eos是4位小数,由于不支持实际意义上的小数,所以这里要使用整形10000代替1eos
eosio_assert(transfer_data.quantity.amount >= 10000, "must more than 1 eos provide");
auto tr_from = transfer_data.from;
if(tr_from == get_self()) {
//自己转出去就不需要参加游戏了
return;
}
auto tr_amount = transfer_data.quantity.amount;
// https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/symbol.hpp
if(transfer_data.quantity.symbol != eosio::symbol("EOS",4)){
// 我们只收eos
return;
}
auto tr_memo = transfer_data.memo;
// 这里一定要按照这样的格式初始化table,不然无法写入数据
// 理论上这种方式初始化的table权限是一样的呢 paymessage_index paymessages(get_self(), _code.value);
paymessage_index paymessages(get_self(), get_self().value);
auto itr = paymessages.find(transfer_data.from.value);
if (itr == paymessages.end()){
paymessages.emplace(get_self(), [&]( auto& row ) {
row.player = tr_from;
row.uamount = tr_amount;
row.tamount = tr_amount;
row.msg = "welcome to shaokun paygame, your pay msg is :" + tr_memo;
});
}else {
paymessages.modify(itr, get_self(), [&]( auto& row ) {
row.tamount += tr_amount;
row.uamount += tr_amount ;
row.msg = "welcome come back to shaokun paygame:"+ tr_memo;
});
}
}

编译,部署,验证

  • 编译,买RAM,部署
  • 使用shaokunpay11部署合约
  • 使用eostoday1235进行添加数据
  • 使用eostoday1235进行两次的upmsg进行修改数据

    shaokun

  • 源码

填坑

  • 之前有一个困惑的问题是关于scatter和eosjs2无法一起使用,官方也没有例子,而现在找到了github
  • 以下我只是搬运工 ^.^
  • scatter + eosjs2
  • npm i -S scatterjs-core scatterjs-plugin-eosjs2 eosjs@20.0.0-beta3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import ScatterJS from 'scatterjs-core';
import ScatterEOS from 'scatterjs-plugin-eosjs2';
import {JsonRpc, Api} from 'eosjs';

ScatterJS.plugins( new ScatterEOS() );

const network = ScatterJS.Network.fromJson({
blockchain:'eos',
chainId:'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
host:'nodes.get-scatter.com',
port:443,
protocol:'https'
});
const rpc = new JsonRpc(network.fullhost());

ScatterJS.connect('hello shaokun', {network}).then(connected => {
if(!connected) return console.error('no scatter');

const eos = ScatterJS.eos(network, Api, {rpc, beta3:true});

ScatterJS.login().then(id => {
if(!id) return console.error('no identity');
const account = ScatterJS.account('eos');

eos.transact({
actions: [{
account: 'eosio.token',
name: 'transfer',
authorization: [{
actor: account.name,
permission: account.authority,
}],
data: {
from: account.name,
to: 'safetransfer',
quantity: '0.0001 EOS',
memo: account.name,
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
}).then(res => {
console.log('sent: ', res);
}).catch(err => {
console.error('error: ', err);
});
});
});
  • scatter + eosjs1
  • npm i -S scatterjs-core scatterjs-plugin-eosjs eosjs@16.0.9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import ScatterJS from 'scatterjs-core';
import ScatterEOS from 'scatterjs-plugin-eosjs';
import Eos from 'eosjs';

ScatterJS.plugins( new ScatterEOS() );

const network = ScatterJS.Network.fromJson({
blockchain:'eos',
chainId:'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
host:'nodes.get-scatter.com',
port:443,
protocol:'https'
});

ScatterJS.connect('YourAppName', {network}).then(connected => {
if(!connected) return console.error('no scatter');

const eos = ScatterJS.eos(network, Eos);

ScatterJS.login().then(id => {
if(!id) return console.error('no identity');
const account = ScatterJS.account('eos');
const options = {authorization:[`${account.name}@${account.authority}`]};
eos.transfer(account.name, 'safetransfer', '0.0001 EOS', account.name, options).then(res => {
console.log('sent: ', res);
}).catch(err => {
console.error('error: ', err);
});
});
});
  • scatter + web3
  • npm i -S scatterjs-core scatterjs-plugin-web3 web3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import ScatterJS from 'scatterjs-core';
import ScatterETH from 'scatterjs-plugin-web3';
import Web3 from 'web3';

ScatterJS.plugins( new ScatterETH() );

const network = ScatterJS.Network.fromJson({
blockchain:'eth',
chainId:'1',
host:'YOUR ETHEREUM NODE',
port:443,
protocol:'https'
});

ScatterJS.connect('YourAppName', {network}).then(connected => {
if(!connected) return console.error('no scatter');

const web3 = ScatterJS.web3(network, Web3);

ScatterJS.login().then(id => {
if(!id) return console.error('no identity');
// https://github.com/GetScatter/scatter-js/blob/master/packages/core/src/models/Blockchains.js
// 这里官方应该写错了,携程trx了,我们把它改过来
const account = ScatterJS.account('eth');
web3.eth.sendTransaction({
from: account.address,
to: '0x...',
value: '1000000000000000'
}).then(res => {
console.log('sent: ', res);
}).catch(err => {
console.error('error: ', err);
});
});
});
  • scatter + tronweb
  • npm i -S scatterjs-core scatterjs-plugin-tron tronweb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import ScatterJS from 'scatterjs-core';
import ScatterTron from 'scatterjs-plugin-tron';
import TronWeb from 'tronweb';

ScatterJS.plugins( new ScatterTron() );

const network = ScatterJS.Network.fromJson({
blockchain:'tron',
chainId:'1',
host:'api.trongrid.io',
port:443,
protocol:'https'
});

const httpProvider = new TronWeb.providers.HttpProvider(network.fullhost());
let tron = new TronWeb(httpProvider, httpProvider, network.fullhost());
tron.setDefaultBlock('latest');

ScatterJS.connect('YourAppName', {network}).then(connected => {
if(!connected) return console.error('no scatter');

tron = ScatterJS.trx(network, tron);

ScatterJS.login().then(id => {
if(!id) return console.error('no identity');
const account = ScatterJS.account('trx');
tron.trx.sendTransaction('TX...', 100).then(res => {
console.log('sent: ', res);
}).catch(err => {
console.error('error: ', err);
});
});
});

总结

  • 这样就实现了以太坊payable关键字的功能了
  • paymsg这个小game的逻辑,其实也和市面上大多数的菠菜类游戏的实现方式类似,只是其中的规则少了点
  • 至此,基于eos开发dapp的所有流程基本上是讲完了
  • 当然其中有很多点我也没有讲到,主要是我在学习的过程中,其他点没有遇到太大的问题.比如说multi_index的操作
  • 关于感想,就是要有耐心去学习这个,因为目前也没有其他的更好的资料,我比较推荐官方的教程eos smart contract
  • 还有一点,如果你使用eosiocpp编译wasm,abi文件,写合约的时候,有时候需要参考源码eosio,如果使用eosio.cdt编译的合约,eosio.cdt eoslib要去这里找(ps:这个地方受过很多伤…)
  • 最后,祝大家学的开心

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

一步一步教你在eos主网上发行自己的tokens

发表于 2019-02-23

前言

  • 2019年来了,祝大家新年快乐
  • 新的一年我们得继续努力呢,当然我也得继续来填坑
  • 接着上篇文章来,本篇文章会带着你一步一步在测试网(主网要真金白银呢)上发行自己的token
  • 本来官网的教程中已经有教怎么样发行自己的token了 eosio token issue,为什么我还要再写一遍呢?
  • 我觉得在我学习的过程中遇到了以下两个问题:
    1. 我没有eosio.token的账号呢,我怎么发行token呢?
    2. 那我就用自己的account进行发布,那这样的token可以使用吗?
    3. 发布token的合约是怎么样的呢?
  • 请自备有足够ram的kylin的账号呢,不然后面没法走哈

获取token源码

  • 相信跟着官网走得同学都是在本地的目录中去看源码的,这个源码版本目前只能使用eosiocpp编译,而我目前需要使用cdt来编译呢.
  • 所以我们得来这里获取支持cdt的源码eosio.contracts,这里存了系统的的contract源码,我们需要eosio.token的合约源码,我们第一步先把它download下来
  • 建议不用修改,除非增加新的业务逻辑

token源码分析

  • 文中有注释就不啰嗦了,各位同学自己看就好
  • 如果有同学看过旧版token的合约,可以发现还有有些变化的.不过我们不用care之前的旧版token合约,使用新版的就好
  • token中主要有两个table,account 和 currency_stats
    1. account主要负责记录用户的余额,这个table储存余额很有趣. 使用owner作为scope(这里可以理解为在这个就是一个单独的小table),然后在里面找有没有对应的symbol(前提这symbol得通过currency_stats table的检查,即已经create的)
    2. currency_stats主要负责记录发行的token的信息,主要包括最大发行量,和已经在市面上流通的token数量
  • token合约中含有create,issue,retire,transfer,open,close六个action,和get_balance,get_supply两个静态方法.其中创建一个token,只有create, issue, transfer是必须的,其他的可根据需求进行增添
    1. retire 燃烧自己的自己的token,这里token燃烧后总量不会变,相当于又返回给issuer了,如果需要此功能建议减少totalSupply而不是suppy
    2. open和close这两个方法我猜测是为了当issuer token给新账号的时候,可以设置这笔费用,个人觉得没什么鸟用
    3. get_balance和get_supply可以直接获取账户余额和已经issue的token

编译,部署,验证

  • 编译,部署
  • 购买ram
  • craete SHAOKUN token,总量10000个,比较值钱哈
  • issue 900个给eostoday1235,然后在retire掉(可以理解为销毁,不要这个action也可以)
  • 后续再转账,都可以在浏览器上查到对应的结果
  • 演示结果
  • 源码

总结

  • 按照上面的步骤,我们已经发行了一种token,注意是使用我们自己的账号
  • 回想一下dispatcher的文章,我们为什么要去判断 code == eosio.token的原因呢? 其实就是确保我们这个token是市面上流通的eos代币,而不是其他某个个人发布的token
  • 一个账号可以发行很多token的,只要symbol不一致就可以了,当然,这个账号也可以发行以eos为symbol的token
  • 最终再和以太坊的erc20 token对比一下,其实这都是一个官方的标准,我们如果想直接使用完全不用修改的,直接往主网部署就可以了.
  • 有时候有新的业务需求,我们完全可以根据自己的需求来更改token内部的某些业务逻辑,毕竟它只是一份部署在你账号的一个智能合约而已,而且这个合约你完全有权限随时更改呢

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos的dispatcher的使用(3 完结)

发表于 2019-01-26

前言

经过前面两篇文章的练习,相信各位同学都已经对自定义dispatcher已经有了自己的看法了,那么今天我们继续跟着官方的教程走,把接下来的知识点完整一下

自定义dispatcher 方式3

  • 这种方式官方说了,不能用于eosio-cpp生成abi,所以我也不详细去探究了,有兴趣的同学可以自己研究一下.官方最后总结这是最为灵活的一种方式呢
1
2
This dispatcher places most of the security logic and control inside the action handler, however, cannot use eosio-cpp's ABI generator.
此调度程序将大多数安全逻辑和控件放在操作处理程序中,但是,不能使用eosio-cpp的ABI生成器。
1
2
3
4
5
6
7
extern "C" void apply(uint64_t receiver, uint64_t code, uint64_t action) { 
switch(action) {
case name("upsert").value: return upsert(receiver, code);
case name("notify").value: return notify(receiver, code);
case name("erase").value: return erase(receiver, code);
}
}
1
2
To handle these requests, we move security logic that was otherwise in the dispatcher, into the action. This can provide more control, but may introduce redundancy, particularly for larger contracts. In the end, it accomplishes all the same points as the above patterns while allowing more information into the scope (namely code). Having access to code inside your action may be useful in some situations. This pattern provides the most flexibility in both the dispatcher and the action, hence "Fully Flexible."
为了处理这些请求,我们将调度程序中的安全逻辑移动到操作中。这可以提供更多控制,但可能会引入冗余,特别是对于大型合同。最后,它完成了与上述模式相同的所有点,同时允许更多信息进入范围(即代码)。在某些情况下,访问操作中的代码可能很有用。这种模式在调度程序和操作中提供了最大的灵活性,因此“完全灵活”。

自定义dispatcher 方式4

  • 官方说,上面自定义apply函数的时候,如果action少还是可以接受的,如果很多呢?就编程了体力劳动了,而且有可能出现漏洞.这显然和我们程序猿的做法不一致,所以官方最后给我们推荐了新的方法,功能类似,且减少体力劳动,不得不说还是很贴心的
1
2
3
4
5
6
7
8
9
10
11
#define EOSIO_DISPATCH_CUSTOM( TYPE, MEMBERS ) \
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( code == receiver ) { \
switch( action ) { \
EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
} \
  • 就是这么简单,虽然简单,但是我们还是得看看有什么魔法(以下内容为官方的介绍)
  1. 一定要使用这个 code == receiver的判断,而这个可以自动允许eosio.token进行transfer的方法呢
1
2
3
4
Checks if code==receiverand that the action is not transfer. If this condition is omitted, you may introduce a vulnerability into your contract. It allows the action through if the code is eosio.token and the action is transfer. This check prevents another contract with a transfer function from exploiting your contract.
```

2. 合约的实例化,可以看出和之前的apply方法是一样的,之前是在apply函数中实例化,这里直接传入一个实例

Instantiates the TYPE (this would be your standard C++11 class)

1
2

3. 说内部调用了execute_action方法,和我们之前的设计一样

Use the EOSIO_DISPATCH_HELPER macro, which inserts a case for each MEMBER into your switch. Inside this case, it calls execute_action using a pattern very similar to the one demonstrated in the Flexible/Compatible Dispatcher defined above.

1
2

4. 直接替换就可以了.不用做任何改变

You can then use this macro the same way you would with the provided EOSIO_DISPATCH macro.

1
2
3

* 既然这样,这里多了一个EOSIO_DISPATCH_HELPER的宏,我们还是得去源码看看它究竟做了什么我们才能放心呢
* 这里出现了一个小的惊喜,由于我目前的源码都是在eos去找,我居然没有找到EOSIO_DISPATCH_HELPER这个宏,这就尴尬了,转而我转向cdt的源码,总算找到了这个宏

// Helper macro for EOSIO_DISPATCH

#define EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
BOOST_PP_SEQ_FOR_EACH( EOSIO_DISPATCH_INTERNAL, TYPE, MEMBERS )

1
2


// Helper macro for EOSIO_DISPATCH_INTERNAL

#define EOSIO_DISPATCH_INTERNAL( r, OP, elem ) \
case eosio::name( BOOST_PP_STRINGIZE(elem) ).value: \
eosio::execute_action( eosio::name(receiver), eosio::name(code), &OP::elem ); \
break;

1
2


#define EOSIO_DISPATCH( TYPE, MEMBERS ) \
extern “C” { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( code == receiver ) { \
switch( action ) { \
EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
} \
/ does not allow destructor of thiscontract to run: eosio_exit(0); / \
} \
} \
} \

1
2
3
4
* 这里一下子三个宏了,我们关心的EOSIO_DISPATCH其实内部也是调用了EOSIO_DISPATCH_HELPER,所以这第三种方式定义的宏我觉得没有必要了哈,
* 哎,官方的文档貌似又落后了,不过已经习以为常了呢.

#### Security, Security, Security...

Your contract’s first line of security begins at your dispatcher. Understanding how the dispatching of actions to your contracts is handled is imperative to limiting exposure to logic inside your actions. Always take great caution when writing a custom dispatcher, and be aware of the security implications of each individual implementation method.

1
2

* 安全第一,所以应该小心使用apply函数,所以官方最后建议还是直接使用EOSIO_DISPATCHER就可以了,至于原因则如下

For simple contracts that only execute internal public actions, the EOSIO_DISPATCH is more than suitable, eliminates cruft and greatly decrease the chance of introducing logical errors.

1
2
3
4

#### 猜想
* 既然使用cdt中的EOSIO_DISPATCH就能够达到需求了,那我们该如何实现前面课程的监听呢?
* 通过查看EOSIO_DISPATCHER的判断,这里是不允许code为eosio.token的的操作的记录的,既然这样那我们改造一下EOSIO_DISPATCHER如下所示

#define EOSIO_DISPATCH_CUSTOM( TYPE, MEMBERS ) \
extern “C” { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( code == receiver || code == “eosio.token”_n.value && action == “transfer”_n.value) { \
switch( action ) { \
EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
} \
/ does not allow destructor of thiscontract to run: eosio_exit(0); / \
} \
} \
} \

1
2

* 那么最终结果和上篇文章的内容是一样的,只是我们也没有自己实现apply函数了,而是借助于helper这个宏帮助我们完成

#include <eosiolib/eosio.hpp>

#include <eosiolib/print.hpp>

using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {

public:
using contract::contract;

addressbook(name receiver, name code, datastream ds): contract(receiver, code, ds) {}

[[eosio::action]]
void upsert(name user, std::string first_name, std::string last_name) {
require_auth(user);
address_index addresses(_code, _code.value);
auto iterator = addresses.find(user.value);
if( iterator == addresses.end() )
{
addresses.emplace(user, & {
row.key = user;
row.first_name = first_name;
row.last_name = last_name;
});
send_summary(user, “ successfully emplaced record to addressbook”);
}
else {
std::string changes;
addresses.modify(iterator, user, & {
row.key = user;
row.first_name = first_name;
row.last_name = last_name;
});
send_summary(user, “ successfully modified record to addressbook”);
}
}

[[eosio::action]]
void erase(name user) {
require_auth(user);

address_index addresses(_self, _code.value);

auto iterator = addresses.find(user.value);
eosio_assert(iterator != addresses.end(), "Record does not exist");
addresses.erase(iterator);
send_summary(user, " successfully erased record from addressbook");

}

void transfer(uint64_t receiver, uint64_t code){
send_summary(name(code), “eosio.token transfer”);
}

private:
struct [[eosio::table]] person {
name key;
std::string first_name;
std::string last_name;
uint64_t primary_key() const { return key.value; }
};

void send_summary(name user, std::string message) {
action(
permission_level{get_self(),”active”_n},
get_self(),
“notify”_n,
std::make_tuple(user, name{user}.to_string() + message)
).send();
};

typedef eosio::multi_index<”people”_n, person> address_index;
};

#define EOSIO_DISPATCH_CUSTOM( TYPE, MEMBERS ) \
extern “C” { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
if( code == receiver || code == “eosio.token”_n.value && action == “transfer”_n.value) { \
switch( action ) { \
EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
} \
/ does not allow destructor of thiscontract to run: eosio_exit(0); / \
} \
} \
} \

EOSIO_DISPATCH_CUSTOM( addressbook, (upsert)(erase)(transfer) )
`

结果验证

  • 通过我们自定义的dispatcher,我们已经实现了和上篇文章同样的功能,且不用自己实现apply函数,极大的简化了操作
  • 当然,目前的方式我们允许的是eosio.token执行transfer操作,那么我们也可以仿照实现其他特殊合约的记录,比如说eosio.system(用来操作cpu,ram)的或者其他我们自定义的合约

shaokun

本课源码

总结

  • 上一篇最后一个问题,我们已经记录了当我们账户上eos的交易的数据,但是具体的数据我们还不知道呢?比如说转给谁?谁转的?转了多少?还有memo(这个也很重要的,有时候很多数据的有效性可以通过它来进行验证)
  • 第二个问题,我们怎么在链上发我们的自己的代币呢?而且这个代币要符合eos代币的标准标准(这里为什么要抛出这个问题呢?因为我只在本地的环境按照教程走,发过行过代币,但是在测试网,我没有eosio.token的账号,我居然懵逼了,不知道怎么发了),过程很简单,但是留给各位同学自己也思考一下
  • 三篇文章下来,可以看到跟着官网走,有好处,权威,也有坑,文档总是落后实际情况,那么我们能做点什么吗?我觉得能的,虽然语法变了,但是核心不变,所以我们掌握它的基础性内容,变来变去我们只需要去补一点它变化的内容就好了,如果你之前有基础,这应该就是手到擒来的事情了,只是,看我们还能否学得动哈^_^

啰嗦一句

  • 莫名其妙,就叫收拾东西走人了,具体原因都没有,寒冬的果实也落在了我的头上了…(ps:猜测是俺站错了队吧,整个团队一起走人了…)
  • 换个方向想,何尝不是又给了我们人生中多一次选择的机会呢?
  • 这应该是年前的最后一篇文章了,希望年后能够继续给大家分享一些dapp开发的知识,谢谢你的阅读

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

eos的dispatcher的使用(2)

发表于 2019-01-19

前言

希望阅读本篇文章的同学去看一下上一篇文章,不然思路断了接不上就有点麻烦了

多说两句

  • 话说以太坊的分叉计划又延期了,而此次的升级主要是针对底层的安全机制,所以可见写智能合约的最关键点还是安全第一吧,
  • eos一直被攻击,从未被停止.最近的eos因为一个derferred action的问题的漏洞又被黑客利用了,哎… 受伤的总是开发者
  • 大家写的合约还有一个点,就是安全.这里一个朋友已经开发了一款应用,也已经上线到主网,各种测试也做了,什么逻辑,压力,数据,前端页面的跳转等等… 然而当项目上线后,他找到我对我说,别人可以绕过前端代码直接调用的它的合约… 我只能说,这就是智能合约,你所写的合约是全世界的人(当然,如果你的项目很成功,那么黑客也是由兴趣来光顾的)都可以调用的,除非你做一些权限的验证.在eos上还好,可以通过升级合约来弥补,而且code还不用开源.但是在eth,code is law,code还要开源,所以各位同学想想这个合约安全的重要性.eos底层虽然还有bug,我们无法避免,但是我们还是应该对我们的应用层的逻辑进行严格的测试,这样开发的智能合约才经得住世界人民的考验,你说是不是呢?

自定义dispatcher 方式2

按照上篇文章的做法,那么我们实现了自己定义apply函数来取代EOSIO_DISPATCHER,但是实现的功能是一模一样的,那我们这样做的目的是什么呢?

  • 那么接下来我们继续按照官方的教程走,通过另外一种方式来实现.
  • 那么此种方式相对于上一中方式有什么区别呢?
1
2
This pattern provides more control over security at the expense of maintainability. Utilizing if...else if statements as opposed to a switch inherently provides more granularity.
此模式以可维护性为代价提供了对安全性的更多控制。利用if ... else if语句而不是switch提供更精细的控制。

好吧,既然看起来优点这么多,那么我们就看看是怎么样实现的吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern "C" {
void apply(uint64_t receiver, uint64_t code, uint64_t action) {
addressbook _addressbook(receiver);
if(code==receiver && action==name("upsert").value) {
execute_action(name(receiver), name(code), &addressbook::upsert );
}
else if(code==receiver && action==name("notify").value) {
execute_action(name(receiver), name(code), &addressbook::notify );
}
else if(code==receiver && action==name("erase").value) {
execute_action(name(receiver), name(code), &addressbook::erase );
}
else if(code==name("eosio.token").value && action==name("transfer").value) {
execute_action(name(receiver), name(code), &addressbook::transfer );
}
}
};
  • 初看和上一篇文章简直是一模一样,就是把switch换成了if…else,看来官方真实说的大实话呢
  • 不过,最后一个else多了一个判断,addressbook去调用transfer方法,可是我们没有啊,既然是这样,那我们就依样画葫芦,待会写一个吧
  • 第二点,最后一个else判断变成了code == eosio.token,这就是我们上篇文章说的这个code的意义了.而eosio.token是系统的账号,当发生eos或者其他代币时才会调用这个方法呢,那么我猜测一下这个判断是当发生eosio.token执行transfer的时候,将会执行我们合约transfer方法

结果验证

按照上面说的,我们就按照我们得思路先写一个吧,那么就加一个transfer方法如下.

1
2
3
void transfer(uint64_t receiver, uint64_t code){
send_summary(name(code), "eosio.token transfer");
}
  • 很简单,当发生transfer时,我们出发一个inline action来记录一下数据
  • 那我们看下结果,结果报错了????
  • why? 我们仅仅是转账啊,自己转出去或者别人转进来,现在都失败了

结果分析

Authorization failure with inline action sent to self

  • 权限不允许给自己发送内联action?
  • 这是什么情况?给自己发送内联action还要权限?
  • 想想想… 对了,要让自己的合约调用其他合约需要给eosio.code发送权限,那我们接下来试试看看是不是这个原因呢

shaokun

再次验证分析结果

  • 使用owner权限,给eosio.code授予active权限
  • 执行相互转账
  • 查看是否能够出发我们自定义的内联action(经过上一步的操作,和我们之前的经验,它应该是调用了我们得inline action,不过还是得看事实说话呢)
  • 去kylin,确实达到了我们的结果,无论转账转出或者转入,那么我们都发送了一个inline action
    shaokun

本课源码

  • 这里说一下这个源码,我已经编译好的abi和wasm文件,各位同学可以直接部署,如果要修改源码,就得自己编译了.不同的编译版本可能会有不同的eos语法,我这里用的是cdt1.3.2
  • 还有,部署的时候,最好文件夹名字和主合约的名字一样,这样可以可以避免一些想不到的奇奇怪怪的错误.

结论

  • 关于上一篇文章开篇提到的类似以太坊中payable关键字的功能,我想各位同学应该知道怎么实现了吧
  • 通过我们目前自己实现apply的方式,不用eosio.code授予权限,我们是可以拒绝或者转出eos的,当然这样的方式达到了目的,但是不够优雅
  • 通过结果,我们发现了我们也可以在合约中检查监听到eos的转出和转入,那么我们是否可以通过此做出相应的动作呢?答案是可以的,具体怎么使用我们以后的文章再说
  • 那么我们怎么优雅的实现拒绝账户eos的交易呢,我觉得最简单的方法就是调用eosio_assert(1==2);哈哈,^_^
  • 问题又来了?如果我不调用inline action,那么是不是我不用授权给eosio.code就可以实现转账呢?答案是可以的,本篇文章主要是为了看到具体的转账结果,所以用了inline action,相当于也给大家复习一下这个权限吧.至于这个不使用inline action的,这个可以交由各位同学自己去实现了哈
  • 问题又来了?那我要怎么拿取转账的信息呢?

关于我

区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。

1234

shaokun

点滴积累,聚少成多! 心中有善,不骄不躁

38 日志
© 2021 shaokun
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4
访问人数 访问总量 次