"Hey smart speaker, swap my Shiba Inu for Pillar"
"Assistant, how much USDC can I buy with what's in my wallet?"
"Send 10 OP to Vitalik and 5 PEPE to Deimantas"
The Universal Orchestrator RPC aims to standardise the minimum shape and requirements of a request for a solution from an arbitrary system managing an Ethereum wallet to, ultimately, an Orchestrator.
An arbitrary system could be a website, device, app, server program etc - anything that manages an Ethereum wallet, speaks Ethereum JSON-RPC and is looking to request solutions from an Orchestrator.
All solutions from an Orchestrator are ChA¹ (Chain Abstraction-first) by default.
Data model standards can be written in any shape. A system will often expose their external interface but require that the request to the aforementioned interface is modelled in a way that the service understands. This creates a huge level of inconsistency and in turn makes Orchestrator interoperability more difficult.
Orchestrators will become more widespread and numerous over time. This is especially true with the advent of Artificial Intelligence (AI) driven systems, the continued advancement of Human Computer Interaction (HCI) devices (especially those that are voice controlled) and the emergence of Extended Reality (XR) platforms.
Standardising the request object that an Orchestrator can understand from a wallet will drive adoption and make decentralised app development easier for developers that don't know how to make on-chain transactions or have the required technical understanding of block building systems.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The Orchestrator's external interface(s) that expose functionality to an end-user application or another system MUST use JavaScript Object Notation (JSON).
The following data definitions are available and MUST prefer chain abstraction (ChA¹), unless stated. Chain abstraction means that, where possible, should an asset span multiple chains - expect a solution from an Orchestrator to use and send assets to and from the supported chains to deliver a complete and cost-effective solution.
The following sequence diagram shows the flow of events in this proposal:
This specification follows the top level data shape of Ethereum JSON-RPC requests, as shown below:
The request definition is what a wallet sends to an Orchestrator for solutions to one or more problems. The request follows the specification from Ethereum JSON-RPC.
interface Rpc: {
id: number; // REQUIRED
jsonrpc: string; // REQUIRED
method: string; // REQUIRED
params: Problem[]; // REQUIRED
}
The top level definition is an Ethereum RPC object.
id
property is a random number that you can assign for your own purposes.jsonrpc
property takes a string
that represents the version of JSON-RPC being used. Usually "2.0"
.method
property is the method call intended for the Orchestrator, and by default CAN be orchestrator_findSolutions
.params
property contains an array of Problem
objects, and is REQUIRED. The Problem object is defined below.interface Problem: {
actions: Action[] // REQUIRED
chainId: number; // OPTIONAL
}
The Problem
definition has just one REQUIRED property, actions
. The Problem
interface leaves space for additional properties in future network upgrades and existing or emerging standards.
actions
property takes an array
of Action
objects, defined below, and is REQUIRED.chainId
takes a number
, representing the chain ID, and is OPTIONAL.chainId
is provided:chainId
property MUST assume 1
interface Action: {
from: string; // REQUIRED
towards: (Asset|Destination)[]; // REQUIRED
with: Offering[]; // OPTIONAL
type: string; // OPTIONAL
functionCallName: string; // OPTIONAL
functionCallData: string; // OPTIONAL
deadline: number; // OPTIONAL
}
The Action
definition has several properties that indicate the desired action. The set properties determine the action that needs to be solved.
from
property is REQUIRED, takes a string
and represents the wallet that this Action
is for.towards
property is REQUIRED, takes an array
of either an Asset
or a Destination
type and represents where this Action
is targeted towards.with
property is OPTIONAL, takes an array
of Offering
type and represents what assets the wallet is prepared to offer to facilitate this Action
with
property contains no Offering
entries, then the Orchestrator MUST consider all assets available in the address space for an Offering
.type
property is OPTIONAL, takes a string
and is intended to help classify this action. Examples might include, but are not limited to - transfer
, swap
, call
etc and is intended to assist the Orchestrator with the action.type
is provided, then 'transfer' MUST be assumedfunctionCallName
property is OPTIONAL, takes a string
and represents the function name to call against the towards
property. If this is defined, the Orchestrator can be assumed that this Action desires to call a smart contract as part of the action.functionCallData
property is OPTIONAL, takes a string
and represents the data payload for functionCallName
. If this is defined but functionCallName
is not, this property SHOULD be ignored.deadline
property is OPTIONAL, takes a number
and represents a wallet-defined unix timestamp for when an action should have a solution by. Useful for high throughput systems, or time sensitive actions.interface Asset: {
symbol: string; // OPTIONAL
address: string; // OPTIONAL
chainId: number; // OPTIONAL
}
The Asset definition defines an asset in question. This definition prefers chain abstraction.
symbol
property is OPTIONAL, takes a string
and represents the symbol of the Asset in question.symbol
is provided, address
must be usedaddress
property is OPTIONAL, takes a string
and represents the address
of the smart contract for this Asset
.address
is provided, the native gas token MUST be usedchainId
property is OPTIONAL, takes a number
and represents the chain that this asset resides on. Useful for direct targeting of an Asset
on a particular chain.chainId
is provided: - The Orchestrator is free to use any corresponding asset on any chain to facilitate the actioninterface Destination: {
address: string; // REQUIRED
chainId: number; // REQUIRED
}
The Destination definition defines a direct target and is used in scenarios where the Orchestrator interpretation MUST NOT be used.
address
property is REQUIRED and takes a string
that represents the address space for this Destination
.chainId
property is REQUIRED and takes a number
and represents the chain the above address
property resides on.interface Offering: {
symbol: string; // SHOULD
address: string; // SHOULD
amount: (number|string); // OPTIONAL
chainId: number; // OPTIONAL
}
The Offering
definition defines what the requester is willing to spend from their wallet in order to facilitate the action being solved.
symbol
property SHOULD be specified and represents the symbol of the Offering
in questionsymbol
is provided, the address MUST be usedaddress
property SHOULD be specified and takes a string
that represents the address space for this Offering
.address
is provided, the native gas token MUST be usedamount
property is OPTIONAL and represents the amount to be offered as part of the Action
. Accepts either a number
, which represents an ether unit, or a string
which can be used for BigNumbers.chainId
property is OPTIONAL and takes a number
that represents the chain the above address
property resides on.chainId
is provided:address
MUST NOT be usedsymbol
MUST be used (ChA¹)chainId
property exists in the Problem
propertyThe response definition is what an Orchestrator sends back as a response to the request for solutions from a wallet. The response, like the request, follows the specification from Ethereum JSON-RPC.
interface Rpc: {
id: string; // REQUIRED
jsonrpc: string; // REQUIRED
result: Solution[]; // REQUIRED
}
The top level object is an Ethereum RPC object.
id
property is REQUIRED, takes a string
and MUST correlate to the same number
that was received as part of the request object to the Orchestrator.jsonrpc
property is REQUIRED, takes a string
and represents the version of JSON-RPC being used. Usually "2.0"
.result
property is REQUIRED and takes an array of Solution
objects. The Solution
object is defined below.interface Solution: {
name: string; // REQUIRED
description: string; // OPTIONAL
transactions: Transaction[]; // REQUIRED
deadline: number; // OPTIONAL
}
The above Solution
interface is the solution to a problem requested above. The Solution
MUST be in the same order to a Problem
that was requested.
name
property is REQUIRED, takes a string
and represents a short, non-technical and user-friendly, name of the solution.description
property is OPTIONAL, takes a string
and represents a longer, non-technical and user-friendly, description of the solution.transactions
property is REQUIRED, takes an array
of Transaction
objects and represents one or more transactions needed for the user to execute the solution.deadline
property is OPTIONAL, takes a number
and represents a unix timestamp by which this Solution
should be executed. This is defined by the Orchestrator.interface Transaction: {
to: Destination; // REQUIRED
chainId: number; // REQUIRED
amount: (number|string); // REQUIRED
calldata: string; // OPTIONAL
}
The above Transaction
interface is a transaction definition that allows a wallet to perform their solution to a problem. There may be 1 or more transactions for a Solution
.
to
property is REQUIRED, takes a Destination
type and represents the target for this Solution.chainId
property is REQUIRED, takes a number and represents the chain that this Transaction
is targeted at. The chainId
is REQUIRED here because the wallet MUST know where to send assets from as an origin due to the existence of multichain assets.amount
property is REQUIRED and represents the amount to be sent as part of the Transaction
. Accepts either a number
, which represents an ether unit, or a string
which can be used for BigNumbers.Problem
object definition, are sparse by design to allow space for future features introduced by other ERC's or network upgrades.No backward compatibility issues found.
The following examples show a few common scenarios with their requests to, and from, an Orchestrator. All examples are chain abstracted (ChA¹) by default, unless specified.
The following request performs an action:
[!NOTE] Notes: All requests for solutions should be chain abstracted (ChA¹) by default. The Orchestrator > > can check for 5 USDC on any chain for the above "from" address, and send a solution that receives > the 5 USDC on any other chain.
{
"id": 1234,
"jsonrpc": "2.0",
"method": "orchestrator_findSolutions",
"params": {
"problems": [
{
"actions": [
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"address": "0x50840CE036eEf2005d3c4d6f6Eb65f8116a01629"
},
"with": [
{
"symbol": "USDC",
"amount": 5
}
]
}
]
}
]
}
}
{
"id": 1234,
"jsonrpc": "2.0",
"result": [
{
"name": "Send 5 USDC",
"description": "Send 5 USDC from 0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165 to 0x50840CE036eEf2005d3c4d6f6Eb65f8116a01629",
"transactions": [
{
"to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"chainId": 1,
"calldata": "0x0...",
"value": 0
}
]
}
]
}
The following request performs an action:
[!NOTE] Notes: All requests for solutions should be chain abstracted (ChA¹) by default. The Orchestrator can take 0.1 native asset from any chain in return for USDC on any chain.
{
"id": 1337,
"jsonrpc": "2.0",
"method": "orchestrator_findSolutions",
"params": {
"problems": [
{
"actions": [
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"symbol": "USDC"
},
"with": [
{
"amount": 0.1
}
]
}
]
}
]
}
}
{
"id": 1337,
"jsonrpc": "2.0",
"result": [
{
"name": "Swap 0.1 ETH for 371.498 USDC",
"description": "Swapping 0.1 ETH from 0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165 to 371.498 USDC via Uniswap",
"transactions": [
{
"to": "0x...",
"chainId": 1,
"calldata": "0x0...",
"value": 0
},
{
"to": "0x...",
"chainId": 1,
"calldata": "0x...",
"value": 0.1
}
]
}
]
}
The following request performs an action:
[!NOTE] Notes: All requests for solutions should be chain abstracted (ChA¹) by default. The Orchestrator can take any amount of SHIB and / or Pillar from any chain in return for an exchanged amount of USDC on any chain.
{
"id": 1234,
"jsonrpc": "2.0",
"method": "orchestrator_findSolutions",
"params": {
"problems": [
{
"actions": [
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"symbol": "USDC"
},
"with": [
{
"symbol": "SHIB"
},
{
"symbol": "Pillar"
}
]
}
]
}
]
}
}
{
"id": 1337,
"jsonrpc": "2.0",
"result": [
{
"name": "Swap 171,246 SHIB and 1004.72 Pillar for 10 USDC",
"description": "Swapping 171,246 SHIB and 1004.72 Pillar from 0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165 to 10 USDC via Uniswap",
"transactions": [
{
"to": "0x...",
"chainId": 1,
"calldata": "0x0...",
"value": 0
},
{
"to": "0x...",
"chainId": 1,
"calldata": "0x0...",
"value": 0
},
{
"to": "0x...",
"chainId": 1,
"calldata": "0x...",
"value": 0.1
}
]
}
]
}
The following request performs the following actions:
[!NOTE] Notes: All requests for solutions should be chain abstracted (ChA¹) by default. The Orchestrator can move the specified asset amounts on any chain where the asset exists in the "from" address.
{
"id": 1000,
"jsonrpc": "2.0",
"method": "orchestrator_findSolutions",
"params": {
"problems": [
{
"actions": [
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"address": "0x50840CE036eEf2005d3c4d6f6Eb65f8116a01629"
},
"with": [
{
"symbol": "USDC",
"amount": 5
}
]
},
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"address": "0xFCd239451346238B5560511Ae47A0b82b1bbE9f0"
},
"with": [
{
"symbol": "PLR",
"amount": 100
}
]
}
]
}
]
}
}
{
"id": 1000,
"jsonrpc": "2.0",
"result": [
{
"name": "Send 5 USDC to 0x...629 and 100 PLR to 0x...9f0",
"description": "Send 5 USDC to 0x50840CE036eEf2005d3c4d6f6Eb65f8116a01629 and 100 PLR to 0xFCd239451346238B5560511Ae47A0b82b1bbE9f0",
"transactions": [
{
"to": "0x...",
"chainId": 1,
"calldata": "0x0...",
"value": 0
},
{
"to": "0x...",
"chainId": 1,
"calldata": "0x...",
"value": 0
}
]
}
]
}
The following request performs an action:
[!NOTE] Notes: All requests for solutions should be chain abstracted (ChA¹) by default - HOWEVER in this example, the
chainId
property on theDestination
interface has been specified. The operation should now be locked to the specified chain. BecausefunctionCallName
andfunctionCallData
exist, the Orchestrator can infer that this is a smart contract call and act accordingly.
{
"id": 420,
"jsonrpc": "2.0",
"method": "orchestrator_findSolutions",
"params": {
"problems": [
{
"actions": [
{
"from": "0xbafB4E1EFA94B359e2E175CF6156AedA2cACa165",
"towards": {
"address": "0x50840CE036eEf2005d3c4d6f6Eb65f8116a01629",
"chainId": 137
},
"with": [
{
"symbol": "USDC",
"amount": 1
}
],
"functionCallName": "inscribe",
"functionCallData": "0x..."
}
]
}
]
}
}
{
"id": 420,
"jsonrpc": "2.0",
"result": [
{
"name": "Inscribe with 1 USDC",
"description": "Call the Inscribe function with 1 USDC on Polygon",
"transactions": [
{
"to": "0x...",
"chainId": 137,
"calldata": "0x0...",
"value": 0
},
{
"to": "0x...",
"chainId": 137,
"calldata": "0x...",
"value": 0
}
]
}
]
}
The ability for anyone to build an Orchestrator inherently brings the opportunity for code errors and therefore a degraded service. Orchestrators may also be abandoned over time. A reputation score should be leveraged by the Orchestrator to determine if the Orchestrator is fit for purpose. This should be up to the requesting system or wallet to determine.
An Orchestrator may return transactions as part of a solution that are wrong or attempt to take more than what was asked of it. Where possible, the Orchestrator should validate returned transaction address destinations and any other data.
Whilst not a security consideration per-se, some Orchestrators may gravitate towards their own business targets which may skew the outcome of Orchestrator solutions. The systems or wallets requesting solutions from Orchestrators should be mindful of this unless it is intended.
Copyright and related rights waived via CC0.