基于php编写桌面应用

如果将一个网站直接打包成一个桌面应用,有很多应用解决方案。比如:electron nwjs(node-webkit) 包括c# delphi等嵌入一个web插件。

但是我们知道要是考虑到h5和css3的支持,还是需要些友好的浏览器内核。而 electron和nwjs都是基于chormium的。

比如我们要将一个SAAS系统打包成一个桌面端软件。系统入口:http://bech.cc/saas/pms

这里介绍nwjs的解决方案。虽然很多人都说electron好, 但是我看到目前国内CocosCreator 、微信开发者工具都是用的nwjs,还有其它很多的软件用它做代理,比如一些数字货币交易所的桌面端软件(win\mac\linux)。

1. 下载nwjs

2. 新建项目文件夹 saas ,在根目录下创建 package.json{
"name": "saastest",
"version": "0.1.0",
"description": "BechTech",
"main": "http://bech.cc/saas/pms/",
"window": {
"width": 1280,
"height": 800,
"position" : "center",
"icon": "./icon.png"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dist": "build --tasks win-x64 --mirror https://npm.taobao.org/mirrors/nwjs/ .",
"start": "run --x64 --mirror https://npm.taobao.org/mirrors/nwjs/ ."
},
"build": {
"nwVersion": "0.46.3",
"win":{
"productName": "BechTech",
"copyright": "Copyright © 2009-2020 BechTech",
"icon": "./favicon.ico"
}
},
"dependencies": {},
"devDependencies": {
"nwjs-builder-phoenix": "^1.15.0"
}
}

3. 将nw.exe的目录放到环境PATH下。切换到目录下

nw saas  //启动查看本地窗口

这里启动后就相当于封装完了软件

4. 打包与发布

这里用官方推荐的 nwjs-builder-phoenix (推荐) 具体方法去git上查看

打包执行命令 npm run dist

后续会生成 dist\saas-0.1.0-win-x64 文件夹

这里要多重复实验才能成功,如果你英文阅读文档功底可以一次就够了。


我们再看另外一种情况,如果你的系统必须局域网内或者vpc访问

其实,这种情况我们经常会碰到,比如为了数据安全 或者考虑到无网环境运行。总之,你需要一个本地服务器。

我们一般的解决方案是配置一个本地的php开发环境。如果集成的会更快如 appserv xmpp phpstudy等等。

但是我们更希望要一个完全的可发布的一体化解决交付方式。

这里我们可以利用php5.5的命令快速实现一个解决方案。然后再用nwjs打包。每一个项目都是一个完美的桌面端软件且运行在本地的localhost.

下载php5.5 win-x86

1. 复制 php.exe php.ini-production ext\ extras\

2. 新建一个www文件夹。这里放的是网站程序 路由入口 index.php( 支持大部分框架 如 TP3)

3. 回到 nwjs 项目根目录下 写一个批处理程序 runserv.bat

@echo off
path = %path%;.\php\;
php.exe -S localhost:8080 -t php\www

4. 写一个main.js 用nodejs 的child_process模块执行批处理脚本

5. package.json 的main 加载 localhost:8080

6. 用nwjs打包

这样一个基于php本地架构的服务方案就完成了。

 

Nodejs+Selenium webdriver自动化测试

  • 安装webdriver

npm install selenium-webdriver –save

  • 安装chrome驱动

npm install chromedriver –save

这里一般会要求科学上网,最好手动去 http://npm.taobao.org/mirrors/chromedriver/ 找到对应版本复制到chrome安装目录下。并设置全局变量。

  • 初始化并打开一个网址

