3

How can you interact with a local Truffle Ganache blockchain through a Rails 6 app? I have prototyped the FixedSupplyToken app in vanilla javascript, and everything works as expected. Next, I try to move it into a Rails 6 app. I am nervous about using the Ruby gems (ethereum.rb - looking for someone to take over maintenance, others seem abandoned). I am wondering if you can you import web3.js directly into Rails 6 using Webpacker? I created a new Rails 6 app and added the following:

yarn add web3
yarn add truffle-contract

Both installed and showed up in the package.json:

{
  "name": "app",
  "private": true,
  "dependencies": {
    "@fortawesome/fontawesome-free": "^5.12.0",
    "@fortawesome/free-brands-svg-icons": "^5.12.0",
    "@fortawesome/free-regular-svg-icons": "^5.12.0",
    "@fortawesome/free-solid-svg-icons": "^5.12.0",
    "@rails/actioncable": "^6.0.0",
    "@rails/actiontext": "^6.0.2-1",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "4.2.2",
    "bootstrap": "^4.4.1",
    "bootswatch": "^4.4.1",
    "chart.js": "^2.9.3",
    "chartkick": "^3.2.0",
    "datatables.net-bs4": "^1.10.20",
    "datatables.net-buttons-bs4": "^1.6.1",
    "datatables.net-responsive-bs4": "^2.2.3",
    "datatables.net-select-bs4": "^1.3.1",
    "flatpickr": "^4.6.3",
    "jquery": "^3.4.1",
    "popper.js": "^1.16.0",
    "trix": "^1.0.0",
    "truffle-contract": "^4.0.31",
    "turbolinks": "^5.2.0",
    "vue": "^2.6.11",
    "web3": "^1.2.5-rc.0"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.10.1"
  }
}

I then added this to app/javascript/packs (web3/index.js):

// Import libraries we need.
import Web3 from 'web3';
import contract from 'truffle-contract';

// Import our contract artifacts and turn them into usable abstractions.
import token_artifacts from './FixedSupplyToken.json';

// TokenContract is our usable abstraction, which we'll use through the code below.
var TokenContract = contract(token_artifacts);

// The following code is to interact with contracts.
var accounts;
var account;

window.App = {
    start: function() {
        var self = this;
        // Bootstrap the TokenContract abstraction for Use.
        TokenContract.setProvider(web3.currentProvider);

        // Get the initial account balance so it can be displayed.
        web3.eth.getAccounts(function (err, accs) {
            if (err != null) {
                alert("There was an error fetching your accounts.");
                return;
            }

            if (accs.length == 0) {
                alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
                return;
            }

            accounts = accs;
            account = accounts[0];

        });
    },

    initManageToken: function() {
        App.updateTokenBalance();
        App.watchTokenEvents();
    },
    updateTokenBalance: function() {
        var tokenInstance;
        TokenContract.deployed().then(function (instance) {
            tokenInstance = instance;
            return tokenInstance.balanceOf.call(account);
        }).then(function (value) {
            console.log(value);
            var balance_element = document.getElementById("balanceTokenInToken");
            balance_element.innerHTML = value.valueOf();
        }).catch(function (e) {
            console.log(e);
            App.setStatus("Error getting balance; see log.");
        });
    },
    watchTokenEvents: function() {
        var tokenInstance;
        TokenContract.deployed().then(function (instance) {
            tokenInstance = instance;
            tokenInstance.allEvents({}, {fromBlock: 0, toBlock: 'latest'}).watch(function (error, result) {
                var alertbox = document.createElement("div");
                alertbox.setAttribute("class", "alert alert-info  alert-dismissible");
                var closeBtn = document.createElement("button");
                closeBtn.setAttribute("type", "button");
                closeBtn.setAttribute("class", "close");
                closeBtn.setAttribute("data-dismiss", "alert");
                closeBtn.innerHTML = "<span>&times;</span>";
                alertbox.appendChild(closeBtn);

                var eventTitle = document.createElement("div");
                eventTitle.innerHTML = '<strong>New Event: ' + result.event + '</strong>';
                alertbox.appendChild(eventTitle);


                var argsBox = document.createElement("textarea");
                argsBox.setAttribute("class", "form-control");
                argsBox.innerText = JSON.stringify(result.args);
                alertbox.appendChild(argsBox);
                document.getElementById("tokenEvents").appendChild(alertbox);
                //document.getElementById("tokenEvents").innerHTML += '<div class="alert alert-info  alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button><div></div><div>Args: '+JSON.stringify(result.args) + '</div></div>';

            });
        }).catch(function (e) {
            console.log(e);
            App.setStatus("Error getting balance; see log.");
        });
    },

    sendToken: function() {
        var amount = parseInt(document.getElementById("inputAmountSendToken").value);
        var receiver = document.getElementById("inputBeneficiarySendToken").value;

        App.setStatus("Initiating transaction... (please wait)");

        var tokenInstance;
        return TokenContract.deployed().then(function (instance) {
            tokenInstance = instance;
            return tokenInstance.transfer(receiver, amount, {from: account});
        }).then(function () {
            App.setStatus("Transaction complete!");
            App.updateTokenBalance();
        }).catch(function (e) {
            console.log(e);
            self.setStatus("Error sending coin; see log.");
        });
    },

    allowanceToken: function() {
        var self = this;

        var amount = parseInt(document.getElementById("inputAmountAllowanceToken").value);
        var receiver = document.getElementById("inputBeneficiaryAllowanceToken").value;

        this.setStatus("Initiating transaction... (please wait)");

        var tokenInstance;
        return TokenContract.deployed().then(function (instance) {
            tokenInstance = instance;
            return tokenInstance.approve(receiver, amount, {from: account});
        }).then(function () {
            self.setStatus("Transaction complete!");
            App.updateTokenBalance();
        }).catch(function (e) {
            console.log(e);
            self.setStatus("Error sending coin; see log.");
        });
    }
};

