# Signing and pushing transactions

You can explore the code examples on github (opens new window)

This is where things start to be really fun! By signing and pushing transactions we mean: Writing data on-chain. And this is what we want, right ?

# Notes on this course part

Be careful !
In this course we will use our private key to sign transactions in our account behalf from our code, for this we reference our private key in a .env file. Never reference your private key directly in your code! If you don’t understand why we are remaining you this, you should read Getting started / terminology for private key definition (opens new window)

Note that we are signing transactions “the back end way”. This is not how you allow other user to interact with your application, this will be covered in the next chapter Your first dapp with the web-sdk (opens new window)

# Sign transaction with the @proton/js API class

First, let me introduce you to 2 new classes from the @proton/js package: the **API`**\`and the **JsSignatureProvider**\. In fact the JSONRpc class, introduced in 1 Reading the on-chain data (opens new window), is one of the two building blocks of the API. The API class accepts two arguments when you instanciate it: a JSONRpc instance and a JsSignatureProvider instance. The API allows us to push transactions, signed by the JsSignatureProvider using the private key you provide.

Let’s just goes with the basic setup:

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(['PVT_K1_...']);
const api = new Api({ rpc: jsonRpc, signatureProvider: signatureProvider });

So we keep our good old JSONRpc friend we saw from 1 Reading the on-chain data (opens new window). We the new class we spoke about above, the JsSignatureProvider and the **API**.`

Now it’s time to create an action (check documentation about actions if you haven’t read yet ), if you haven’t read and try to push it. Make sure the private key provided in the JsSignatureProvider constructor array is the one that belong to the authorization.actor and the data.from

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(['PVT_K1_...']);
const api = new Api({ rpc: jsonRpc, signatureProvider: signatureProvider });

const action = {
  account:"eosio.token",
  name:"tranfer",
  authorization: [
    {
      actor: "devcourse",
      permission:"active"
      
    }
  ],
  data: {
    from: "devcourse",
    to: "token.burn",
    quantity: "10.0000 XPR",
    memo:"XPRNetwork dev 101 courses rule !"
  }
}

So far, so good, we have created an action to `transfer` (the action name) 10 XPR (that data quantity) through the eosio.token (the XPR token contract) to the token.burn account (the to from the data). Let’s fire it up through the api:

Run this piece of code:

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(["PVT_K1_2btTxMLq72bwHUZgTf9fyxwF3CU6mtFkCtLvdpQD1PEngFdHfk"]);
const api = new Api({rpc: jsonRpc, signatureProvider: signatureProvider});

const action = {
  account: "eosio.token",
  name: "transfer",
  authorization: [
    {
      actor: "devcourse",
      permission: "active",
    },
  ],
  data: {
    from: "devcourse",
    to: "token.burn",
    quantity: "10.0000 XPR",
    memo: "XPRNetwork dev 101 courses rule !",
  },
};

try {

  api
  .transact({actions: [action]}, {expireSeconds: 30, blocksBehind: 3})
  .then(result => {
    console.log('Transaction succeed');
    console.log(result);
  });
} catch (e) {
  console.log('Transaction fail');
  console.log(e);
}

See 2_your_first_transaction_to_transfer_tokens from github repo (opens new window)

And it will output:

{
  transaction_id: "2fe2c145a5ea38eafc4d5b62d5dc6cbeae7f6f69819041eebad1f06132abdab4",
  processed: {
    id: "2fe2c145a5ea38eafc4d5b62d5dc6cbeae7f6f69819041eebad1f06132abdab4",
    block_num: 301383517,
    block_time: "2025-01-16T13:03:46.000",
    producer_block_id: null,
    receipt: {
      status: "executed",
      cpu_usage_us: 186,
      net_usage_words: 20,
    },
    elapsed: 186,
    net_usage: 160,
    scheduled: false,
    action_traces: [
      [Object ...]
    ],
    account_ram_delta: null,
    except: null,
    error_code: null,
  },
}

Look at this! We have a transaction_id and the receipt have a “executed” status. Our transaction is executed, let’s check in the testnet explorer a more user friendly confirmation