var webdriver = require(‘selenium-webdriver’);
var driver = new webdriver.Builder() .forBrowser(‘chrome’) .build();
driver.get(‘http://www.baidu.com’);

ethereum初探七:solidity

了解solidity 最好还是自己去阅读英文的官方文档加上一些中文文档辅助。

英文文档 当前最新版本地址:https://solidity.readthedocs.io/en/v0.5.8/

一. 注释

//单行注释

/* 多行注释 */

///可以生成文档的注释   /** 可以生成文档的注释 */

二. 数据类型

1 整型

通过int/uint定义。可以设置占用空间的大小。从8位开始,8位步长,最大到256位。int8、int16、int24、uint8……

int unit默认是256位。

uint一般用来表示货币数量和时间戳。solidity目前是不支持double float的。

如:uint balace = 1 ether;  uint time = 1 hours;

整型常量需要通过 constant 修饰。

2 布尔型

bool 常量值有true false。

3. 地址

以太坊中的地址位160位,即20个字节大小。

address public owner;

4 字节

固定长度字节数以 bytes 加上数字定义。如:bytes 2 foo;

动态长度字节以 bytes数组和string定义。bytes类似byte[],string 不提供长度和按索引访问。

bytes foo;  或者 string str = “hello”;

5 时间单位和货币单位

seconds minutes hours days weeks years 均可作为后缀,并进行相互转换。默认为秒。

wei finney szabo ether等是货币单位。

6 数组和多维数组

固定长度的数组 uint[5] x =[1,2,3,4,5];

动态数组 bytes32 [] names;

多维数组中需要指出的是 solidity中的行列和大多数编程语言是相反的。

7 映射/字典

映射的定义 mapping(_KeyType => _KeyValue)。如:mapping(string =>uint) public balance;

ethereum初探六:geth 控制台使用

以太坊开发离不开geth控制台的使用。直接的交互,很容易快速和以太坊的网络进行通讯。geth控制台实现了所有web3的API和Admin的API。

启动与退出

启动 最简单的命令 : geth console
退出输入 exit
输出日志 geth console 2 >> eth_log.log
使用 –verbosity 0 console 可以控制日记级别
连接一个geth的节点 geth attach path/ipc 或者 geth attach http://192.168.1.2:8545 或者 geth attach ws://192.168.1.2:8546

ethereum初探五:接入开发

以太坊下载后我们就可以启用geth进行启动节点,然后使用接口接入以太坊区块进行应用层的开发了。

首先安装 看我的文章:ethereum初探一:以太坊安装

在正式开始前,我们要先安装node,到node官网上下载linux64位的二进制包。

$cd /usr/local/src

$wget https://nodejs.org/dist/v8.9.4/node-v8.9.4-linux-x64.tar.xz

下载完了解压:

$xz -d node-v8.9.4-linux-x64.tar.xz

$tar -xvf node-v8.9.4-linux-x64.tar.xz

然后进行链接:

$ln -s /usr/local/src/node-v8.9.4-linux-x64/bin/node /usr/local/bin/node

$ln -s /usr/local/src/node-v8.9.4-linux-x64/bin/npm /usr/local/bin/npm

检查下:

$node -v

$npm -v

启动以太坊:

geth –datadir=/chaindata/.eth –nodiscover –rpc –rpcapi  –rpccorsdomain=’*’ –rpcaddr=”localhost”  –ws –wsorigins=’*’  console

这里先为了测试可以用*号,后续为了安全一定要把各项参数都改下。变内网做安全防火墙策略。具体细节不表。

安装web3,这里有个坑。网上目前资料很少,直接去官网才发现web3已经变成1.0,很多不兼容。而且1.0还是测试阶段。所以我还是选用了0.20.X. 我们先换下npm安装源,国外那源国内能2个小时不带动的。

$npm config set registry http://registry.npm.taobao.org/

查看下:$npm get registry.

$npm install web3@^0.20.1

没有git 先安装下git。截止我发稿最新的是0.20.3

$vi base.js

var Web3 = require(‘web3’);
var web3 = new Web3();
console.log(web3);

web3.setProvider(new web3.providers.HttpProvider(‘http://localhost:8545’));
console.log(web3.version);
var coinbase = web3.eth.accounts[0];
console.log(coinbase);

$node base.js

ethereum初探四:geth命令详解

命令用法

geth [选项] 命令 [命令选项] [参数…]

版本:

1.7.3-stable

命令:

account    管理账户
attach     启动交互式JavaScript环境(连接到节点)
bug        上报bug Issues
console    启动交互式JavaScript环境
copydb     从文件夹创建本地链
dump       Dump(分析)一个特定的块存储
dumpconfig 显示配置值
export     导出区块链到文件
import     导入一个区块链文件
init       启动并初始化一个新的创世纪块
js         执行指定的JavaScript文件(多个)
license    显示许可信息
makecache  生成ethash验证缓存(用于测试)
makedag    生成ethash 挖矿DAG(用于测试)
monitor    监控和可视化节点指标
removedb   删除区块链和状态数据库
version    打印版本号
wallet     管理Ethereum预售钱包
help,h     显示一个命令或帮助一个命令列表

ETHEREUM选项:

--config value          TOML 配置文件
--datadir “xxx”         数据库和keystore密钥的数据目录
--keystore              keystore存放目录(默认在datadir内)
--nousb                 禁用监控和管理USB硬件钱包
--networkid value       网络标识符(整型, 1=Frontier, 2=Morden (弃用), 3=Ropsten, 4=Rinkeby) (默认: 1)
--testnet               Ropsten网络:预先配置的POW(proof-of-work)测试网络
--rinkeby               Rinkeby网络: 预先配置的POA(proof-of-authority)测试网络
--syncmode "fast"       同步模式 ("fast", "full", or "light")
--ethstats value        上报ethstats service  URL (nodename:secret@host:port)
--identity value        自定义节点名
--lightserv value       允许LES请求时间最大百分比(0 – 90)(默认值:0) 
--lightpeers value      最大LES client peers数量(默认值:20)
--lightkdf              在KDF强度消费时降低key-derivation RAM&CPU使用

开发者(模式)选项:

--dev               使用POA共识网络,默认预分配一个开发者账户并且会自动开启挖矿。
--dev.period value  开发者模式下挖矿周期 (0 = 仅在交易时) (默认: 0)

ETHASH 选项:

--ethash.cachedir                        ethash验证缓存目录(默认 = datadir目录内)
--ethash.cachesinmem value               在内存保存的最近的ethash缓存个数  (每个缓存16MB ) (默认: 2)
--ethash.cachesondisk value              在磁盘保存的最近的ethash缓存个数 (每个缓存16MB) (默认: 3)
--ethash.dagdir ""                       存ethash DAGs目录 (默认 = 用户hom目录)
--ethash.dagsinmem value                 在内存保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 1)
--ethash.dagsondisk value                在磁盘保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 2)

 

交易池选项:

--txpool.nolocals            为本地提交交易禁用价格豁免
--txpool.journal value       本地交易的磁盘日志:用于节点重启 (默认: "transactions.rlp")
--txpool.rejournal value     重新生成本地交易日志的时间间隔 (默认: 1小时)
--txpool.pricelimit value    加入交易池的最小的gas价格限制(默认: 1)
--txpool.pricebump value     价格波动百分比(相对之前已有交易) (默认: 10)
--txpool.accountslots value  每个帐户保证可执行的最少交易槽数量  (默认: 16)
--txpool.globalslots value   所有帐户可执行的最大交易槽数量 (默认: 4096)
--txpool.accountqueue value  每个帐户允许的最多非可执行交易槽数量 (默认: 64)
--txpool.globalqueue value   所有帐户非可执行交易最大槽数量  (默认: 1024)
--txpool.lifetime value      非可执行交易最大入队时间(默认: 3小时)

 

性能调优的选项:

--cache value                分配给内部缓存的内存MB数量,缓存值(最低16 mb /数据库强制要求)(默认:128)
--trie-cache-gens value      保持在内存中产生的trie node数量(默认:120)

 

帐户选项:

--unlock value              需解锁账户用逗号分隔
--password value            用于非交互式密码输入的密码文件

 

API和控制台选项:

--rpc                       启用HTTP-RPC服务器
--rpcaddr value             HTTP-RPC服务器接口地址(默认值:“localhost”)
--rpcport value             HTTP-RPC服务器监听端口(默认值:8545)
--rpcapi value              基于HTTP-RPC接口提供的API
--ws                        启用WS-RPC服务器
--wsaddr value              WS-RPC服务器监听接口地址(默认值:“localhost”)
--wsport value              WS-RPC服务器监听端口(默认值:8546)
--wsapi  value              基于WS-RPC的接口提供的API
--wsorigins value           websockets请求允许的源
--ipcdisable                禁用IPC-RPC服务器
--ipcpath                   包含在datadir里的IPC socket/pipe文件名(转义过的显式路径)
--rpccorsdomain value       允许跨域请求的域名列表(逗号分隔)(浏览器强制)
--jspath loadScript         JavaScript加载脚本的根路径(默认值:“.”)
--exec value                执行JavaScript语句(只能结合console/attach使用)
--preload value             预加载到控制台的JavaScript文件列表(逗号分隔)

 

网络选项:

--bootnodes value    用于P2P发现引导的enode urls(逗号分隔)(对于light servers用v4+v5代替)
--bootnodesv4 value  用于P2P v4发现引导的enode urls(逗号分隔) (light server, 全节点)
--bootnodesv5 value  用于P2P v5发现引导的enode urls(逗号分隔) (light server, 轻节点)
--port value         网卡监听端口(默认值:30303)
--maxpeers value     最大的网络节点数量(如果设置为0,网络将被禁用)(默认值:25)
--maxpendpeers value    最大尝试连接的数量(如果设置为0,则将使用默认值)(默认值:0)
--nat value             NAT端口映射机制 (any|none|upnp|pmp|extip:<IP>) (默认: “any”)
--nodiscover            禁用节点发现机制(手动添加节点)
--v5disc                启用实验性的RLPx V5(Topic发现)机制
--nodekey value         P2P节点密钥文件
--nodekeyhex value      十六进制的P2P节点密钥(用于测试)

 

矿工选项:

--mine                  打开挖矿
--minerthreads value    挖矿使用的CPU线程数量(默认值:8)
--etherbase value       挖矿奖励地址(默认=第一个创建的帐户)(默认值:“0”)
--targetgaslimit value  目标gas限制:设置最低gas限制(低于这个不会被挖?) (默认值:“4712388”)
--gasprice value        挖矿接受交易的最低gas价格
--extradata value       矿工设置的额外块数据(默认=client version)

 

GAS价格选项:

--gpoblocks value      用于检查gas价格的最近块的个数  (默认: 10)
--gpopercentile value  建议gas价参考最近交易的gas价的百分位数,(默认: 50)

 

虚拟机的选项:

--vmdebug        记录VM及合约调试信息

 

日志和调试选项:

--metrics            启用metrics收集和报告
--fakepow            禁用proof-of-work验证
--verbosity value    日志详细度:0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3)
--vmodule value      每个模块详细度:以 <pattern>=<level>的逗号分隔列表 (比如 eth/*=6,p2p=5)
--backtrace value    请求特定日志记录堆栈跟踪 (比如 “block.go:271”)
--debug                     突出显示调用位置日志(文件名及行号)
--pprof                     启用pprof HTTP服务器
--pprofaddr value           pprof HTTP服务器监听接口(默认值:127.0.0.1)
--pprofport value           pprof HTTP服务器监听端口(默认值:6060)
--memprofilerate value      按指定频率打开memory profiling    (默认:524288)
--blockprofilerate value    按指定频率打开block profiling    (默认值:0)
--cpuprofile value          将CPU profile写入指定文件
--trace value               将execution trace写入指定文件

 

WHISPER实验选项:

--shh                        启用Whisper
--shh.maxmessagesize value   可接受的最大的消息大小 (默认值: 1048576)
--shh.pow value              可接受的最小的POW (默认值: 0.2)

 

弃用选项:

--fast     开启快速同步
--light    启用轻客户端模式

 

其他选项:

–help, -h    显示帮助

 

版权:

Copyright 2013-2017 The go-ethereum Authors

ECS挂载数据盘

运行 fdisk -l 命令查看实例是否有数据盘。

创建一个单分区数据盘,依次执行以下命令:

  1. 运行 fdisk /dev/vdb:对数据盘进行分区。
  2. 输入 n 并按回车键:创建一个新分区。
  3. 输入 p 并按回车键:选择主分区。因为创建的是一个单分区数据盘,所以只需要创建主分区。

    说明:如果要创建 4 个以上的分区,您应该创建至少一个扩展分区,即选择 e

  4. 输入分区编号并按回车键。因为这里仅创建一个分区,可以输入 1。
  5. 输入第一个可用的扇区编号:按回车键采用默认值 1(ECS是2048)。
  6. 输入最后一个扇区编号:因为这里仅创建一个分区,所以按回车键采用默认值。
  7. 输入 wq 并按回车键,开始分区。

查看新的分区:运行命令 fdisk -l。如果出现以下信息,说明已经成功创建了新分区 /dev/vdb1

在新分区上创建一个文件系统:运行命令 mkfs.ext3 /dev/vdb1

/etc/fstab 写入新分区信息:运行命令 echo /dev/vdb1 /mnt ext3 defaults 0 0 >> /etc/fstab

注意:Ubuntu 12.04 不支持 barrier,所以对该系统正确的命令是:echo '/dev/vdb1 /mnt ext3 barrier=0 0 0' >> /etc/fstab

如果需要把数据盘单独挂载到某个文件夹,比如单独用来存放网页,请将以上命令 /mnt 替换成所需的挂载点路径。

查看 /etc/fstab 中的新分区信息:运行命令 cat /etc/fstab

挂载文件系统:运行命令 mount /dev/vdb1 /mnt

查看目前磁盘空间和使用情况:运行命令 df -h。如果出现新建文件系统的信息,说明挂载成功,可以使用新的文件系统了。

ethereum初探三:在没有第三方下筹集资金

众筹你的创意

有时一个好创意需要大量的资金和集体努力。你可以要求捐赠,但捐赠者更愿意捐赠给他们更确定的项目,这样就能得到足够的支持和资金。这是一个众筹将会很理想的例子:你设定了一个目标,并在最后期限前完成。如果你错过了你的目标,这些捐赠会被返还,从而减少捐赠者的风险。由于代码是开放的和可审计的,所以不需要一个集中的、可信任的平台,因此每个人只需要支付的费用就是燃气费。

TOKENS AND DAOS

在这个例子中,我们将通过解决两个重要的问题来写一个更好的众筹:如何管理和保存奖励,以及在筹集资金后如何使用这些资金。

众筹的回报通常是由一个中心不变的数据库来处理的,该数据库可以跟踪所有捐赠者:任何错过了活动截止日期的人都无法进入,任何改变主意的捐赠者都无法离开。相反,我们将采用分散的方式,并创建一个代币来跟踪奖励,任何贡献的人都会得到一个令他们可以交易、出售或保留的代币。当给出实物奖励的时候,生产者只需要交换真实产品的代币。捐赠者可以保留他们的代币,即使这个项目没有实现它的目标,也可以作为一个纪念品。

而且,一般来说,那些在资金筹集和管理不善的情况下,资金如何使用的人也不能有任何发言权,这往往导致项目根本无法提供任何东西。在这个项目中,我们将使用一个民主的组织,它必须批准任何来自系统的资金。这通常被称为“众包”或“股东权益”,这是非常重要的,在某些情况下,代币可以是奖励本身,尤其是在一些项目中,一群人聚集在一起,建立共同的公共利益。

Get the necessary contracts

  • If you are just testing, switch the wallet to the testnet and start mining.
  • First of all, create a fixed supply token. For this example, we are going to create a supply of 100, use the name gadgets, the box emoji (📦) as a symbol and 0 decimal places. Deploy it and save the address.
  • Now create a shareholder association. In this example we are going to use the address of the token we just created as the Shares Address, a minimum quorum of 10, and 1500 minutes (25 hours) as the voting time. Deploy this contract and save the address.

THE CODE

Now copy this code and let’s create the crowdsale:


pragma solidity ^0.4.16;

interface token {
    function transfer(address receiver, uint amount);
}

contract Crowdsale {
    address public beneficiary;
    uint public fundingGoal;
    uint public amountRaised;
    uint public deadline;
    uint public price;
    token public tokenReward;
    mapping(address => uint256) public balanceOf;
    bool fundingGoalReached = false;
    bool crowdsaleClosed = false;

    event GoalReached(address recipient, uint totalAmountRaised);
    event FundTransfer(address backer, uint amount, bool isContribution);

    /**
     * Constrctor function
     *
     * Setup the owner
     */
    function Crowdsale(
        address ifSuccessfulSendTo,
        uint fundingGoalInEthers,
        uint durationInMinutes,
        uint etherCostOfEachToken,
        address addressOfTokenUsedAsReward
    ) {
        beneficiary = ifSuccessfulSendTo;
        fundingGoal = fundingGoalInEthers * 1 ether;
        deadline = now + durationInMinutes * 1 minutes;
        price = etherCostOfEachToken * 1 ether;
        tokenReward = token(addressOfTokenUsedAsReward);
    }

    /**
     * Fallback function
     *
     * The function without name is the default function that is called whenever anyone sends funds to a contract
     */
    function () payable {
        require(!crowdsaleClosed);
        uint amount = msg.value;
        balanceOf[msg.sender] += amount;
        amountRaised += amount;
        tokenReward.transfer(msg.sender, amount / price);
        FundTransfer(msg.sender, amount, true);
    }

    modifier afterDeadline() { if (now >= deadline) _; }

    /**
     * Check if goal was reached
     *
     * Checks if the goal or time limit has been reached and ends the campaign
     */
    function checkGoalReached() afterDeadline {
        if (amountRaised >= fundingGoal){
            fundingGoalReached = true;
            GoalReached(beneficiary, amountRaised);
        }
        crowdsaleClosed = true;
    }


    /**
     * Withdraw the funds
     *
     * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,
     * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw
     * the amount they contributed.
     */
    function safeWithdrawal() afterDeadline {
        if (!fundingGoalReached) {
            uint amount = balanceOf[msg.sender];
            balanceOf[msg.sender] = 0;
            if (amount > 0) {
                if (msg.sender.send(amount)) {
                    FundTransfer(msg.sender, amount, false);
                } else {
                    balanceOf[msg.sender] = amount;
                }
            }
        }

        if (fundingGoalReached && beneficiary == msg.sender) {
            if (beneficiary.send(amountRaised)) {
                FundTransfer(beneficiary, amountRaised, false);
            } else {
                //If we fail to send the funds to beneficiary, unlock funders balance
                fundingGoalReached = false;
            }
        }
    }
}

