Transaction Generation

Once a user has submitted a deposit, they’re ready to start making transactions. It’s important that transactions are relatively standardized so that transaction generation is as simple as possible. This page describes a standard transaction format and generation process that clients MUST adhere to.

Transaction Format

From the perspective of each predicate, a transaction just consists of an arbitrary string of bytes. Each predicate could parse these bytes in a unique way and therefore define its own transaction format. However, clients should be able to correctly generate a transaction for any given predicate. As a result, we’ve developed a standard transaction format that simplifies the transaction generation process.

The interface for a Transaction object looks like this:

interface Transaction {
  plasmaContract: string
  start: number
  end: number
  methodId: string
  parameters: string
}

Where the components of this interface are:

  1. plasmaContract - string: The address of the specific plasma deposit contract which identifies the asset being transferred. This is somewhat equivalent to Ethereum’s chain ID transaction parameter.
  2. start - number: Start of the range being transacted.
  3. end - number: End of the range being transacted.
  4. methodId - string: A unique method identifier that tells a given predicate what type of state transition a user is trying to execute. This is necessary because a predicate may define multiple ways in which a state object can be mutated. methodId should be computed as the keccak256 hash of the method’s signature, as given by the Predicate API.
  5. parameters - string: Input parameters to be sent to the predicate along with method to compute the state transiton. Must be ABI encoded according to the Predicate API. This is similar to the transaction input value encoding in Ethereum.

Transaction Encoding and Decoding

Plasma transactions must be ABI encoded or decoded according to the following schema:

{
    plasmaContract: address,
    start: uint256,
    end: uint256,
    methodId: bytes32,
    parameters: bytes
}

Sending Transactions

The client SHOULD verify the history of the range being transacted before sending the transaction to the operator. Doing so will confirm that no `invalid transactions`_ have been maliciously inserted into the blockchain by the operator between the block in which the user received a state update and the latest block. Otherwise the client may have to start `limbo exit`_, which is more costly than a standard exit.

Transactions can be submitted to a node via the sendTransaction RPC method. If the node that receives this request is not the operator, then it will forward the transaction to the operator on the requester’s behalf.

Example: SimpleOwnership Predicate

We’re going to look at the whole process for generating a valid transaction to interact with some state objects locked by the SimpleOwnership predicate. This example will explain how a client can use the Predicate API to generate a valid state-changing transaction. In this case, we’ll generate a transaction that changes the ownership of the objects. We’ll then look at the process of encoding the transaction and sending it to the operator.

First, let’s pick some arbitary values for plasmaContract, start, and end. Users will know these values in advance, so we don’t really need to explain the process of getting them in the first place. Let’s say that the plasmaContract of the SimpleOwnership predicate is 0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c and we want to send the range (0, 100).

Now we just need to figure out our values for methodId and parameters. We’re going to use the Predicate API for SimpleOwnership in order to generate these values. Users can get this API from a variety of places, but it’s likely that most wallet software will come with a hard-coded API. Once we have the API, we know that send looks like this:

{
    name: "send",
    constant: false,
    inputs: [
        {
            name: "newOwner",
            type: "address"
        }
    ],
    outputs: []
}

This is already enough information to generate methodId and parameters. As we previously described, methodId is generated by taking the keccak256 hash of the method’s signature. In this case:

const methodId = keccak256('send(bytes)')

Now let’s generate parameters. Our only parameter to send is newOwner. We’re going to send to a random address, 0xd98165d91efb90ecef0ddf089ce06a06f6251372. We need to ABI encode this address:

const newOwner = '0xd98165d91efb90ecef0ddf089ce06a06f6251372'
const parameters = abi.encode(['address'], newOwner)

This is all we need to generate the transaction:

const transaction = abi.encode([
  'address',
  'uint256',
  'uint256',
  'bytes32',
  'bytes'
], [
  plasmaContract,
  start,
  end,
  methodId,
  parameters
])

Finally, we need to generate a valid witness for this transaction. SimpleOwnership requires a signature from the previous owner over the whole encoded transaction (except, of course, the signature itself) as a witness:

const key = '0x...'
const witness = sign(transaction, key)

We now have everything we need to send this transaction off to the operator!