Open the explorer (opens new window)
image1 As you can see all informations from the action are in the actions tab, and our transaction_id is the same from the api.transact response call.

That’s the basics of signing and pushing transactions. You see how easy it is.
And you can stack actions in the transaction, but keep in mind that execution order matters (opens new window).

# A more functional example

The most common example is the escrow contract. Just a quick overview of what is that:

An escrow is a financial arrangement in which a third party holds and manages funds or assets on behalf of two other parties involved in a transaction. The third party only releases the funds or assets when certain predefined conditions are met. This ensures that both parties fulfill their obligations, providing protection to both the buyer and the seller.*

For this example we have deployed a dedicated smart contract to keep the process simple at this point. This very example will be analysed in deep on 3 Your first dApp with the web-sdk (opens new window) and 4 introduction to smart contracts (opens new window).

The contract in question is a greeting contract with an escrow process. In short, by paying 10 XPR to the contract, you are authorized to publish a message… Lame but easy to understand. A quick look at the 41.devcourse contract on testnet (opens new window)
image2
You will see that we have 2 tables in the tables tab: tickets and greets. The tickets table holds “How many authorization to publish greeting you buy” and greets is “The message you have published using a ticket”.

In the actions tab, you see that we have only a single greet action, to publish a greeting message by sending an account name as the greeter and the greeting message.
image3

Let's try to use this contract ! The process now involves 2 actions: a regular transfer action, the one that allows you to send tokens to any account on XPRNetwork and the greet action to publish a greeting. We will just do the first action to see what’s happen on the contract side.

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(["PVT_K1_2btTxMLq72bwHUZgTf9fyxwF3CU6mtFkCtLvdpQD1PEngFdHfk"]);
const api = new Api({rpc: jsonRpc, signatureProvider: signatureProvider});

const action = {
  account: "eosio.token",
  name: "transfer",
  authorization: [
    {
      actor: "devcourse",
      permission: "active",
    },
  ],
  data: {
    from: "devcourse",
    to: "41.devcourse",
    quantity: "10.0000 XPR",
    memo: "",
  },
};

try {

  api
  .transact({actions: [action]}, {expireSeconds: 30, blocksBehind: 3})
  .then(result => {
    console.log('Transaction succeed');
    console.log(result);
  });
} catch (e) {
  console.log('Transaction fail');
  console.log(e);
}

See 3_transaction_to_the_greeting_contract from github repo (opens new window)

As you can see the example is almost the same as the basic transfer, except we have changed the recipient of the transfer (the to in the action data) to 41.devcourse and removed the memo. On the contract, the cost to buy a ticket is set to 10.0000 XPR.
Run it!

{
  transaction_id: "fd7800178c5534e74be6e85344ac805fec9b5de40c0d8c51d30cfba281656568",
  processed: {
    id: "fd7800178c5534e74be6e85344ac805fec9b5de40c0d8c51d30cfba281656568",
    block_num: 302225728,
    block_time: "2025-01-21T10:05:09.500",
    producer_block_id: null,
    receipt: {
      status: "executed",
      cpu_usage_us: 277,
      net_usage_words: 16,
    },
    elapsed: 1055,
    net_usage: 128,
    scheduled: false,
    action_traces: [
      [Object ...]
    ],
    account_ram_delta: null,
    except: null,
    error_code: null,
  },
}

Easy peasy, you should receive the transaction_id and the receipt status should be “executed”
On the testnet explorer, the transaction (opens new window) show that 41.devcourse received 10XPR

image4

And on the contract side, the devcourse account has been granted one ticket to publish a greeting message. Ok, now let’s add the greet action call to the transaction. Let’s rename the action variable to be more clear.

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(["PVT_K1_2btTxMLq72bwHUZgTf9fyxwF3CU6mtFkCtLvdpQD1PEngFdHfk"]);
const api = new Api({rpc: jsonRpc, signatureProvider: signatureProvider});

const transferAction = {
  account: "eosio.token",
  name: "transfer",
  authorization: [
    {
      actor: "devcourse",
      permission: "active",
    },
  ],
  data: {
    from: "devcourse",
    to: "41.devcourse",
    quantity: "10.0000 XPR",
    memo: "",
  },
};

