# Near Polywrapper Specification
Under the scope of the current grant, we promised to (1) implement 2-4 functions in a Polywrapper and (2) produce a specification of a full Near Polywrapper for dApp developers. We exceeded our promise and implemented 16 functions, some of which are primarily WASM-based while others are written primarily in JavaScript. A mixed approach helped us implement functions iteratively and ensure the deliverable of this grant is useful. Under the scope of a new grant, we can proceed to move most of the existing JavaScript logic to AssemblyScript and add many additional functions to which Near's dApp developer community have become accustomed.
The full specification of a future Near Polywrapper is described in this document. The proposed interface is outlined in a series of three GraphQL schemas. The schemas contain notes clarifying which functions have already been imlemented and tested.
# Two Parts
The Polywrapper implementation has two parts:
1. Polywrapper: A WASM port of the near-api-js SDK
2. JavaScript Plugin: A Polywrap Client plugin used for actions that cannot be performed within a WASM module
# Polywrapper
The Polywrapper is a set of WASM modules that contain the bulk of the logic ported from the near-api-js JavaScript SDK. The Polywrapper calls the aforementioned JavaScript Plugin only when necessary to perform specific tasks.
A call to the Polywrapper might look something like this:
```typescript=
const result = await client.query<{ findAccessKey: AccessKeyInfo }>({
uri: "w3://ens/near-api.web3api.eth",
query: `query {
findAccessKey(
accountId: $accountId
)
}`,
variables: {
accountId: "polywraptest.testnet",
}
});
```
A Polywrapper can have two WASM modules--a "Mutation" module and a "Query" module. As the names imply, functions in the Mutation module may change on-chain state while Query functions do not.
The proposed GraphQL schema specification for the Query module:
```graphql=
#import { Query, Transaction, Action, PublicKey, KeyType, Signature } into Near from "w3://ens/nearPlugin.web3api.eth"
type Query {
"""
Wallet Query Functions (Implemented, Not Tested)
"""
requestSignIn(
contractId: String
methodNames: [String!]
successUrl: String
failureUrl: String
): Boolean!
signOut: Boolean!
isSignedIn: Boolean!
getAccountId: String
"""
RPC Query Functions not part of JsonRpcProvider (Implemented, Tested)
"""
getAccountState(
accountId: String!
): AccountView!
findAccessKey(
accountId: String!
): AccessKeyInfo
getPublicKey(
accountId: String!
): Near_PublicKey
"""
RPC Query Functions not part of JsonRpcProvider (Not Implemented)
"""
# get account balance
getAccountBalance(
accountId: String!
): AccountBalance!
# get list of authorized applications
getAccountDetails(
accountId: String!
): [AccountAuthorizedApp]!
# get all access keys associated with account
getAccessKeys(
accountId: String!
): [AccessKeyInfo]!
# Invoke a contract view function using the RPC API
viewFunction(
contractId: String!
methodName: String!
args: JSON!
): JSON!
viewContractState(
prefix: (String | Bytes)!
blockQuery: BlockReference!
): [KeyValuePair]!
"""
Transaction Query Functions (Implemented, Tested)
"""
# creates a transaction. If signerId is not provided, creates transaction with wallet.
createTransaction(
receiverId: String!
actions: [Near_Action!]!
signerId: String
): Near_Transaction!
# signs a transaction without wallet
signTransaction(
transaction: Near_Transaction!
): SignTransactionResult!
"""
Utility Functions (Not Implemented)
"""
# takes amount in Near, returns amount in YoctoNear
parseNearAmount(
amount: BigInt!
): BigInt!
# takes amount in YoctoNear, returns amount in Near
formatNearAmount(
amount: BigInt!
): BigInt!
"""
JsonRpcProvider Query Functions (Implemented, Tested)
"""
getBlock(
blockQuery: BlockReference!
): BlockResult!
"""
JsonRpcProvider Query Functions (Not Implemented)
"""
status(): NodeStatusResult!
txStatus(
txHash: (Bytes | String)!,
accountId: String!
): FinalExecutionOutcome!
txStatusReceipts(
txHash: Bytes!
accountId: String!
): FinalExecutionOutcome!
blockChanges(
blockQuery: BlockReference!
): BlockChangeResult!
chunk(
chunkId: (String | [BigInt!])! # chunk hash or [blockId, chunkId]
): ChunkResult!
validators(
blockId: BigInt
): EpochValidatorInfo!
experimental_protocolConfig(
blockReference: BlockReference!
): NearProtocolConfig!
lightClientProof(
request: LightClientProofRequest!
): LightClientProof!
accessKeyChanges(
accountIdArray: [String!]!
blockQuery: BlockReference!
): ChangeResult!
singleAccessKeyChanges(
accessKeyArray: [AccessKeyWithPublicKey!]!,
blockQuery: BlockReference
): ChangeResult
accountChanges(
accountIdArray: [String!]!
blockQuery: BlockReference!
): ChangeResult!
contractStateChanges(
accountIdArray: [String!]!
blockQuery: BlockReference!
keyPrefix: String
): ChangeResult!
contractCodeChanges(
accountIdArray: [String!]!
blockQuery: BlockReference!
): ChangeResult!
gasPrice(
blockId: BigInt
): BigInt!
}
"""
Query Types
"""
type AccountView {
blockHeight: BigInt!
blockHash: String!
amount: String!
locked: String!
codeHash: String!
storageUsage: BigInt!
storagePaidAt: BigInt!
}
type AccountBalance {
total: String!
stateStaked: String!
staked: String!
available: String!
}
type AccountAuthorizedApp {
contractId: String!
amount: String!
publicKey: String!
}
type BlockReference {
blockId: String
finality: String
syncCheckpoint: String
}
type BlockHeader {
height: BigInt!
epoch_id: String!
next_epoch_id: String!
hash: String!
prev_hash: String!
prev_state_root: String!
chunk_receipts_root: String!
chunk_headers_root: String!
chunk_tx_root: String!
outcome_root: String!
chunks_included: BigInt!
challenges_root: String!
timestamp: BigInt!
timestamp_nanosec: String!
random_value: String!
validator_proposals: [JSON!]!
chunk_mask: [Boolean!]!
gas_price: String!
rent_paid: String!
validator_reward: String!
total_supply: String!
challenges_result: [JSON!]!
last_final_block: String!
last_ds_final_block: String!
next_bp_hash: String!
block_merkle_root: String!
approvals: [String]!
signature: String!
latest_protocol_version: BigInt!
}
type Chunk {
chunk_hash: String!
prev_block_hash: String!
outcome_root: String!
prev_state_root: String!
encoded_merkle_root: String!
encoded_length: BigInt!
height_created: BigInt!
height_included: BigInt!
shard_id: BigInt!
gas_used: BigInt!
gas_limit: BigInt!
rent_paid: String!
validator_reward: String!
balance_burnt: String!
outgoing_receipts_root: String!
tx_root: String!
validator_proposals: [JSON!]!
signature: String!
}
type BlockResult {
author: String!
header: BlockHeader!
chunks: [Chunk!]!
}
type ChunkHeader {
balance_burnt: String!
chunk_hash: String!
encoded_length: BigInt!
encoded_merkle_root: String!
gas_limit: BigInt!
gas_used: String!
height_created: BigInt!
height_included: BigInt!
outgoing_receipts_root: String!
prev_block_hash: String!
prev_state_num_parts: BigInt!
prev_state_root_hash: String!
rent_paid: String!
shard_id: BigInt!
signature: String!
tx_root: String!
validator_proposals: [JSON!]!
validator_reward: String!
}
type ChunkResult {
header: ChunkHeader!
receipts: [JSON!]!
transactions: [Transaction!]!
}
type BlockChange {
chagneType: String!
account_id: String!
}
type BlockChangeResult {
block_hash: String!
changes: [BlockChange!]!
}
type SyncInfo {
latest_block_hash: String!
latest_block_height: BigInt!
latest_block_time: String!
latest_state_root: String!
syncing: Boolean!
}
type Version {
version: String!
build: String!
}
type NodeStatusResult {
chain_id: String!
rpc_addr: String!
sync_info: SyncInfo!
validators: [String!]!
version: Version!
}
type CurrentEpochValidatorInfo {
account_id: String!
public_key: String!
is_slashed: Boolean!
stake: String!
shards: [BigInt!]!
num_produced_blocks: UInt!
num_expected_blocks: UInt!
}
type NextEpochValidatorInfo {
account_id: String!
public_key: String!
stake: String!
shards: [BigInt!]!
}
type ValidatorStakeView {
account_id: String!
public_key: String!
stake: String!
}
type EpochValidatorInfo {
next_validators: [NextEpochValidatorInfo!]!
current_validators: [CurrentEpochValidatorInfo!]!
next_fisherman: [ValidatorStakeView!]!
current_fisherman: [ValidatorStakeView!]!
current_proposals: [ValidatorStakeView!]!
prev_epoch_kickout: [ValidatorStakeView!]!
epoch_start_height: BigInt!
}
type NearProtocolConfig {
runtime_config: NearProtocolRuntimeConfig!
}
type NearProtocolRuntimeConfig {
storage_amount_per_byte: String!
}
type MerkleNode {
hash: String!
direction: String!
}
type BlockHeaderInnerLiteView {
height: BigInt!
epoch_id: String!
next_epoch_id: String!
prev_state_root: String!
outcome_root: String!
timestamp: BigInt!
next_bp_hash: String!
block_merkle_root: String!
}
type LightClientBlockLiteView {
prev_block_hash: String!
inner_rest_hash: String!
inner_lite: BlockHeaderInnerLiteView!
}
type ExecutionOutcomeWithIdView {
proof: [MerkleNode!]!
block_hash: String!
id: String!
outcome: ExecutionOutcome!
}
type LightClientProof {
outcome_proof: ExecutionOutcomeWithIdView!
outcome_root_proof: [MerkleNode!]!
block_header_lite: LightClientBlockLiteView!
block_proof: [MerkleNode!]!
}
enum IdType {
Transaction
Receipt
}
type LightClientProofRequest {
type: IdType!
light_client_head: String!
transaction_hash: String
sender_id: String
receipt_id: String
receiver_id: String
}
type ChangeResult {
block_hash: String!
changes: [JSON!]!
}
type AccessKeyWithPublicKey {
account_id: String!
public_key: String!
}
type KeyValuePair {
key: Bytes
value: Bytes
}
"""
Common Types
"""
type SignedTransaction {
transaction: Transaction!
signature: Near_Signature!
}
# Return value of Mutation.signTransaction(...); contains transaction hash and signed transaction
type SignTransactionResult {
hash: Bytes!
signedTx: SignedTransaction!
}
type ExecutionStatus {
successValue: String
successReceiptId: String
failure: JSON
}
type ExecutionOutcomeWithId {
block_hash: String
id: String!
outcome: ExecutionOutcome!
proof: [ExecutionProof!]
}
type ExecutionProof {
direction: String!
hash: String!
}
# Execution status of a sent transaction
type ExecutionOutcome {
executor_id: String
gas_burnt: BigInt!
logs: [String!]
metadata: OutcomeMetaData
receipt_ids: [String!]!
status: ExecutionStatus!
tokens_burnt: String
}
type OutcomeMetaData {
gas_profile: [GasProfile]!
version: UInt!
}
type GasProfile {
cost: String!
cost_category: String!
gas_used: String!
}
# Final outcome of a sent transaction
type FinalExecutionOutcome {
status: ExecutionStatus!
transaction: Transaction!
transaction_outcome: ExecutionOutcomeWithId!
receipts_outcome: [ExecutionOutcomeWithId!]!
}
```
The proposed GraphQL schema specification for the Mutation module:
```graphql=
#import { Query, Transaction, Action, PublicKey, Signature } into Near from "w3://ens/nearPlugin.web3api.eth"
type Mutation {
"""
JsonRpcProvider Mutation Functions (Implemented, Tested)
"""
# send a JSON RPC to Near node
sendJsonRpc(
method: String!
params: JSON!
): JSON!
"""
Generic Mutation Functions (Implemented, Tested)
"""
# send one or more transactions to NEAR wallet to be signed and executed
requestSignTransactions(
# list of transactions to sign
transactions: [Near_Transaction!]!
# url NEAR Wallet will redirect to after transaction signing is complete
callbackUrl: String
# meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param
meta: String
): Boolean!
# sends a signed transaction and awaits execution
sendTransaction(
signedTx: SignedTransaction!
): FinalExecutionOutcome!
# sends a signed transaction and immediately returns transaction hash
sendTransactionAsync(
signedTx: SignedTransaction!
): String!
# creates, signs, and sends a transaction without wallet and awaits execution
signAndSendTransaction(
receiverId: String!
actions: [Near_Action!]!
signerId: String!
): FinalExecutionOutcome!
# creates, signs, and sends a transaction without wallet and immediately returns transaction hash
signAndSendTransactionAsync(
receiverId: String!
actions: [Near_Action!]!
signerId: String!
): String!
"""
Convenience Mutation Functions (Not Implemented)
"""
# create a new Near account
createAccount(
newAccountId: String!
publicKey: Near_PublicKey! # | String
amount: BigInt
signerId: String!
): FinalExecutionOutcome!
# delete Near account and transfer remaining funds to beneficiary
deleteAccount(
accountId: String!
beneficiaryId: String!
signerId: String!
): FinalExecutionOutcome!
# deploy a contract
deployContract(
data: Bytes!
contractId: String!
signerId: String!
): FinalExecutionOutcome!
# transfer Near from signer to receiver
sendMoney(
amount: BigInt!
receiverId: String!,
signerId: String!
): FinalExecutionOutcome!
# call a contract function
functionCall(
contractId: String!
methodName: String!
args: JSON
gas: BigInt
deposit: BigInt
walletMeta: String
walletCallbackUrl: String
SignerId: String
): FinalExecutionOutcome!
# add access key to account
addKey(
publicKey: Near_PublicKey! # | String
contractId: String
methodNames: [String!],
amount: BigInt
signerId: String!
): FinalExecutionOutcome!
# delete access key associated with public key
deleteKey(
publicKey: Near_PublicKey! # | String
signerId: String!
): FinalExecutionOutcome!
# Create a new account and deploy a contract to it
createAndDeployContract(
contractId: String!
publicKey: (String | Near_PublicKey)!
data: Bytes!
amount: BigInt!
): Boolean!
}
"""
Common Types
"""
type SignedTransaction {
transaction: Transaction!
signature: Near_Signature!
}
# Return value of Mutation.signTransaction(...); contains transaction hash and signed transaction
type SignTransactionResult {
hash: Bytes!
signedTx: SignedTransaction!
}
type FinalExecutionStatus {
successValue: String
failure: JSON
}
type ExecutionStatus {
successValue: String
successReceiptId: String
failure: JSON
}
type ExecutionOutcomeWithId {
id: String!
outcome: ExecutionOutcome!
}
# Execution status of a sent transaction
type ExecutionOutcome {
logs: [String!]!
receiptIds: [String!]!
gasBurnt: BigInt!
status: ExecutionStatus!
}
# Final outcome of a sent transaction
type FinalExecutionOutcome {
status: FinalExecutionStatus!
transaction: Transaction!
transaction_outcome: ExecutionOutcomeWithId!
receipts_outcome: [ExecutionOutcomeWithId!]!
}
```
# Near JavaScript Plugin
A JavaScript plugin is necessary to perform the following actions that cannot be implemented directly within a Polywrapper due to limitations of WASM:
1. Browser interaction, including interaction with the Near Wallet
2. Filesystem interaction, such as reading and writing to local keystores
3. Sending HTTP requests, including JSON RPC queries
Likewise, Polywrapper execution is stateless and therefore the plugin is used to cache the network configuration parameters provided when instantiating the plugin. Configuration items can include a network ID, RPC URLs, a near-api-js KeyStore class instance, and other optional data developers might provide when instantiating a near-api-js Near class instance.
The plugin would typically be instantiated and configured when instantiating the Polywrap Client, like so:
```typescript=
import {
nearPlugin,
KeyStores
} from "@web3api/near-plugin-js";
const client = new Web3ApiClient({
plugins: [{
uri: "w3://ens/near-plugin-js.web3api.eth",
plugin: nearPlugin({
networkId: "testnet",
keyStore: new KeyStores.BrowserLocalStorageKeyStore(),
nodeUrl: "https://rpc.testnet.near.org",
walletUrl: "https://wallet.testnet.near.org",
helperUrl: "https://helper.testnet.near.org",
explorerUrl: "https://explorer.testnet.near.org",
})
}]
})
```
The GraphQL schema of the Near Plugin produced under the scope of the current grant (with types declarations removed for readability):
```graphql=
type Query {
requestSignIn(
contractId: String
methodNames: [String!]
successUrl: String
failureUrl: String
): Boolean!
signOut: Boolean!
isSignedIn: Boolean!
getAccountId: String
getPublicKey(
accountId: String!
): PublicKey
"""
Creates a transaction.
If signerId is provided, the transaction will be signed using data from the KeyStore in the plugin config.
Otherwise, wallet authorization is expected.
"""
createTransactionWithWallet(
receiverId: String!
actions: [Action!]!
): Transaction!
# signs a transaction without wallet
signTransaction(
transaction: Transaction!
): SignTransactionResult!
}
type Mutation {
sendJsonRpc(
method: String!
params: JSON!
): JSON!
# send one or more transactions to NEAR wallet to be signed and executed
requestSignTransactions(
transactions: [Transaction!]!
callbackUrl: String
meta: String
): Boolean!
# sends a signed transaction and awaits execution
sendTransaction(
signedTx: SignedTransaction!
): FinalExecutionOutcome!
# sends a signed transaction and immediately returns transaction hash
sendTransactionAsync(
signedTx: SignedTransaction!
): String!
}
```
The proposed GraphQL schema specification for the Plugin:
```graphql=
type Query {
"""
Wallet Query Functions (Implemented, Not Tested)
"""
requestSignIn(
contractId: String
methodNames: [String!]
successUrl: String
failureUrl: String
): Boolean!
signOut: Boolean!
isSignedIn: Boolean!
getAccountId: String
createTransactionWithWallet(
receiverId: String!
actions: [Action!]!
): Transaction!
"""
KeyStore, KeyPair, and Signer Query Functions (Implemented, Tested)
"""
getPublicKey(
accountId: String!
): PublicKey
"""
KeyStore, KeyPair, and Signer Query Functions (Not Implemented)
"""
signMessage(
message: Bytes!
signerId: String!
): Signature!
}
type Mutation {
"""
Generic Functions (Implemented, Tested)
"""
sendJsonRpc(
method: String!
params: JSON!
): JSON!
"""
Wallet Mutation Functions (Implemented, Not Tested)
"""
# send one or more transactions to NEAR wallet to be signed and executed
requestSignTransactions(
transactions: [Transaction!]!
callbackUrl: String
meta: String
): Boolean!
"""
KeyStore, KeyPair, and Signer Query Functions (Not Implemented)
"""
createKey(
accountId: String!
networkId: String!
): PublicKey!
}
"""
Plugin Types (can be imported by Polywrapper to prevent redundancy)
"""
# Supported public key types
enum KeyType {
ed25519
}
# Account public key data
type PublicKey {
keyType: KeyType!
data: Bytes!
}
type CreateAccount {}
type DeployContract { code: Bytes! }
type FunctionCall { methodName: String! args: JSON gas: BigInt! deposit: BigInt! }
type Transfer { deposit: BigInt! }
type Stake { stake: BigInt! publicKey: PublicKey! }
type AddKey { publicKey: PublicKey! accessKey: AccessKey! }
type DeleteKey { publicKey: PublicKey! }
type DeleteAccount { beneficiaryId: String! }
# Action types define the data necessary to complete a type of action in a transaction
type Action = ( CreateAccount | DeployContract | FunctionCall | Transfer | Stake | AddKey | DeleteKey | DeleteAccount)!
type Transaction {
signerId: String!
publicKey: PublicKey!
nonce: BigInt!
receiverId: String!
actions: [Action!]!
blockHash: Bytes
hash: String
}
type Signature {
keyType: KeyType!
data: Bytes!
}
```