“It's not improbable that a man may receive more solid satisfaction from pudding while he is alive than from praise after he is dead”
-- Chinese Proverb
Ether Pudding
Ether Pudding (or just “Pudding”) is a packager and build artifact manager for Ethereum and Javascript. It turns ABIs, binaries, deployed addresses, etc. into Javascript files you can include in your project with simply a require
. e.g., it lets you do this:
var MyContract = require("./MyContract.sol.js");
MyContract.setProvider(someWeb3Provider);
MyContract.deployed().doStuff().then(function(tx) {
// We just talked to the ethereum network!
});
👏
Features
- Manages contract ABIs, binaries and deployed addresses, so you don't have to.
- Packages up build artifacts into
.sol.js
files, which can then be included in your project with a simplerequire
. - Includes multiple versions of the same contract in a single package, automatically detecting which artifacts to use based on the network version (more on this below).
- Manages library addresses for linked libraries.
See the following discussion for more features.
The State of Web3: A Discussion
We can't go on without noting that Pudding was originally created to be an easier-to-use contract abstraction for Web3. However, Web3 plans to include a lot of Pudding's features (like promises) and so Pudding has pivoted to focus on package management. Pudding's original abstraction API still exists, but will be replaced with Web3's once the related EIP has been merged and published.
Some of the features Pudding provides over the current abstraction:
- Synchronized transactions, so your app/tests won’t receive their callbacks until the transactions have been processed.
- Promises. No more callback hell.
- Default values for transactions, like
from
address orgas
.
Install
$ npm install ether-pudding
Usage
Pudding consists of two parts: The code generator that generates your .sol.js
files, and then the .sol.js
files themselves.
Generator
Generate .sol.js
files given a contract name and contract data, structured as an object. This will save a .sol.js
file into the destination directory for each contract specified.
Note: Pudding (the generator) isn't intended to be used in the browser or in a package manager like browserify or webpack. The resultant .sol.js
files are well suited for the browser, however.
var Pudding = require("ether-pudding");
var contract_data = {
abi: ..., // Array; required.
unlinked_binary: "..." // String; optional.
address: "..." // String; optional.
};
Pudding.save(contract_data, "./MyContract.sol.js").then(function() {
// The file ./MyContract.sol.js now exists, which you can
// import into your project like any other Javascript file.
});
Note that in the example above, there are three important pieces of data:
abi
: The ABI of your contract, provided by the solidity compiler.unlinked_binary
: The compiled binary code of your contract. Ensure that if your contract relies on libraries to be linked, you pass in the unlinked version here.address
: An address this contract is deployed to, if it's a singleton.
Using .sol.js
Files
Once a .sol.js
file has been created, using it is easy. These abstractions use Web3 under the hood, and so will need a provider set just like Web3:
var provider = new Web3.providers.HttpProvider("http://localhost:8545");
var MyContract = require("./MyContract.sol.js");
MyContract.setProvider(provider);
You'll receive errors if you try to make a transaction without a Web3 provider set.
See Interacting With Your Contracts below for details on how to use the newly created MyContract
object.
Interacting With Your Contracts
Let's explore Pudding contract classes via MetaCoin contract described in Dapps For Beginners:
// Require the package
var MetaCoin = require("./path/to/MetaCoin.sol.js");
// Remember to set the Web3 provider (see above).
MetaCoin.setProvider(provider);
// In this scenario, two users will send MetaCoin back and forth, showing
// how Pudding allows for easy control flow.
var account_one = "5b42bd01ff...";
var account_two = "e1fd0d4a52...";
// Note our MetaCoin contract exists at a specific address.
var contract_address = "8e2e2cf785...";
var coin = MetaCoin.at(contract_address);
// Make a transaction that calls the function `sendCoin`, sending 3 MetaCoin
// to the account listed as account_two.
coin.sendCoin(account_two, 3, {from: account_one}).then(function(tx) {
// This code block will not be executed until Pudding has verified
// the transaction has been processed and it is included in a mined block.
// Pudding will error if the transaction hasn't been processed in 120 seconds.
// Since we're using promises, we can return a promise for a call that will
// check account two's balance.
return coin.balances.call(account_two);
}).then(function(balance_of_account_two) {
alert("Balance of account two is " + balance_of_account_two + "!"); // => 3
// But maybe too much was sent. Let's send some back.
// Like before, will create a transaction that returns a promise, where
// the callback won't be executed until the transaction has been processed.
return coin.sendCoin(account_one, 1.5, {from: account_two});
}).then(function(tx) {
// Again, get the balance of account two
return coin.balances.call(account_two)
}).then(function(balance_of_account_two) {
alert("Balance of account two is " + balance_of_account_two + "!") // => 1.5
}).catch(function(err) {
// Easily catch all errors along the whole execution.
alert("ERROR! " + err.message);
});
Pudding manages your contract's binary code as well, so you can easily deploy new contracts of the same type using the abstraction:
MetaCoin.new().then(function(coin) {
// From here, the example becomes just like the above.
// Note that coin.address is the address of the newly created contract.
return coin.sendCoin(...);
)}.catch(function(err) {
console.log("Error creating contract!");
console.log(err.stack);
)};
More Examples
- Extending Pudding contract classes
- Setting global contract-level and instance-level defaults
- When not to use synchronized transactions, and how to do it
Pudding API
Pudding.save([contract_name,] contract_data, filename, options)
Save contract data as a .sol.js
file. Returns a Promise.
-
contract_name
: String. Optional. Name of contract class to be created. -
contract_data
: Object. Example:{ abi: ..., // Array; required. unlinked_binary: "..." // String; optional. address: "..." // String; optional. }
Note:
save()
will also accept an alreadyrequire
'd contract object. i.e.,var MyContract = require("./path/to/MyContract.sol.js"); Pudding.save(MyContract, ...).then(...);
-
filename
: Path to save contract file. -
options
: Object. See below.
The options
object takes two parameters:
options.overwrite
: Boolean. Overwrite the existing contract file if it exists. If true, will ignore previously-saved contract data in the existing file. If false, will create a new contract file and merge in the contract data passed tosave()
.options.network_id
: String. Will save the contract data passed tosave()
under the specified network id. If no network id is specified, will use network"default"
. See discussion a about network id's below.
The contract name is only important in the source code that gets generated, which will appear in error messages. If contract_name
is not present it will default to "Contract".
Pudding.saveAll(contracts, directory, options)
Save many contracts to the filesystem at once. Returns a Promise.
-
contracts
: Object. Keys are the contract names and the values arecontract_data
objects, as in thesave()
function above:{ "MyContract": { "abi": ..., "unlinked_binary": ... } "AnotherContract": { // ... } }
-
directory
: String. Destination directory. Files will be saved via<contract_name>.sol.js
within that directory. -
options
: Object. Same options listed insave()
above.
Pudding.generate([contract_name,] networks)
Generate the source code that populates the .sol.js
file. Returns a String.
-
contract_name
: String. Optional. Name of the contract to generate. -
networks
: Object. Contains the information about this contract for each network, keyed by the network id.{ "live": { "abi": ..., "unlinked_binary": ..., "address": ... }, "morden": { "abi": ..., "unlinked_binary": ..., "address": ... }, "1337": { "abi": ..., "unlinked_binary": ..., "address": ... } }
Note that each ABI, unlinked_binary and address refer to the same contract, but deployed on different networks. If no network ids are present -- i.e., a
contract_data
object was passed instead -- thengenerate()
will automatically use that data for the default network. i.e.,Pudding.generate("MyContract", { "abi": ..., "unlinked_binary": ... });
The contract name is only important here in the source code that gets generated, which will appear in error messages. If contract_name
is not present it will default to "Contract".
Pudding.whisk([contract_name,] networks)
Like generate()
, this function will create the source code that populates the .sol.js
file, but instead of returning it as a string it will import it and return an object ready for use. Parameters are the same as generate()
.
The contract name is only important here in the source code that gets generated, which will appear in error messages. If contract_name
is not present it will default to "Contract".
// Couple examples:
// Whisk in different networks:
var MyContract = Pudding.whisk("MyContract", {
"live": {
"abi": ...
"unlinked_binary": ...
"address": ...
},
"morden": {
// ...
}
});
// Or whisk in a single network using the default contract name:
var MyContract = Pudding.whisk({
"abi": ...,
"unlinked_binary": ...,
"address": ...
});
// Then, use the class immediately:
MyContract.setProvider(someWeb3Provider);
MyContract.defaults({
from: "0xabcd..."
});
MyContract.new().then(...);
Running Tests
$ npm test
License
MIT