A library to assist with the EOSIO Signing Request (ESR) protocol. The full specification for ESR (Revision 3) is available here:
https://github.com/eosio-eps/EEPs/blob/master/EEPS/eep-7.md
The ESR protocol allows for an application (dapp) to generate signature requests (transactions) which can then be passed to signers (wallets) for signature creation. These signature requests can be used within URI links, QR Codes, or other transports between applications and signers.
To add eosio-signing-request to your project, install via the package manager of your choice:
npm install eosio-signing-request
yarn add eosio-signing-request
In an environment where an application/dapp is requesting that an end user perform a transaction within their preferred signer/wallet, each of these applications will utilize the eosio-signing-request
library to fulfill different roles.
- The application/dapp will be creating and encoding the signing request.
- The signer/wallet will be decoding and resolving the signing request.
The specification itself then allows either the signer/wallet itself to broadcast the finalized transaction, or the transaction/signature themselves can be passed back to the application/dapp to broadcast.
The eosio-signing-request
library is not responsible for transporting this information between the application/dapp
and signer/wallet, and so this topic will not be covered in this README.
These examples will use nodejs to create and manipulate a signing request, which can then be sent to any compatible signer for signature creation and ultimately sent to an EOSIO blockchain.
The code within this README will show partial snippets of the process, with full working examples located here:
https://github.com/greymass/eosio-signing-request-demo
To create a signing request, the first piece of data we need is either an EOSIO transaction or action(s). For all examples in this README we will use the eosio:voteproducer
action to set a proxy of greymassvote
.
The actions are as follows:
const actions = [{
account: 'eosio',
name: 'voteproducer',
authorization: [{
actor: '............1',
permission: '............2'
}],
data: {
voter: '............1',
proxy: 'greymassvote',
producers: [],
}
}]
Two things to note:
- The
actor
andvoter
fields contain a placeholder which resolves to the signers account name (............1
). - The
permission
fields contain a placeholder which resolves to the signers account permission (............2
).
These are optional parameters that can be passed anywhere within the authorization
or data
fields. If the application already knows who the end user is, the application can bypass the use of placeholders and specify that data directly.
Many of the SigningRequest
method calls below will reference an opts
parameter, which is a set of options that tell the signing request how to perform various tasks. For these examples, we will be using the following opts
value.
const opts = {
// string encoder
textEncoder,
// string decoder
textDecoder,
// zlib string compression (optional, recommended)
zlib: {
deflateRaw: (data) => new Uint8Array(zlib.deflateRawSync(Buffer.from(data))),
inflateRaw: (data) => new Uint8Array(zlib.inflateRawSync(Buffer.from(data))),
},
// Customizable ABI Provider used to retrieve contract data
abiProvider: {
getAbi: async (account) => (await eos.getAbi(account))
}
}
With the above actions established, to create the signing request itself we use the eosio-signing-request library and its create
method. The full working example to create this request can be found here.
(ES8 or TypeScript)
const request = await SigningRequest.create({ actions }, opts)
console.log(request)
(ES6)
SigningRequest.create({ actions }, opts).then((request) => {
console.log(request)
})
This call will return an instance of a SigningRequest
SigningRequest {
version: 2,
data: {
req: [
'action',
{
account: 'eosio',
name: 'voteproducer',
authorization: [ { actor: '............1', permission: '............2' } ],
data: '0100000000000000A032DD181BE9D56500'
}
],
chain_id: [ 'chain_alias', 1 ],
flags: 1,
callback: '',
info: []
}
}
With an instance of a SigningRequest
available, we can now call the encode
method on it in order to generate a compressed payload to transport to a signing application.
const encoded = request.encode()
This encoded
variable will now contain a string we can either pass directly to the signer via URI, QRCode or any other method. The string itself is:
esr://gmNgZGRkAIFXBqEFopc6760yugsVYWBggtKCMIEFRnclpF9eTWUACgAA
These encoded strings can be shared and viewed by a number of applications, including:
EOSIO.to
This website is a utility which allows the viewing of a signing request as well as the opportunity to create a signature for it. The above encoded request can be passed to the eosio.to domain:
https://eosio.to/gmNgZGRkAIFXBqEFopc6760yugsVYWBggtKCMIEFRnclpF9eTWUACgAA
EOSIO URI Builder
This web application allows for the viewing, editing, and customization of signing requests. The above encoded request can be passed to the builder via a URL parameter:
Using the encoded signing request generated in the example above:
const uri = 'esr://gmNgZGRkAIFXBqEFopc6760yugsVYWBggtKCMIEFRnclpF9eTWUACgAA'
Another application can now decode this request into an instance of a SigningRequest
with the from
method. The full working example for decoding can be found here.
const decoded = SigningRequest.from(uri, opts)
Decoding the signing request will return the same instance as when it was originally created, as follows:
SigningRequest {
version: 2,
data: {
chain_id: [ 'chain_alias', 1 ],
req: [
'action[]',
[
{
account: 'eosio',
name: 'voteproducer',
authorization: [ { actor: '............1', permission: '............2' } ],
data: '0100000000000000A032DD181BE9D56500'
}
]
],
flags: 1,
callback: '',
info: []
},
textEncoder: TextEncoder { encoding: 'utf-8' },
textDecoder: TextDecoder { encoding: 'utf-8', fatal: false, ignoreBOM: false },
zlib: {
deflateRaw: [Function: deflateRaw],
inflateRaw: [Function: inflateRaw]
},
abiProvider: { getAbi: [AsyncFunction: getAbi] },
signature: undefined
}
This SigningRequest
can then be used within signing applications to start interacting with the request itself.
With an instance of a SigningRequest
available, a signing application can now resolve the specific request into a transaction. The resolving step does a few things:
- Generates a transaction with valid TAPOS values.
- Templates the transaction, removing any placeholders and resolving it to be used by a specific end user.
- Serializes the transaction for use within the signer.
This step now requires that the application understand who the user is, and have access to the blockchain itself to retrieve TAPOS values. The full example of the code below to resolve a signing request can be found here.
// An encoded eosio:voteproducer transaction
const uri = 'esr://gmNgZGRkAIFXBqEFopc6760yugsVYWBggtKCMIEFRnclpF9eTWUACgAA'
// Decode the URI
const decoded = SigningRequest.from(uri, opts)
// In order to resolve the transaction, we need a recent block
const head = (await rpc.get_info(true)).head_block_num;
const block = await rpc.get_block(head);
// Fetch the ABIs needed for decoding
const abis = await decoded.fetchAbis();
// An authorization to resolve the transaction to
const authorization = {
actor: 'teamgreymass',
permission: 'active',
}
// Resolve the transaction as a specific user
const resolved = await decoded.resolve(abis, authorization, block);
The resolve
method will return an instance of a ResolvedSigningRequest
, which contains:
- The original
SigningRequest
asrequest
. - The
signer
that was used to template the transaction. - The
transaction
which has been templated and is ready to use. - The transaction already serialized as
serializedTransaction
.
Below is the representation of an instance of this object.
ResolvedSigningRequest {
request: SigningRequest {
version: 2,
data: {
chain_id: [ 'chain_alias', 1 ],
req: [
'action[]',
[
{
account: 'eosio',
name: 'voteproducer',
authorization: [ { actor: '............1', permission: '............2' } ],
data: '0100000000000000A032DD181BE9D56500'
}
]
],
flags: 1,
callback: '',
info: []
},
textEncoder: TextEncoder { encoding: 'utf-8' },
textDecoder: TextDecoder { encoding: 'utf-8', fatal: false, ignoreBOM: false },
zlib: {
deflateRaw: [Function: deflateRaw],
inflateRaw: [Function: inflateRaw]
},
abiProvider: { getAbi: [AsyncFunction: getAbi] },
signature: undefined
},
signer: { actor: 'teamgreymass', permission: 'active' },
transaction: {
actions: [
{
account: 'eosio',
name: 'voteproducer',
authorization: [ { actor: 'teamgreymass', permission: 'active' } ],
data: { voter: 'teamgreymass', proxy: 'greymassvote', producers: [] }
}
],
context_free_actions: [],
transaction_extensions: [],
expiration: '2020-01-08T18:44:57.000',
ref_block_num: 1423,
ref_block_prefix: 4278398322,
max_cpu_usage_ms: 0,
max_net_usage_words: 0,
delay_sec: 0
},
serializedTransaction: Uint8Array [
41, 35, 22, 94, 143, 5, 114, 45, 3, 255, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 234, 48, 85, 112,
21, 210, 137, 222, 170, 50, 221, 1, 128, 177, 145, 94,
93, 38, 141, 202, 0, 0, 0, 0, 168, 237, 50, 50,
17, 128, 177, 145, 94, 93, 38, 141, 202, 160, 50, 221,
24, 27, 233, 213, 101, 0, 0
]
}
The transaction
or serializedTransaction
can now be used within any signature provider to generate a signature for this transaction, and ultimately broadcast the signed transaction to the blockchain.
This README will be updated further to provide more usage as time progresses. The library itself already supports sessions, identities, callbacks, signature generation, and more. It will just take time to properly document every use case.
We have a telegram channel dedicated to the development of this protocol which you can find here: