We are going to test paying into an open source crowdfunding contract which has been uploaded here:
https://rinkeby.etherscan.io/address/0xd42a26792b8ae963622186b84f2bcdddd02c2d31
The contract source code and all related utilities can be found here: https://github.com/invisible-college/democracy/blob/master/ZcashEscrow.solc
The contract was uploaded on 2017-05-21 23:56:21 EDT
INFO [05-21|23:56:21] Submitted contract creation fullhash=0x65daa9c4b2e2ebde13fff85a30c89947e397a8b53d7d9ebe74ec185938f43380 contract=0xd42a26792b8ae963622186b84f2bcdddd02c2d31
I
and initialized on 2017-05-23 11:45:04
INFO [05-23|11:45:04] Submitted transaction fullhash=0x4fa35b8c96982620deed958703d1221955920ac0d48433ec4147dff2e931b4cc recipient=0xd42a26792b8ae963622186b84f2bcdddd02c2d31
INFO [05-23|11:45:05] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=2.106ms mgasps=0.000 number=236254 hash=6eb319…6f82f8
with the following parameters:
instance.initialize(web3.toWei(100, "ether"), [eth.accounts[1]], 1495684800, {from: eth.coinbase, gas: 250000}, function(err, data, something) { console.log("err: " + err); console.log("data: " + data); console.log("something: " + something); })
That is, we want to raise 100 ETH by May 25th 0:00 EDT (UNIX epoch time 1495684800). If the goal has been reached by that time, it will go through to a beneficiary (a test account that I own) at 0x83fa180e1557b6598b15d2de154bbef657f8fafe.
The contract arbiter gets 1% of the raised funds rounded to the nearest wei, only if the contract goes through. If you run this contract on your own (you become the arbiter), you can modify the source code to give yourself a different percentage if you choose.
This is a preamble to running a real crowdfund on Ethereum mainnet for a distributed seed funding for Lean Startup founders. https://www.gofundme.com/leanstartupfundingexperiments
That's great, but how do you participate in historical greatness?
First, you'll need an Ethereum node that you can connect to. For our tutorial, we'll assume you first followed the instructions here:
https://gist.github.com/cryptogoth/10a98e8078cfd69f7ca892ddbdcf26bc
That is, you're running a Rinkeby testnet node on localhost:8545, you have about 3 Rinkeby ETH
in an account that you know the password to, you started geth with the personal module
loaded, and you attached a geth console to it.
In your geth console,
> geth attach
Welcome to the Geth JavaScript console!
instance: Geth/v1.6.1-stable-021c3c28/darwin-amd64/go1.8.1
coinbase: 0xb2e9fe08ca9a0323103883fe12c9609ed380f475
at block: 227656 (Sun, 21 May 2017 23:55:27 EDT)
datadir: /Users/ppham/.rinkeby
modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
Load the ABI, which you can also get from the Github repo above:
abi = [{"constant":true,"inputs":[],"name":"isBeforeDeadline","outputs":[{"name":"retVal","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"safeKill","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"payoutsETH","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"beneficiary","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"contributionsETH","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"deadlineEpoch","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"payoutETH","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fundingGoal","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"amountRaised","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"description","type":"bytes32"}],"name":"bump","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"amountRaisedBTC","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getNow","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"description","type":"bytes32"}],"name":"payIn","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"backer_history","outputs":[{"name":"ethAddr","type":"address"},{"name":"amount","type":"uint256"},{"name":"timestamp","type":"uint256"},{"name":"description","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"amountRaisedETH","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"creationTime","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getContribution","outputs":[{"name":"retVal","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"feeWithdrawn","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"open","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"safeWithdrawal","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fundingGoalInWei","type":"uint256"},{"name":"_beneficiary","type":"address"},{"name":"_deadlineEpoch","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"beneficiary","type":"address"},{"indexed":false,"name":"amountRaised","type":"uint256"}],"name":"GoalReached","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"beneficiary","type":"address"},{"indexed":false,"name":"amountRaised","type":"uint256"}],"name":"PayOut","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ReturnContribution","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"BuyIn","type":"event"}]
[{
constant: true,
inputs: [],
name: "isBeforeDeadline",
outputs: [{
name: "retVal",
type: "bool"
}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [],
name: "safeKill",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [{
name: "",
type: "address"
}],
name: "payoutsETH",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "beneficiary",
outputs: [{
name: "",
type: "address"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [{
name: "",
type: "address"
}],
name: "contributionsETH",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "deadlineEpoch",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "payoutETH",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "fundingGoal",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaised",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{
name: "amount",
type: "uint256"
}, {
name: "description",
type: "bytes32"
}],
name: "bump",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "owner",
outputs: [{
name: "",
type: "address"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaisedBTC",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "getNow",
outputs: [{
name: "retVal",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{
name: "description",
type: "bytes32"
}],
name: "payIn",
outputs: [],
payable: true,
type: "function"
}, {
constant: true,
inputs: [{
name: "",
type: "uint256"
}],
name: "backer_history",
outputs: [{
name: "ethAddr",
type: "address"
}, {
name: "amount",
type: "uint256"
}, {
name: "timestamp",
type: "uint256"
}, {
name: "description",
type: "bytes32"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaisedETH",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "creationTime",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "fee",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "getContribution",
outputs: [{
name: "retVal",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "feeWithdrawn",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "open",
outputs: [{
name: "",
type: "bool"
}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [],
name: "safeWithdrawal",
outputs: [],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{
name: "_fundingGoalInWei",
type: "uint256"
}, {
name: "_beneficiary",
type: "address"
}, {
name: "_deadlineEpoch",
type: "uint256"
}],
name: "initialize",
outputs: [],
payable: false,
type: "function"
}, {
inputs: [],
payable: false,
type: "constructor"
}, {
payable: true,
type: "fallback"
}, {
anonymous: false,
inputs: [{
indexed: false,
name: "beneficiary",
type: "address"
}, {
indexed: false,
name: "amountRaised",
type: "uint256"
}],
name: "GoalReached",
type: "event"
}, {
anonymous: false,
inputs: [{
indexed: false,
name: "beneficiary",
type: "address"
}, {
indexed: false,
name: "amountRaised",
type: "uint256"
}],
name: "PayOut",
type: "event"
}, {
anonymous: false,
inputs: [{
indexed: false,
name: "backer",
type: "address"
}, {
indexed: false,
name: "amount",
type: "uint256"
}],
name: "ReturnContribution",
type: "event"
}, {
anonymous: false,
inputs: [{
indexed: false,
name: "backer",
type: "address"
}, {
indexed: false,
name: "amount",
type: "uint256"
}],
name: "BuyIn",
type: "event"
}]
Use the ABI to get a handle on the contract at the specified address
> instance = web3.eth.contract(abi).at("0xd42a26792b8ae963622186b84f2bcdddd02c2d31")
{
abi: [{
constant: true,
inputs: [],
name: "isBeforeDeadline",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [],
name: "safeKill",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [{...}],
name: "payoutsETH",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "beneficiary",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [{...}],
name: "contributionsETH",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "deadlineEpoch",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "payoutETH",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "fundingGoal",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaised",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}, {...}],
name: "bump",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "owner",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaisedBTC",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "getNow",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "payIn",
outputs: [],
payable: true,
type: "function"
}, {
constant: true,
inputs: [{...}],
name: "backer_history",
outputs: [{...}, {...}, {...}, {...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "amountRaisedETH",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "creationTime",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "fee",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "getContribution",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "feeWithdrawn",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "open",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [],
name: "safeWithdrawal",
outputs: [],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}, {...}, {...}],
name: "initialize",
outputs: [],
payable: false,
type: "function"
}, {
inputs: [],
payable: false,
type: "constructor"
}, {
payable: true,
type: "fallback"
}, {
anonymous: false,
inputs: [{...}, {...}],
name: "GoalReached",
type: "event"
}, {
anonymous: false,
inputs: [{...}, {...}],
name: "PayOut",
type: "event"
}, {
anonymous: false,
inputs: [{...}, {...}],
name: "ReturnContribution",
type: "event"
}, {
anonymous: false,
inputs: [{...}, {...}],
name: "BuyIn",
type: "event"
}],
address: "0xd42a26792b8ae963622186b84f2bcdddd02c2d31",
transactionHash: null,
BuyIn: function(),
GoalReached: function(),
PayOut: function(),
ReturnContribution: function(),
allEvents: function(),
amountRaised: function(),
amountRaisedBTC: function(),
amountRaisedETH: function(),
backer_history: function(),
beneficiary: function(),
bump: function(),
contributionsETH: function(),
creationTime: function(),
deadlineEpoch: function(),
fee: function(),
feeWithdrawn: function(),
fundingGoal: function(),
getContribution: function(),
getNow: function(),
initialize: function(),
isBeforeDeadline: function(),
open: function(),
owner: function(),
payIn: function(),
payoutETH: function(),
payoutsETH: function(),
safeKill: function(),
safeWithdrawal: function()
}
Unlock your account (temporarily) so that you can spend from it.
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xb2e9fe08ca9a0323103883fe12c9609ed380f475
Passphrase:
true
Finally, pay into the crowdfund! You can substitute your own cool moniker / hacker pseudonym for "Ethereum Backer 1". When we make a nice web interface for the crowdfund, these monikers will be displayed, possibly in descending order of donation or contribution.
> instance.payIn("Ethereum Backer 1", {from: eth.accounts[0], to: instance.address, value: web3.toWei(1, "ether"), gas: 1816077})
"0x3376fe6d4560dbf262f0266696fd1adb850afb46ec4eb8550c1e833faf92220a"
Now verify the contract page, and hit refresh impatiently for 15 seconds until your transaction gets picked up.
https://rinkeby.etherscan.io/address/0xd42a26792b8ae963622186b84f2bcdddd02c2d31
You've done it! Thanks for your contribution of play money, and also testing an open source project that will help other people crowdfund as well.
If the crowdfund succeeds on May 25th 0:00 EDT, you'll see the payment go through to the beneficiary on the contract page. I'll send a tweet about it, and we'll all celebrate. I'll write another testnet contract that will let you retrieve back your testnet donation if you want it. Otherwise, I'll donate it to some plucky orphans who want to learn Ethereum programming to bootstrap themselves out of poverty.
If the crowdfund doesn't succeed by that time, we'll be a little sad, but hey, it's a testnet contract. Move on to the next step.
This will only work after the crowdfunding deadline. As with Kickstarter-style crowdfunding, you can't go withdrawing your contribution willy-nilly. First, unlock your personal account again, since it relocks every few seconds.
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xb2e9fe08ca9a0323103883fe12c9609ed380f475
Passphrase:
true
> instance.safeWithdrawal({from: backerAddress, gas: 180000})
The gas amounts are estimates. Other amounts may work as well. If the gas amount is missing, the transaction will appear to go through, but no amounts will actually transfer.
That's it! Have fun.
You can tweet any questions to @InvisibleLearn or find me (cryptogoth) on Github.
Could you explain about the secure way to participate in mainnet with my unlocked wallet on my mainnet node.