CODE HIGHLIGHTS

Notice that in the Crowdsale function (the one that is called upon contract creation), how the variables deadline and fundingGoal are set:

fundingGoal = fundingGoalInEthers * 1 ether;
deadline = now + durationInMinutes * 1 minutes;
price = etherCostOfEachToken * 1 ether;

Those are some of the special keywords in solidity that help you code, allowing you to evaluate some things like 1 ether == 1000 finney or 2 days == 48 hours. Inside the system all ether amounts are kept track in wei, the smallest divisible unit of ether. The code above converts the funding goal into wei by multiplying it by 1,000,000,000,000,000,000 (which is what the special keyword ether converts into). The next line creates a timestamp that is exactly X minutes away from today by also using a combination of the special keywords now and minutes. For more global keywords, check the solidity documentation on Globally available variables.

The following line will instantiate a contract at a given address:

tokenReward = token(addressOfTokenUsedAsReward);

Notice that the contract understands what a token is because we defined it earlier by starting the code with:

interface token { function transfer(address receiver, uint amount){  } }

This doesn’t fully describe how the contract works or all the functions it has, but describes only the ones this contract needs: a token is a contract with a transfer function, and we have one at this address.

HOW TO USE

Go to contracts and then deploy contract:

Crowdsale deployment

  • Put the address of the organization you just created in the field if successful, send to.
  • Put 250 ethers as the funding goal
  • If you are just doing it for a test or demonstration, put the crowdsale duration as 3-10 minutes, but if you are really raising funds you can put a larger amount, like 45,000 (31 days).
  • The ether cost of each token should be calculated based on how many tokens you are putting up for sale (a maximum of how many you added as “initial supply” of your token on the previous step). In this example, put 5 ethers.
  • The address of the token you created should be added to the token reward address