const greetingAction = {
  account: "41.devcourse",
  name: "greet",
  authorization: [
    {
      actor: "devcourse",
      permission: "active",
    },
  ],
  data: {
    greeter: "devcourse",
    message: "Hello from XPRNetwork, the only network that beat Chuck Norris",
  },
};

try {

  api
  .transact({actions: [transferAction,greetingAction]}, {expireSeconds: 30, blocksBehind: 3})
  .then(result => {
    console.log('Transaction succeed');
    console.log(result);
  });
} catch (e) {
  console.log('Transaction fail');
  console.log(e);
}

See 4_full_transaction_to_the_greeting_contract from github repo (opens new window)

So we have renamed the original action to `transferAction` and added the greetingAction. Booths have been added to the actions array of the transact function on the api instance.
Let’s run this baby !

Once again the response is instant! transaction_id and executed status, we have everything !

{
  transaction_id: "2b00658855d4ba607163abba6c0cca8738d431b343d575d07279b392145ed548",
  processed: {
    id: "2b00658855d4ba607163abba6c0cca8738d431b343d575d07279b392145ed548",
    block_num: 302229877,
    block_time: "2025-01-21T10:41:08.000",
    producer_block_id: null,
    receipt: {
      status: "executed",
      cpu_usage_us: 243,
      net_usage_words: 29,
    },
    elapsed: 243,
    net_usage: 232,
    scheduled: false,
    action_traces: [
      [Object ...], [Object ...]
    ],
    account_ram_delta: null,
    except: null,
    error_code: null,
  },
}

On the explorer side, we can see that our transaction has been executed sequentially, with the transfer executed on the eosio.token contract in first position, then the greet action on the 41.devcourse contract.
image6

On the 41.devcourse contract, we can see in the greets table our published message!
image7

But from the `tickets` table, there remains 1 ticket for our account … Why ? Because the last action has bough a ticket and consumed it immediately! The remaining ticket was acquired by our previous single transfer action example, that means we can publish 1 other message without transferring tokens.

We remove the transfer action and run this code

import {Api, JsonRpc, JsSignatureProvider} from "@proton/js";

const jsonRpc = new JsonRpc(["https://testnet.rockerone.io"]);
const signatureProvider = new JsSignatureProvider(["PVT_K1_2btTxMLq72bwHUZgTf9fyxwF3CU6mtFkCtLvdpQD1PEngFdHfk"]);
const api = new Api({rpc: jsonRpc, signatureProvider: signatureProvider});

const greetingAction = {
  account: "41.devcourse",
  name: "greet",
  authorization: [
    {
      actor: "devcourse",
      permission: "active",
    },
  ],
  data: {
    greeter: "devcourse",
    message: "Pump up the jam, pump it up While your feet are stompin', And the jam is pumpin', Look ahead, the crowd is jumpin",
  },
};

try {

  api
  .transact({actions: [greetingAction]}, {expireSeconds: 30, blocksBehind: 3})
  .then(result => {
    console.log('Transaction succeed');
    console.log(result);
  });
} catch (e) {
  console.log('Transaction fail');
  console.log(e);
}

Response from this call:

{
  transaction_id: "f6e248ab5d312db28edad0fd4dac8305199601c6d43b4e8c733f7656f8acadcf",
  processed: {
    id: "f6e248ab5d312db28edad0fd4dac8305199601c6d43b4e8c733f7656f8acadcf",
    block_num: 302232108,
    block_time: "2025-01-21T10:59:43.500",
    producer_block_id: null,
    receipt: {
      status: "executed",
      cpu_usage_us: 190,
      net_usage_words: 27,
    },
    elapsed: 190,
    net_usage: 216,
    scheduled: false,
    action_traces: [
      [Object ...]
    ],
    account_ram_delta: null,
    except: null,
    error_code: null,
  },
}

And here is our transaction on the explorer (opens new window) with our single call.
image7

And our new message from our greets table image7

And finally there is no ticket left for our account
image7

So now if we run our code again and make a single call to the greet action, we have this expected error.

error: assertion failure with message: No ticket found, transfer 10 XPR before greeting at new RpcError

Session complete

Next: Write your first dApp