Offline Transactions
Introduction
STRATO allows users to send transactions that are prepared and signed manually, rather than by STRATO itself. This functionality is available through a separate endpoint from the standard transactions endpoint. Transactions must be prepared according to the Ethereum transaction specification, including the correct user nonce
, and r
, s
, & v
ECDSA signature values. See Preparing a Transaction for details on these steps.
The endpoint bypasses STRATO's key vault to sign transactions, so it is not required that an address-key pair already exist within the vault to make a valid transaction. Therefore, users must store and manage their own public-private keys, Ethereum addresses, and account nonces. User account information is only stored within STRATO when a user has successfully posted to the STRATO /key
endpoint. If a user does not wish their information to be stored within STRATO, than a user should not use the /key
endpoint and sign all transactions manually as described within this article. Since a user's key is only retrieved for completing transactions, a user may still interact with all other features of STRATO without using the /key
endpoint.
Transaction Endpoint Behavior
While key management may be done outside of STRATO, request authorization is still managed by the OAuth client set up at boot time. However, using manually signed transactions allows users to separate their OAuth identity with their public and private-keys associated with that identity. Learn more about managing your STRATO identity in the User Identity & Authorization page.
Warning
Since STRATO only manages user keys that are in its key vault, if a user loses or forgets their private-key that has not been stored by STRATO, it is irrecoverable by any mechanism within STRATO or by any representative of BlockApps. Furthermore any data associated that key such as nonce or address is also lost. A user's public-key is only recoverable when all r
, s
& v
values are included in a transaction payload. Therefore, use this feature at your own risk, bearing in mind to keep your credentials in a safe place where they will be remembered.
Prerequisites
Manually signing transactions requires the understanding of several cryptographic and Ethereum related topics:
- Ethereum transaction types.
- Public-Private key encryption.
- Elliptic Curve Encryption (ECDSA).
- Keccak256 Hashing.
- ABI Contract Type Encoding.
- RLP Encoding/Serialization.
API Endpoint
Manually signed transactions are sent to the raw
transaction endpoint.
Endpoint
POST https://<strato_address>/bloc/v2.2/transaction/raw
The endpoint takes the arguments of a standard signed Ethereum transaction, as well as the user's address and additional metadata elements. The transaction is sent in the request's body as a JSON object.
Request Body
nonce
: number *- The current nonce for this user address (key). This will be the nonce used for this transaction.
- See Account Nonce.
gasPrice
: number *- The number of tokens the user will spend per unit of gas for this transaction.
- Ignored for networks with gas disabled.
gasLimit
: number *- The maximum amount of gas the user would want to use for this transaction.
- Ignored for networks with gas disabled.
to
: string *- Optional.
- The Ethereum account address prefixed with
0x
. - The address to which this transaction will send tokens to/call a function on.
null
for contract creation.
value
: number *- The number of tokens to send to the
to
account of the transaction. - Ignored for networks with gas disabled.
- Typically
0
for contract creation and function call transactions.
- The number of tokens to send to the
chainId
: string- Optional.
- The chainID of the private chain on which to execute this transaction.
- See Note on Private Chains.
initOrData
: string *- The transaction data payload for this transaction, encoded as per the Ethereum protocol for transactions using the EVM.
- See Preparing a Transaction.
address
: string- The 20-byte Ethereum address of the user account signing this transaction.
r
: number *- The first 32 bytes of the ECDSA signature generated from the signed raw transaction.
- Formatted in hexadecimal.
s
: number *- The last 32 bytes of the ECDSA signature generated from the signed raw transaction.
- Formatted in hexadecimal.
v
:27
|28
*- Optional if
address
is included. - The recovery ID (parity bit) of the ECDSA signature, for public-key recovery.
v = 27 + recovery ID
.- When omitted, the public-key associated with private-key used to sign the transaction will be irrecoverable.
- Optional if
metadata
: object- Optional.
- Key-value metadata elements to include in the transaction.
- See Metadata Elements to learn more about how to use the
metadata
property.
*This property is part of the Ethereum transaction specification.
Example Payload for a Contract Creation Transaction
{
"nonce": 23,
"gasPrice": 1,
"gasLimit": 1230000,
"value": 0,
"to": null,
"initOrData": "<SolidVM-contract-code>",
"r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
"s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
"v": 28,
"address": "86d28b82d5478f714411b201f0a162b2916ade6a",
"metadata": {
"VM": "SolidVM",
"name": "SimpleStorage",
"args": "(5)"
}
}
Preparing a Transaction
Users must first prepare an unsigned Ethereum transaction via the Ethereum Transaction Protocol.
Deriving a User Address
User addresses on STRATO follow the Ethereum Account Protocol. An address is associated with a single elliptic curve public-key generated by a user. To generate the proper ethereum address, follow the instructions below:
- Generate a public-private key pair using the
secp256k1
curve. Ensure that the key is not compressed. - Convert the public-key to hexadecimal format (if not already). This should be exactly 64 bytes long.
- Generate the Keccak256 hash of the public-key.
- Extract the last 40 bytes (20 hex-characters) of the key's hash as the user's address.
Use this guide for a simple walkthrough of generating an ethereum address in a terminal environment.
Account Nonce
Every account has an associated nonce
which is a counter for the number of transactions this user account has already performed. An account's nonce starts at 0 when it has executed its first transaction. For each transaction sent, the nonce is incremented by 1.
Since an account's nonce is stored with the user's identity in STRATO's vault, a transaction sent to the transaction/raw
endpoint will not update or store an account's nonce, since it has no internal record of the account. The user sending manually signed transactions must increment and store the correct nonce for a given account.
Encoding Transaction Data
The instructions declaring what a transaction does is contained within the initOrData
field (named data
in Ethereum). Shown here are the steps to properly form each transaction type.
Token Transfer
The transaction must contain a to
address, and a value
in tokens to transfer to the specified address.
No data is needed in the initOrData
field of the transaction.
Contract Creation
The transaction to
address must be null
or omitted since the transaction will be creating a new contract account.
The code of the contract to be created is provided in the initOrData
field:
- For EVM contracts, this is the compiled EVM bytecode for the corresponding solidity smart contract.
- The STRATO API provides the
bloc/v2.2/contracts/compile
endpoint to allow users to easily compile their smart contracts for the EVM. See the Complete API Documentation for more information.
- The STRATO API provides the
- For SolidVM contracts, this is the UTF-8 encoded smart contract Solidity code.
Important
When creating the transaction payload to sign with a private-key, the contract code must be in the proper encoding as listed above. However, when sending the final request to the STRATO API, the encoded contract data must be converted to hexadecimal as the value in the request body's initOrData
field.
Function Calls
The transaction to
address is the address of the contract account being called.
SolidVM
Function name and arguments for contracts using SolidVM are provided in the metadata
property of the API request body. Therefore the initOrData
property is left empty for SolidVM function calls.
EVM
Function calls on contracts that use the EVM must follow the Ethereum function call standard, as described below. The signature of the function being called and the arguments being passed to the function must individually be encoded and then concatenated with each other. This final payload is the data in the initOrData
field.
- Encode the function signature. Typically called the "function selector".
- A functions signature is its name and its argument types. E.g.
function(type_0,type_1,...type_n) returns (type_2)
. - Generate the Keccak256 hash of the function signature.
- Extract the first 4 bytes (8 hexadecimal characters) as the Function Selector.
- A functions signature is its name and its argument types. E.g.
- Encode the function arguments.
- Encode each argument value to its Contract ABI Specification value.
- Concatenate the function selector with each encoded argument value in order.
functionSelector + ABI(arg_0) + ABI(arg_1) ... + ABI(arg_n)
Important
When creating the transaction payload to sign with a private-key, the function call information must be in the proper encoding as listed above. However, when sending the final request to the STRATO API, the encoded function call information must be converted to hexadecimal as the value in the request body's initOrData
field.
See this explanation from Medium.com for a more in-depth explanation of creating function-call transactions.
Creating the Raw Transaction Payload
Once the proper initOrData
payload has been created, the raw transaction payload can be prepared. The raw transaction payload is an array of the following properties in this order:
nonce
gasPrice
gasLimit
to
value
initOrData
chainId
The final transaction payload is encoded with the RLP serialization method. RLP is a method of serialization that allows arbitrary data structures to be encoded in a standardized, compact way.
RLP serialization can be reviewed in detail at the Ethereum Wiki.
Example Raw Transaction Payloads
{
"nonce": 0,
"gasPrice": 1,
"gasLimit": 1230000,
"value": 100,
"to": "0x211446238914bb9dfa81913f961c50ff79556c1d",
"initOrData" : "",
}
RLP_List = [0, 1, 1230000, 100, "0x211446238914bb9dfa81913f961c50ff79556c1d", ""]
{
"nonce": 0,
"gasPrice": 1,
"gasLimit": 1230000,
"value": 0,
"to": null,
"initOrData" : "<UTF-8-SolidVM-contract>",
}
RLP_List = [0, 1, 1230000, 0, null, "<UTF-8-SolidVM-contract>"]
{
"nonce": 0,
"gasPrice": 1,
"gasLimit": 1230000,
"value": 0,
"to": "0x612dcdb6ae3c9e1c69ad83f1f0b262e91d33852d",
"initOrData" : "",
}
RLP_List = [0, 1, 1230000, 0, "0x612dcdb6ae3c9e1c69ad83f1f0b262e91d33852d", ""]
Signing a Transaction
Once the raw transaction payload has been created, a user must sign it. This cryptographically links the transaction payload with the sender. It ensures the transaction's origin and contents are unchanged from creation to execution. Furthermore, any user may inspect a transaction to validate its origin and contents, provided they have the public-key for the key-pair.
Sign Transaction:
- Generate the Keccak256 hash of the RLP-serialized transaction payload.
- Sign the transaction hash with the user's private-key.
- Signatures can be either standard ECDSA (64 bytes) or recoverable ECDSA (65 bytes).
Extract signature values:
Once a transaction is signed, it will produce a 64 or 65 byte output signature - the first and second 32 bytes are the values r
and s
respectively. These values are used in the API payload in their respective fields.
The ECDSA Recovery ID v
is the last byte of the output if the signature is recoverable (65-byte signature). This value will be a 0 or 1. Per the Ethereum standard, the v
value should be either 27 or 28 (corresponding to 0 and 1 respectively), so when providing the recovery ID to STRATO, add 27 to the v
value. v
is optional in the API request since it may be inferred from the address
field. v
allows the public-key of the key-pair used for this transaction to be recovered instantly, rather than using the public-key recovery algorithm.
Metadata Elements
The metadata
property in a request payload is used to send STRATO extra information about the request outside of the Ethereum transaction standard. Typically, metadata elements are used in conjunction with SolidVM contracts. You can learn about the metadata property in the API Basics Article.
Using SolidVM
Any contract creation or function call that uses SolidVM must have the VM
property to "SolidVM"
.
{
"metadata": {
"VM": "SolidVM"
}
}
For contract creations, the name
of the contract to be created in the provided code collection must also be included:
{
"metadata": {
"VM": "SolidVM",
"name": "MyContract"
}
}
Function Arguments
Function arguments (for contract constructors or function calls) are provided in the args
property as a comma separated list (no spaces between list elements) surrounded with parentheses.
{
"metadata": {
"VM": "SolidVM",
"args": "(1)"
}
Function Calls
For function calls, the function name must be provided.
{
"metadata": {
"VM": "SolidVM",
"funcName": "MyFunc",
"args": "(42,100)"
}
Sending a Transaction
The transaction/raw
endpoint is identical to the standard /transaction
endpoint in its query parameters and response format. Since the transaction/raw
endpoint only accepts a single transaction, it will only respond with a single response object, rather than an array of results. Visit the transaction endpoint documentation for full details.
Send a transaction by putting the values derived from preparing the transaction in the previous steps into their respective fields of the request body.
Important
Remember to encode the initOrData
value into hexadecimal for a contract creation call or EVM function call.
Example
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"nonce": 23,
"gasPrice": 1,
"gasLimit": 1230000000,
"value": 100,
"to": "0x211446238914bb9dfa81913f961c50ff79556c1d",
"initOrData": "",
"r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
"s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
"address": "4673cb1451756a70de6f2040de3cb1451756af20"
}' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"nonce": 24,
"gasPrice": 1,
"gasLimit": 1230000000,
"value": 0,
"to": null,
"initOrData": "<hex-encoded-contract>",
"r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
"s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
"address": "4673cb1451756a70de6f2040de3cb1451756af20",
"metadata": {
"VM": "SolidVM",
"name": "SimpleStorage",
"args": "(5)"
}
}' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"nonce": 25,
"gasPrice": 1,
"gasLimit": 1230000000,
"value": 0,
"to": "0x446238914bc50ff79556c1d21b9dfa81913f9611",
"initOrData": "",
"r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
"s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
"address": "4673cb1451756a70de6f2040de3cb1451756af20",
"metadata": {
"VM": "SolidVM",
"funcName": "set",
"args": "(5)"
}
}' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"
Response
The transaction hash and transaction result. Note the response
field is the Ethereum standard encoded response from the transaction.
A response from a contract creation transaction:
{
"status": "Success",
"hash": "936f9ea1f29edd4f71af8004c115c90687b9dedc4b99c3e653c6a06f7341e6c1",
"txResult": {
"deletedStorage": "",
"status": "success",
"contractsDeleted": "",
"gasUsed": "0000000000000000000000000000000000000000000000000000000000000123",
"stateDiff": "",
"time": 0.0006,
"kind": "SolidVM",
"chainId": "0x4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e",
"response": "",
"blockHash": "356fa535acf1396be5e8d8fec3745b4331c1531b59efbdb61e86aee8cbdc00d8",
"transactionHash": "fbf20ab650b690b5626b8d3b8e9719460de61d7d87e24a58174338cee5c5ef08",
"etherUsed": "0000000000000000000000000000000000000000000000000000000000000123",
"newStorage": "",
"message": "Success!",
"trace": "",
"contractsCreated": ""
},
"data": null
}
Note
Because transaction responses are returned in the Ethereum format, STRATO does fill in the response data
field, which normally contains information such as the contract compilation info, address, and more. Most of these values are still available in txResult
however they must be extracted or decoded from their respective fields.
Note on Private Chains
The creation of STRATO Private Chains requires a user's key to be present in the key vault. Therefore any user that manages their own keys cannot create a private chain. However any user may send transactions to a private chain, regardless of how their keys are managed.