Put a gas price, click deploy and wait for your crowdsale to be created. Once the crowdsale page is created, you now need to deposit enough rewards so it can pay the rewards back. Click the address of the crowdsale, then deposit and send 50 gadgets to the crowdsale.

I have 100 gadgets. Why not sell them all?

This is a very important point. The crowdsale we are building will be completely controlled by the token holders. This creates the danger that someone controlling 50%+1 of all the tokens will be able to send all the funds to themselves. You can try to create special code on the association contract to prevent these hostile takeovers, or you can instead have all the funds sent to a simple address. To simplify we are simply selling off half of all the gadgets: if you want to further decentralize this, split the remaining half between trusted organizations.

RAISE FUNDS

Once the crowdsale has all the necessary tokens, contributing to it is easy and you can do it from any ethereum wallet: just send funds to it. You can see the relevant code bit here:

function () {
    require(!crowdsaleClosed);
    uint amount = msg.value;
    // ...

The unnamed function is the default function executed whenever a contract receives ether. This function will automatically check if the crowdsale is active, calculate how many tokens the caller bought and send the equivalent. If the crowdsale has ended or if the contract is out of tokens the contract will throw meaning the execution will be stopped and the ether sent will be returned (but all the gas will be spent).

Crowdsale error

This has the advantage that the contract prevents falling into a situation that someone will be left without their ether or tokens. In a previous version of this contract we would also self destruct the contract after the crowdsale ended: this would mean that any transaction sent after that moment would lose their funds. By creating a fallback function that throws when the sale is over, we prevent anyone losing money.

The contract has a safeWithdrawl() function, without any parameters, that can be executed by the beneficiary to access the amount raised or by the funders to get back their funds in the case of a failed fundraise.

Crowdsale execution

Extending the crowdsale

WHAT IF THE CROWDSALE OVERSHOOTS ITS TARGET?

In our code, only two things can happen: either the crowdsale reaches its target or it doesn’t. Since the token amount is limited, it means that once the goal has been reached no one else can contribute. But the history of crowdfunding is full of projects that overshoot their goals in much less time than predicted or that raised many times over the required amount.

UNLIMITED CROWDSALE

So we are going to modify our project slightly so that instead of sending a limited set of tokens, the project actually creates a new token out of thin air whenever someone sends them ether. First of all, we need to create a Mintable token.

Then modify the crowdsale to rename all mentions of transfer to mintToken:

contract token { function mintToken(address receiver, uint amount){  } }
// ...
    function () {
        // ...
        tokenReward.mintToken(msg.sender, amount / price);
        // ...
    }

Once you published the crowdsale contract, get its address and go into your Token Contract to execute a Change Ownership function. This will allow your crowdsale to call the Mint Token function as much as it wants.

Warning: This opens you to the danger of hostile takeover. At any point during the crowdsale anyone who donates more than the amount already raised will be able to control the whole pie and steal it. There are many strategies to prevent that, but implementing will be left as an exercise to the reader:

  • Modify the crowdsale such that when a token is bought, also send the same quantity of tokens to the founder’s account so that they always control 50% of the project
  • Modify the Organization to create a veto power to some trusted third party that could stop any hostile proposal
  • Modify the token to allow a central trusted party to freeze token accounts, so as to require a verification that there isn’t any single entity controlling a majority of them

SCHEDULING A CALL

Ethereum contracts are passive, in that they can only do something once they have been activated. Fortunately there are some third party community services that provide that service for you: the Ethereum Alarm Clock is an open marketplace where anyone can receive ether to execute scheduled calls or pay ether to schedule them. This tutorial will be using the 0.6.0 version of the Alarm service. Documentation for this version available here.

Add the alarm clock

First, you need to add the contract to your watchlist. Go to your Contracts tab and then Watch contract (not deploy contract): Give the name “Ethereum Alarm Clock”, use 0xe109EcB193841aF9dA3110c80FDd365D1C23Be2a as address (the icon should look like a green eyed creature) and add this code as the Json Interface:

[{"constant":false,"inputs":[{"name":"contractAddress","type":"address"},{"name":"abiSignature","type":"bytes4"},{"name":"targetBlock","type":"uint256"}],"name":"scheduleCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"contractAddress","type":"address"},{"name":"abiSignature","type":"bytes4"},{"name":"targetBlock","type":"uint256"},{"name":"suggestedGas","type":"uint256"},{"name":"gracePeriod","type":"uint8"}],"name":"scheduleCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"getDefaultPayment","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"getDefaultFee","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"contractAddress","type":"address"},{"name":"abiSignature","type":"bytes4"},{"name":"targetBlock","type":"uint256"},{"name":"suggestedGas","type":"uint256"}],"name":"scheduleCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"callAddress","type":"address"}],"name":"getNextCallSibling","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"callAddress","type":"address"}],"name":"isKnownCall","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"basePayment","type":"uint256"}],"name":"getMinimumCallCost","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"contractAddress","type":"address"},{"name":"abiSignature","type":"bytes4"},{"name":"targetBlock","type":"uint256"},{"name":"suggestedGas","type":"uint256"},{"name":"gracePeriod","type":"uint8"},{"name":"basePayment","type":"uint256"}],"name":"scheduleCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"getMinimumCallCost","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"contractAddress","type":"address"},{"name":"abiSignature","type":"bytes4"},{"name":"targetBlock","type":"uint256"},{"name":"suggestedGas","type":"uint256"},{"name":"gracePeriod","type":"uint8"},{"name":"basePayment","type":"uint256"},{"name":"baseFee","type":"uint256"}],"name":"scheduleCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"basePayment","type":"uint256"},{"name":"baseFee","type":"uint256"}],"name":"getMinimumCallCost","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"getMinimumCallGas","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"getCallWindowSize","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"blockNumber","type":"uint256"}],"name":"getNextCall","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"getMinimumGracePeriod","outputs":[{"name":"","type":"uint256"}],"type":"function"}]

