Cómo crear correctamente una transacción sin procesar y firmarla usando web3 en el navegador

Quiero crear y firmar una transacción en el navegador usando web3. Para crear una transacción sin procesar, creo que tengo que hacer algo como esto (espero que puedas llenar los vacíos de mi conocimiento)

var pk = '0x6ba33b3f7997c2bf63d82f3baa1a8069014a59fa1f554af3266aa85afee9d0a9';

pk = new Buffer(pk,'hex');

var address = '0xFb4d271F3056aAF8Bcf8aeB00b5cb4B6C02c7368';

var myContractsAddress = '0x0cb4edc28d17c43a75797bf5effc141fd5da8715';

var rawTx = {

nonce: web3.toHex(web3.eth.getTransactionCount(acct1)),

to: myContractsAddress,

gasPrice: web3.toHex(20000000000),

gasLimit: web3.toHex(200000),

value: ***Here I am not sure, am I supposed to encode my list of variables? and do I encode everything to hex, or just integers? And how would that list look like?***

data: *This field is irrelevant for now, and just for documentationpurpose, right?*
}

Esto debería darme mi transacción sin procesar (¡la parte sobre el campo de datos que desafortunadamente no conozco y me encantaría recibir ayuda!

Digamos que mi transacción sin procesar es válida y correcta en este punto, tendría que firmarla con la clave privada ahora. La solución que he visto usaba una biblioteca node.js llamada ethereumjs-tx . ¿Hay una solución solo con web3, o tengo que portar esta biblioteca de alguna manera a mi navegador?

ethereumjs-txno necesita ser "portado" al navegador, ya que ya funciona bien allí. ¿Tiene el ABI para su contrato? valuees la cantidad de éter que está enviando (quizás 0 si solo está llamando a una función de contrato). dataes lo que le dice al contrato qué función está llamando (a través de un hash llamado "selector de función") y con qué parámetros (codificados por ABI). Si tiene el ABI, web3.js puede calcular el datacampo por usted.
¡Gracias por su respuesta! Tengo los contratos abi. Entonces, por valor, usaría web3.toHex (0) Si no le importa, comparta conmigo un ejemplo de cómo completar el campo de datos. supongamos que el abi está almacenado en var abi = ...;
Entonces las dos respuestas aquí deberían funcionar para usted.
He actualizado mi respuesta nuevamente, la escribí un poco mal
Creo que mi respuesta ya muestra cómo completar el campo de datos.
oh sí, lo hace. Tuve que actualizar, mi mal y muchas gracias por la rápida respuesta <3

Respuestas (4)

A continuación se muestra el código de trabajo que llama "incremento" en https://programtheblockchain.com/dapps/counter . (Más información sobre esa muestra aquí: https://programtheblockchain.com/posts/2017/12/13/building-decentralized-apps-with-ethereum-and-javascript/ ).

Utiliza web3.jsy ethereumjs-tx:

<!-- from https://github.com/ethereumjs/browser-builds/raw/master/dist/ethereumjs-tx/ethereumjs-tx-1.3.3.min.js -->
<script src="ethereumjs-tx-1.3.3.min.js"></script>
<script>
  var address = "0xf15090c01bec877a122b567e5552504e5fd22b79";
  var abi = [{"constant":true,"inputs":[],"name":"getCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_count","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}];

  var account = "<REDACTED ACCOUNT ADDRESS>";
  var privateKey = "<REDACTED PRIVATE KEY WITHOUT 0x PREFIX>";

  var web3 = new Web3(new Web3.providers.HttpProvider(
    "https://ropsten.infura.io/<REDACTED API KEY"));

  web3.eth.getTransactionCount(account, function (err, nonce) {
    var data = web3.eth.contract(abi).at(address).increment.getData();

    var tx = new ethereumjs.Tx({
      nonce: nonce,
      gasPrice: web3.toHex(web3.toWei('20', 'gwei')),
      gasLimit: 100000,
      to: address,
      value: 0,
      data: data,
    });
    tx.sign(ethereumjs.Buffer.Buffer.from(privateKey, 'hex'));

    var raw = '0x' + tx.serialize().toString('hex');
    web3.eth.sendRawTransaction(raw, function (err, transactionHash) {
      console.log(transactionHash);
    });
  });
</script>
está bien, desafortunadamente tengo que molestarte de nuevo... La función de incremento no espera ningún parámetro. ¿Qué pasaría si quisiera (cadena, uint256) es decir, porque no puedo simplemente cambiar var data = web3.eth.contract(abi).at(address).increment(string,uint256).getData();
Los parámetros se pasan a getData:...increment.getData("foo", 32);
Está bien, lo descubrí por completo ahora, eres mi héroe. ¿Puedo agradecerte de alguna manera?
¡Feliz de ayudar! Promocione programtheblockchain.com entre sus amigos desarrolladores de Ethereum. :-)
Ojalá tuviera amigos desarrolladores, jaja. Enlázame tu Steamid, puedo regalarte algo allí si quieres (si incluso usas Steam)
No uso vapor. :-)
demasiado. pero supongo que es mejor así. por eso te pregunto a ti y no al revés :D

Usando Web3.js 1.0.0

encoded = contractInstance.methods.myMethod(params).encodeABI()

var tx = {
    to : myContractAddress,
    data : encoded
}

web3.eth.accounts.signTransaction(tx, privateKey).then(signed => {
    web3.eth.sendSignedTransaction(signed.rawTransaction).on('receipt', console.log)
});
¿Cómo puedo acceder a los valores devueltos desde myMethod?
Parece que a la transacción sin procesar le faltan algunas piezas, no estoy seguro de si son necesarias. ¿Esto realmente funciona? ¿Simplemente infiere el nonce, gasPrice, gasLimit, etc?
@ohsully En su mayoría, puede inferir esos parámetros cuando usa web3 y cuentas / billetera integradas. Ocasionalmente, los parámetros operativos son atípicos, que es cuando necesita configurarlos manualmente.
En caso de que alguien se encuentre con esto, mi escenario específico es que estaba tratando de crear una transacción sin procesar y luego firmarla con una cuenta que no está almacenada en web3. Si no está utilizando web3 para firmar, su txobjeto también debe incluir nonce, gasPrice, gasLimit, valuey chainId. Estos normalmente serían inferidos por web3, pero si desea mantener las claves en otro lugar y firmar usando algo como eth-lightwallet, eso es lo que su txobjeto debe tener configurado.
¿De dónde obtiene su clave privada cuando solo tiene un archivo de almacén de claves?

Estuve perdido en este misterio durante algún tiempo y terminé escribiendo un paquete para resolverlo. Es web3js-raw, que es un contenedor simple alrededor de web3.js. Se puede encontrar una muestra completamente funcional usando esto aquí .

Puede usar browserify para empaquetar todo en un solo archivo .js y ejecutarlo en el navegador. El ejemplo anterior también explica cómo usar browserify.

Echa un vistazo a mi respuesta. Tengo curiosidad por saber qué es diferente/mejor en su biblioteca o si simplemente hace el mismo tipo de cosas debajo del capó que el código en mi respuesta.
@smarx Seguramente no estoy haciendo nada fundamentalmente diferente. Todo lo que he hecho es envolver web3.js de una manera fácil de usar. Hay tantas partes móviles/permutaciones de uso (por ejemplo, tener variables o no, enviar ether con llamada de función o no, involucrar a MetaMast bajo el capó, etc.) cuando intenta enviar una transacción. Entonces, mi enfoque es usar sendRawTransaction (que parece ser una de las funciones auxiliares subyacentes en web3.js) para todos los casos posibles, desde la implementación de un contrato para invocar una función.
¡Gracias por la explicación! Sin embargo, espero que los casos en los que un usuario tenga que entregar sus claves privadas a JavaScript en el navegador sean raros. :-)
Pude ver la conveniencia de no tratar con claves privadas tanto para usuarios promedio como para desarrolladores. Pero si las DApps alguna vez necesitan despegar y entrar en el flujo principal, los usuarios promedio deberían tener formas fáciles de administrar sus claves privadas. ¿Crees que MetaMask sería la mejor manera de manejar claves privadas? Personalmente, siento que es demasiado pedirle a un usuario promedio que instale MetaMask. ¿Alguna idea?
Creo que MetaMask (o algo así) es definitivamente el camino a seguir. Si los usuarios están pegando sus claves privadas en páginas web, creo que la criptomoneda seguramente morirá rápidamente cuando a todos les roben su dinero. :-) Pero es pronto.
De acuerdo con usted.