document.addEventListener('load', function() {
    // Checking if Web3 has been injected by the browser (Mist/MetaMask)
    if (typeof web3 !== 'undefined') {
        console.warn("Using web3 detected from external source. If you find that your accounts don't appear or you have 0 MetaCoin, ensure you've configured that source properly. If using MetaMask, see the following link. Feel free to delete this warning. :) http://truffleframework.com/tutorials/truffle-and-metamask")
        // Use Mist/MetaMask's provider
        window.web3 = new Web3(web3.currentProvider);
    } else {
        console.warn("No web3 detected. Falling back to http://localhost:8545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
        // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
        window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));
    }
});

Then, when I launch the Rails 6 app in a Docker container, everything seems fine until the webpack-dev-server attempts to compile the assets, and it throws this error:

webpack-dev-server    | ℹ 「wdm」: Compiling...
webpack-dev-server    | ✖ 「wdm」: Hash: 8531025c7b3abcc5d778
webpack-dev-server    | Version: webpack 4.41.5
webpack-dev-server    | Time: 1963ms
webpack-dev-server    | Built at: 01/21/2020 4:46:26 PM
webpack-dev-server    |                                                           Asset      Size       Chunks                         Chunk Names
webpack-dev-server    |                          js/application-8df32eacba8ebdbf7444.js  6.49 MiB  application  [emitted] [immutable]  application
webpack-dev-server    |                      js/application-8df32eacba8ebdbf7444.js.map  6.93 MiB  application  [emitted] [dev]        application
webpack-dev-server    |                                                   manifest.json   1.9 KiB               [emitted]
webpack-dev-server    | media/images/Logo_medlarge-e2a09f85c0c6ed39d81ce70b3bd44e51.png   130 KiB
webpack-dev-server    |      media/images/pdf_icon-c00c8db0462b60773a72302f7b9d456b.png  1.36 KiB
webpack-dev-server    |                       media/webfonts/fa-brands-400-088a34f7.eot   129 KiB
webpack-dev-server    |                       media/webfonts/fa-brands-400-273dc9bf.ttf   129 KiB
webpack-dev-server    |                     media/webfonts/fa-brands-400-822d94f1.woff2  74.2 KiB
webpack-dev-server    |                       media/webfonts/fa-brands-400-d7229311.svg   692 KiB
webpack-dev-server    |                      media/webfonts/fa-brands-400-f4920c94.woff    87 KiB
webpack-dev-server    |                      media/webfonts/fa-regular-400-3ac49cb3.eot  33.6 KiB
webpack-dev-server    |                    media/webfonts/fa-regular-400-9efb8697.woff2  13.3 KiB
webpack-dev-server    |                     media/webfonts/fa-regular-400-a57bcf76.woff  16.4 KiB
webpack-dev-server    |                      media/webfonts/fa-regular-400-d2e53334.svg   141 KiB
webpack-dev-server    |                      media/webfonts/fa-regular-400-ece54318.ttf  33.3 KiB
webpack-dev-server    |                        media/webfonts/fa-solid-900-2aa6edf8.ttf   189 KiB
webpack-dev-server    |                        media/webfonts/fa-solid-900-7a5de9b0.svg   829 KiB
webpack-dev-server    |                        media/webfonts/fa-solid-900-7fb1cdd9.eot   190 KiB
webpack-dev-server    |                       media/webfonts/fa-solid-900-93f28454.woff  96.7 KiB
webpack-dev-server    |                      media/webfonts/fa-solid-900-f6121be5.woff2  74.3 KiB
webpack-dev-server    |
webpack-dev-server    | ERROR in ./app/javascript/web3/index.js 126:22-26
webpack-dev-server    | "export 'default' (imported as 'Web3') was not found in 'web3'
webpack-dev-server    |     at HarmonyImportSpecifierDependency._getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:109:11)
webpack-dev-server    |     at HarmonyImportSpecifierDependency.getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:68:16)
webpack-dev-server    |     at Compilation.reportDependencyErrorsAndWarnings (/app/node_modules/webpack/lib/Compilation.js:1463:22)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compilation.js:1258:10
webpack-dev-server    |     at AsyncSeriesHook.eval [as callAsync] (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
webpack-dev-server    |     at AsyncSeriesHook.lazyCompileHook (/app/node_modules/tapable/lib/Hook.js:154:20)
webpack-dev-server    |     at Compilation.finish (/app/node_modules/webpack/lib/Compilation.js:1253:28)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compiler.js:672:17
webpack-dev-server    |     at _done (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:9:1)
webpack-dev-server    |     at eval (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:32:22)
webpack-dev-server    |
webpack-dev-server    | ERROR in ./app/javascript/web3/index.js 130:22-26
webpack-dev-server    | "export 'default' (imported as 'Web3') was not found in 'web3'
webpack-dev-server    |     at HarmonyImportSpecifierDependency._getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:109:11)
webpack-dev-server    |     at HarmonyImportSpecifierDependency.getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:68:16)
webpack-dev-server    |     at Compilation.reportDependencyErrorsAndWarnings (/app/node_modules/webpack/lib/Compilation.js:1463:22)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compilation.js:1258:10
webpack-dev-server    |     at AsyncSeriesHook.eval [as callAsync] (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
webpack-dev-server    |     at AsyncSeriesHook.lazyCompileHook (/app/node_modules/tapable/lib/Hook.js:154:20)
webpack-dev-server    |     at Compilation.finish (/app/node_modules/webpack/lib/Compilation.js:1253:28)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compiler.js:672:17
webpack-dev-server    |     at _done (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:9:1)
webpack-dev-server    |     at eval (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:32:22)
webpack-dev-server    |
webpack-dev-server    | ERROR in ./app/javascript/web3/index.js 130:31-35
webpack-dev-server    | "export 'default' (imported as 'Web3') was not found in 'web3'
webpack-dev-server    |     at HarmonyImportSpecifierDependency._getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:109:11)
webpack-dev-server    |     at HarmonyImportSpecifierDependency.getErrors (/app/node_modules/webpack/lib/dependencies/HarmonyImportSpecifierDependency.js:68:16)
webpack-dev-server    |     at Compilation.reportDependencyErrorsAndWarnings (/app/node_modules/webpack/lib/Compilation.js:1463:22)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compilation.js:1258:10
webpack-dev-server    |     at AsyncSeriesHook.eval [as callAsync] (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
webpack-dev-server    |     at AsyncSeriesHook.lazyCompileHook (/app/node_modules/tapable/lib/Hook.js:154:20)
webpack-dev-server    |     at Compilation.finish (/app/node_modules/webpack/lib/Compilation.js:1253:28)
webpack-dev-server    |     at /app/node_modules/webpack/lib/Compiler.js:672:17
webpack-dev-server    |     at _done (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:9:1)
webpack-dev-server    |     at eval (eval at create (/app/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:32:22)
webpack-dev-server    | ℹ 「wdm」: Failed to compile.

Websearches have not unearthed any possible solutions, and I have not dug up any examples of using web3.js using Webpacker in Rails. Is it possible; any solutions or examples out there? Thanks!

1 Answer 1

3

I think this is a naming conflict. You've named your own javascript file web3/index.js, which Webpack sees as web3; that conflicts with the node module you're trying to import by the same name. Try renaming your own javascript folder/file to something else. Related issue: https://github.com/webpack/webpack/issues/4817#issuecomment-316119100

Sign up to request clarification or add additional context in comments.

1 Comment

I renamed the web3 directory to ethereum, and the webpack-dev-server compiled without any complaints. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.