Tip: if you are on the test net, use the address 0xb8Da699d7FB01289D4EF718A55C3174971092BEf instead

Click on the green icon that you just added and then choose a function call under the Write to contract title. There will be multiple Schedule Call functions, choose the first one, that only has three fields:

  • contractAddress will be the address of the deployed crowdsale contract.
  • abiSignature will be 0x01cb3b20. You can figure out the signature for any function by trying to execute them but in the confirmation window, instead of typing your password, copy the code in the Data field. The function signature is the first 10 characters in bold.
  • targetBlock is the block number in which you want the function to be executed, read below to calculate an estimation.

The crowdsale contract specifies a deadline using a timestamp, but the Alarm clock currently schedules calls based on block numbers. Since ethereum has a block time of approximately 17 seconds, we need to compute a block number that is going to be probabilistically past the deadline. We can do this with the formula current_block_number + duration_in_minutes * 60 / 17 + buffer where buffer is a number of blocks that is sufficiently large that you can rely on it occurring after the crowdsale deadline. For short crowdsales less than a day in duration a buffer of 200 blocks should be sufficient. For durations closer to 30 days, you should probably pick a number closer to 5,000.

You can use the following chart for rough estimates for how many blocks to add to the current block to compute the targetBlock.

  • 1 hour duration (60 minutes): 212 blocks
  • 1 day duration (1440 minutes): 5082 blocks
  • 1 week duration (10,800 minutes): 38,117 blocks
  • 1 month duration (44,640 minutes): 157,553 blocks

On the Send field, you need to send enough ether to pay the transaction fee, plus some more to pay the scheduler. Any extra money sent will be refunded, so sending at least 0.25 ether will probably keep you on the safe side.

After that, just press execute and your call will be scheduled. There are no guarantees that someone will actually execute it, so you should check back after the deadline has passed to be sure.