Existe una forma mejor y más sencilla de firmar y ejecutar la función de contrato inteligente. Aquí su función es addBonus.

En primer lugar, crearemos la instancia de contrato inteligente:

 const createInstance = () => {
  const bscProvider = new Web3(
    new Web3.providers.HttpProvider(config.get('bscRpcURL')),
  );
  const web3BSC = new Web3(bscProvider);
  const transactionContractInstance = new web3BSC.eth.Contract(
    transactionSmartContractABI,
    transactionSmartContractAddress,
  );
  return { web3BSC, transactionContractInstance };
};

Ahora crearemos una nueva función para iniciar sesión y ejecutar la función addBonus

const updateSmartContract = async (//parameters you need) => {
     try {
    const contractInstance = createInstance();
// need to calculate gas fees for the addBonus
    const gasFees =
      await contractInstance.transactionContractInstance.methods
        .addBonus(
         // all the parameters
        )
        .estimateGas({ from: publicAddress_of_your_desired_wallet });
   const tx = {
      // this is the address responsible for this transaction
      from: chainpalsPlatformAddress,
      // target address, this could be a smart contract address
      to: transactionSmartContractAddress,
      // gas fees for the transaction
      gas: gasFees,
      // this encodes the ABI of the method and the arguments
      data: await contractInstance.transactionContractInstance.methods
        .addBonus(
       // all the parameters
        )
        .encodeABI(),
    };
  // sign the transaction with a private key. It'll return messageHash, v, r, s, rawTransaction, transactionHash
    const signPromise =
       await contractInstance.web3BSC.eth.accounts.signTransaction(
        tx,
        config.get('WALLET_PRIVATE_KEY'),
      );
    // the rawTransaction here is already serialized so you don't need to serialize it again
    // Send the signed txn
    const sendTxn =
      await contractInstance.web3BSC.eth.sendSignedTransaction(
        signPromise.rawTransaction,
      );
    return Promise.resolve(sendTxn);
} catch(error) {
  throw error;
}
    }

Feliz codificación :)