Affiliate Program

Part 2

## Recap * Architectural Overview * Contracts * Affiliate Contract * Affiliate Factory Contract * Proxy Contract * Great case study for OpenRelay
## Today * dApp implementation
## Affiliate dApp Walkthrough
## dApp code * jQuery / jQuery UI * Depends on Injected Web3 * Not terribly structured, but suitable for its size

Web3 Setup


							$(window).on("load", function(){
							  // Web3 will be injected by this point
							  if(!window.web3) {
							    $("#dapp").hide()
							    $("#need-web3").show()
							    return;
							  }
							  window.web3 = new Web3(window.web3.currentProvider);
						

Contract Setup


							// Web3 will be injected by this point
							var affiliateFactoryAddress;
							var affiliateFactoryContract;
							web3.version.getNetwork(function(err, networkId) {
							  if(networkId == 42) {
							    affiliateFactoryAddress = "0xbedea5e0ce982e546e467cc0624c72d662c2d0d1";
							  } else if (networkId == 5777) {
							    affiliateFactoryAddress = "0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f";
							  }
							  var affiliateFactoryAbi = [{"constant":true,"inputs":[],"name":"beneficiary","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"beneficiaryStake","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"target","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"senderStake","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"affiliateAddress","type":"address"},{"indexed":false,"name":"targetAddress","type":"address"},{"indexed":false,"name":"affiliateName","type":"string"},{"indexed":true,"name":"sender","type":"string"}],"name":"AffiliateDeployed","type":"event"},{"constant":false,"inputs":[{"name":"_stakeHolders","type":ddress[]"},{"name":"_stakes","type":"uint256[]"},{"name":"_name","type":"string"}],"name":"signUp","outputs":[{"name":"affiliateContract","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"}];
							  affiliateFactoryContract = web3.eth.contract(affiliateFactoryAbi).at(
							                                               affiliateFactoryAddress);
							});
						

Get Accounts


							function checkAccounts(){
							  window.web3.eth.getAccounts(function(err, accounts) {
							    if(err || accounts.length == 0) {
							      $("#dapp").hide()
							      $("#need-web3").show()
							    } else {
							      $("#dapp").show()
							      $("#need-web3").hide()
							      var firstAddressInput = $(".target_address").first();
							      if(firstAddressInput.val() == "") {
							        firstAddressInput.val(accounts[0]);
							        markValid(firstAddressInput);
							      }
							    }
							    setTimeout(checkAccounts, 1000);
							  });
							}
							checkAccounts();
						

Confirmation Screen


							$("#submit button").click(function(){
							Promise.all([
							  callPromise(affiliateFactoryContract, "beneficiaryStake"),
							  callPromise(affiliateFactoryContract, "senderStake"),
							  callPromise(affiliateFactoryContract, "beneficiary"),
							  callPromise(affiliateFactoryContract, "target"),
							]).then(function(results) {
							  var beneficiaryStake = results[0];
							  var senderStake = results[1];
							  var beneficiary = results[2];
							  var target = results[3];
							  [...]
							})
						

Signup Submission


							"Confirm": function() {
							  $(this).dialog("close");
							  var stakeHolders = [];
							  var stakes = [];
							  $("tr.affiliate-row.valid_address").each(function(){
							    var record = $(this);
							    stakeHolders.push(record.find(".target_address").val());
							    stakes.push(record.find(".share_split").val());
							  });
							  web3.eth.getAccounts(function(err, accounts) {
							    // Set up watcher for completion event
							    var watcher = affiliateFactoryContract.AffiliateDeployed({
							      sender: accounts[0]
							    });
							    var txInterval;
							    watcher.watch(function(err, result) {
							      // Clean up watchers and forward to dashboard
							      clearInterval(txInterval);
							      watcher.stopWatching(console.log);
							      window.location = "./dashboard.html#" + result.args.sender;
							    });
							    // Submit signup transaction
							    affiliateFactoryContract.signUp(
							      stakeHolders,
							      stakes,
							      $("#affiliate-name").val(),
							      {from: accounts[0]},
							      function(err, txid) {
							        if(err) {
							          $("#dialog-error .content").html("An error has occurred: " + err);
							          errorDialog.dialog("open");
							        } else {
							          $("#tx-etherscan").attr("href", "https://etherscan.io/tx/" + txid);
							          txDialog.dialog("open");
							          txInterval = setInterval(function() {
							            web3.eth.getTransactionReceipt(txid, function(err, receipt) {
							              // Clean up watchers and forward to dashboard
							              clearInterval(txInterval);
							              watcher.stopWatching(console.log);
							              window.location = "./dashboard.html#0x" + (
							                // Since we're not dealing with an Event object
							                // we just have to know where to find the address
							                receipt.logs[0].data.slice(26, 66);
							              )
							            });
							          }, 1000);
							        }
							    });
							  });
							},
						
## Notes About Transaction Monitoring * 2 ways to monitor for tx completion * Watch for transaction receipts * Fails if the user resubmits the order with a gas change * Monitor for events * Doesn't work with all web3 clients * If an event is important, do both

Dashboard Setup


							window.web3 = new Web3(window.web3.currentProvider);
							web3.version.getNetwork(function(err, networkId) {
							  var tokensToCheck;
							  if(networkId == 42) {
							    tokensToCheck = [
							      {"name": "ZRX", "icon": "./img/zrx.png", "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570", "divideby": "1000000000000000000"},
							      {"name": "WETH", "icon": "./img/weth.png", "address": "0x1aacd64d9e67701791617664d20908380854a34d", "divideby": "1000000000000000000"},
							    ];
							  } else if(networkId == 1){
							    tokensToCheck = [
							      {"name": "ZRX", "icon": "./img/zrx.png", "address": "0xe41d2489571d322189246dafa5ebde1f4699f498", "divideby": "1000000000000000000"},
							      {"name": "WETH", "icon": "./img/weth.png", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "divideby": "1000000000000000000"},
							      {"name": "MBGN", "icon": "./img/mbgn.png", "address": "0xdde19c145c1ee51b48f7a28e8df125da0cc440be", "divideby": "1000000000000000000"},
							    ];

							  }
						

Contract Setup


							// Set up token contract without address
							var tokenBase = web3.eth.contract([{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]);

							// Set up affiliate contract with address
							var affiliateContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"relayerName","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Payout","type":"event"},{"constant":false,"inputs":[{"name":"tokens","type":"address[]"}],"name":"payout","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]
							                        ).at(window.location.hash.slice(1));

							// Get relayerName from affiliate contract
							affiliateContract.relayerName.call(function(err, name) {
							  $(".relayer-name").text(name);
							})
						

Populate Balances


							function populateTable() {
							  $("#balanceTable tr").remove();
							  $("#payout").hide();
							  payouts = [];
							  for(var i = 0; i < tokensToCheck.length; i++) {
							    (function(token) {
							      var tokenRow = $('<tr><td class="icon" style="width:50px;"><img src=""></img></td><td class="symbol" style="width:50px;"></td><td class="balance" style="width:50px;"></td></tr>');
							      $("#balanceTable").append(tokenRow);
							      tokenRow.find(".icon img").attr("src", token.icon);
							      tokenRow.find(".symbol").html(token.name);
							      tokenBase.at(token.address).balanceOf(location.hash.slice(1), function(err, balance) {
							        if(err) {
							          tokenRow.find(".balance").text("Error!");
							        } else {
							          tokenRow.find(".balance").text(balance.div(token.divideby).toString());
							          if(balance.gt(0)) {
							            payouts.push(token.address);
							            $("#payout").show();
							          }
							        }
							      });
							    })(tokensToCheck[i]);
							  }
							}
						
## Conclusions * Web3 Interactions * Web3 object setup * Contract interfaces * Calls vs Transactions * BigNumber values * Principles generally apply across frameworks
## Next Week * Affiliate Monitor Microservice * OpenRelay's Upcoming Widget Framework