Initial meta issue: massalabs#13
Authors: G.Libert, N.Seva
Status: Draft
Version: 0.4
This standard defines a protocol for communication between a cryptocurrency wallet and a decentralized application (DApp) running in a web browser. The protocol provides a standard interface for DApps to request user authorization for blockchain transactions, as well as a standard mechanism for wallets to sign and broadcast those transactions. By following this standard, DApp developers can provide a seamless user experience for users of any wallet that implements the protocol, while wallet developers can ensure that their products are compatible with a wide range of DApps.
This standard is targeted towards developers who are building decentralized applications (DApps) and cryptocurrency wallets that need to interact with each other in a browser environment.
The standard assumes a working knowledge of web development and blockchain technology. It is also assumed that the reader has experience with browser extensions and understands the basic principles of secure communication between web pages and extensions.
NOTE: For more information on content scripts and background scripts, see MDN's browser extensions pages.
To facilitate communication between a DApp and a wallet, an event-based messaging system will be employed. There are two types of events that can be used in this system:
-
Events used by the wallet extension to communicate with the web page. These events are triggered on a static target, specifically an invisible HTML element that is attached to the document body and assigned an ID of
massaWalletProvider
. -
Events used by the web page to communicate with the wallet extension. These events are triggered on an extension-specific target, which is an invisible HTML element with an ID of
massaWalletProvider-<wallet provider name>
, also attached to the document body.
For instance, if the wallet provider name is "AwesomeWallet," the second target ID for this extension would be the element returned by document.getElementById('massaWalletProvider-AwesomeWallet')
.
It's worth noting that this implementation follows the considerations set forth by Mozilla in this section.
This event is used by the extension to register itself to the webpage as a wallet provider.
Direction | Type | Format | Example |
---|---|---|---|
Extension to webpage | register |
type:
object
properties:
address:
type: string
required:
- address |
{
"id": "awesomeWalletprovider",
"name": "Your Awesome Wallet Provider"
} |
This event is used by the webpage to list known accounts by the extension.
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.list |
none | null |
Extension to webpage | account.list.response |
type: array
items:
type: object
properties:
address:
type: string
format: base58check
name:
type: string
required:
- address |
[
{
"address": "A12...",
"name": "Account 1"
},
{
"address": "A12..."
},
] |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.balance |
type: object
properties:
address:
type: string
format: base58check
required:
- address |
{
"address": "A12..."
} |
Extension to webpage | account.balance.response |
type: object
properties:
finalBalance:
type: string
format: BigInt
candidateBalance:
type: string
format: BigInt
required:
- finalBalance
- candidateBalance |
{
"finalBalance": "1000",
"currentBalance": "10"
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.delete |
type: object
properties:
address:
type: string
format: base58check
required:
- address |
{
"address": "A12..."
} |
Extension to webpage | account.delete.response |
type: object
properties:
response:
type: string
enum: ["OK", "REFUSED", "ERROR"]
message:
type: string
required:
- response |
{
"response": "REFUSED"
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.import |
type: object
properties:
privateKey:
type: string
format: base58check
publicKey:
type: string
format: base58check
required:
- privateKey
- publicKey |
{
"privateKey": "S12...",
"publicKey": "P12...",
} |
Extension to webpage | account.import.response |
type: object
properties:
response:
type: string
enum: ["OK", "REFUSED", "ERROR"]
message:
type: string
required:
- response |
{
"response": "ERROR",
"message": "No connection with blockchain"
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.sign |
type: object
properties:
address:
type: string
format: base58check
data:
type: array
items:
type: integer
format: uint8
required:
- address
- data |
{
"address": "A12...",
"data": [49, 3, 255]
} |
Extension to webpage | account.sign.response |
type: object
properties:
signature:
type: array
items:
type: integer
format: uint8
publicKey:
type: string
format: base58check
required:
- signature
- publicKey |
{
"signature": [49, 3, 255],
"publicKey": "P12..."
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.sellRolls |
type: object
properties:
fee:
type: string
format: BigInt
amount:
type: string
format: BigInt
required:
- fee
- amount |
{
"fee": "12000000",
"amount": "10"
} |
Extension to webpage | account.sellRolls.response |
type: object
properties:
operationId:
type: string
required:
- operationId |
{
"operationId": "O1sBc7PanPjB8tEadNC4t4GGPFM5kqC8yTKqwzHHV9q7FksuBoE"
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.buyRolls |
type: object
properties:
fee:
type: string
format: BigInt
amount:
type: string
format: BigInt
required:
- fee
- amount |
{
"fee": "12000000",
"amount": "10"
} |
Extension to webpage | account.buyRolls.response |
type: object
properties:
operationId:
type: string
required:
- operationId |
{
"operationId": "O1sBc7PanPjB8tEadNC4t4GGPFM5kqC8yTKqwzHHV9q7FksuBoE"
} |
This method is used to call smart contract.
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.callSC |
type: object
properties:
nickname:
type: string
description: Account nickname to use to sign the operation.
functionName:
type: string
at:
type: string
args:
type: Args
description: Smart contract arguments
coins:
type: string
format: BigInt
description: Amount of coins to send to the block creator
nonPersistentExecution (optional):
type: object
properties:
isNPE:
type: boolean
default: false
maxGas:
type: string
format: BigInt
default:"0"
description: Setup a Non-persistent execution of the SC call
required:
- nickname
- functionName
- at
- args
- coins |
{
"nickname": "my-account",
"name": "register",
"at": "AU19tCSKtiE4k9MJLyLH5sWGDZ7Rr2SiBf1ti3XqeCptwsXGvkef",
"args": {
"offset": 4,
"serialized": [
4, 0, 0, 0,
116, 101, 115, 116
]
},
"coins": "2.0",
"nonPersistentExecution": {
"isNPE": true,
"maxGas": "100000000"
}
} |
Extension to webpage | account.callSC.response |
type: object
properties:
operationId:
type: string
required:
- operationId |
{
"operationId": "O1sBc7PanPjB8tEadNC4t4GGPFM5kqC8yTKqwzHHV9q7FksuBoE"
} |
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.sendTransaction |
type: object
properties:
fee:
type: string
format: BigInt
amount:
type: string
format: BigInt
recipientAddress:
type: string
required:
- fee
- amount
- recipientAddress |
{
"fee": "12000000",
"amount": "2000000000",
"recipientAddress": "AU19tCSKtiE4k9MJLyLH5sWGDZ7Rr2SiBf1ti3XqeCptwsXGvkef"
} |
Extension to webpage | account.sendTransaction.response |
type: object
properties:
operationId:
type: string
required:
- operationId |
{
"operationId": "O1sBc7PanPjB8tEadNC4t4GGPFM5kqC8yTKqwzHHV9q7FksuBoE"
} |
This method is used to get the node URLs known by the extension.
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | Provider.getNodeUrls |
none | null |
Extension to webpage | Provider.getNodeUrls.response |
type: array
items:
type: string
format: URL |
[
"http://localhost:1234",
"https://massa-nodes.net"
] |
This method will generate a new account upon request with a specified name defined by the user. The account shall be added to the wallet automatically.
Direction | Type | Format | Example |
---|---|---|---|
Webpage to extension | account.generateNewAccount |
type: object
properties:
name:
type: string |
{
"name": "my-trading-account"
} |
Extension to webpage | account.generateNewAccount.response |
type: object
properties:
name:
type: string
address:
type: string
required:
- address |
{
"name": "my-trading-account",
"address": "AU12..."
} |
- The wallet provider must validate all inputs before performing any action.
- The webpage must validate all input before processing any action.
- The extension should ensure that communication is restricted to the intended parties and prevent any third-party intervention.
- The wallet provider should be built with security as a primary concern, including measures such as encryption, authentication, and authorization to prevent unauthorized access or data breaches.
The Massalabs implementation of this standard can be found on GitHub at https://github.com/massalabs/wallet-provider.
Here's an example of how to use it:
import { providers } from "@massalabs/wallet-provider";
// Get all available Massa wallet providers.
const availableProviders = providers();
// Get a provider.
const myProvider = availableProviders[0];
// Import an account via the Massa wallet provider.
const privateKey = "Sxxxxxxxxxxxxxx";
const publicKey = "Pxxxxxxxxxxxxxxx";
await myProvider.importAccount(publicKey, privateKey);
// Get accounts.
const myAccounts = await myProvider.accounts();
// Get one account.
const myAccount = myAccounts[0];
// Get the account's address.
const accountAddress = myAccount.address();
// Get the account's balances.
const accountBalance = await myAccount.balance();
// Sign a message.
const signature = await myAccount.sign([0, 1, 2]);
// Delete an account.
await myProvider.importAccount(myAccount.address());
// Get nodes url
const urls = await myProvider.getNodeUrls();
// buy rolls
const buyOperationId = await myAccount.buyRolls("10", "12000000");
// sell rolls
const sellOperationId = await myAccount.sellRolls("3", "12000000");
// send transaction
const sendTransactionId = await myAccount.sendTransaction("2000000000", "AU19tCSKtiE4k9MJLyLH5sWGDZ7Rr2SiBf1ti3XqeCptwsXGvkef", "12000000");
// generate and add a new random account
const newAccountDetails = await myProvider.generateNewAccount("my-trading-account");