Wallet dApp API Standards

Account returned by the wallet is only the { address, publicKey }. The private key should never be accessible to the dapp.

1 Like

It’s not the same Account object from the TS SDK. Only address & publicKey.

2 Likes

Another thing that could be nice is to have common types for certain things, e.g. address, publicKey, etc, similar to newtypes in Rust. It might be a tough sell to get all wallet creators to use these types though, but perhaps easier if we’re just re-exposing types from within the SDK.

1 Like

Alright, it’s much clear now. So what we actually sending with Account is publicKey and account address so dApp can prepare transaction payload for submission back to wallet. That is not what I initially though as @magnum6 has mentioned before it use the same naming as Aptos Account in TS SDK which is keypair + authKey. Perhaps, continue following SDK naming as suggested by @dport is a great idea to keep standard structure. In this case, it could be worth to reserve all function names used in SDK and rename Account in this current context to something like UserIdentity (just an example) to avoid any confusion in future.

1 Like

That’s a good idea. I’ll call it PublicAccount

1 Like

Should we have?

  • submitTransaction(signedTxnRequest:SubmitTransactionRequest) => Promise<Types.PendingTransaction> (for result of signTransaction)
  • simulateTransaction(txnRequest: Types.UserTransactionRequest): Promise<Types.OnChainTransaction> (for client can estimate gas before submit).

And

Should we have?

  • getNodeURL() => Promise<string>;
  • getBalance() => Promise<string>;

Also, we can have:

sdk: { ... /* with get functions of SDK */ };

Look like:

sdk: {
   getAccountTransactions(
      accountAddress: MaybeHexString,
      query?: { start?: number; limit?: number },
   ): Promise<Types.OnChainTransaction[]> 
   // ...
}

About Transaction API, We should support functions of BCS too? and generateTransaction functions.

  • `submitTransaction and simulateTransaction

My opinion here is that those functions are directly accessible via the sdk, and a dapp should probably be running it’s own instance of the sdk rather than us having a wrapper for everything.

getNodeURL() => Promise; and getBalance() => Promise;

For node url, yes we should have something like this, good idea. Though I think it should be more like getNetwork, because a dapp more cares about if the wallet is on mainnet or not.

For getBalance, it should also be handled by the sdk.

Interested in hearing other arguments here though.

1 Like

I know you want the user to use the SDK, but the wallet cannot give the AptosAccount directly to the DApp (for security reasons) so the functions that use the AptosAccount must be forwarded

1 Like

For current, our standard (next version of wallet 0.3.3 and @fewcha/web3 0.1.0), users can get the balance from SDK to look like that

// get balance
const data = await web3.action.sdk.getAccountResources(address);
const accountResource = data.find((r) => r.type === "0x1::Coin::CoinStore<0x1::TestCoin::TestCoin>");
const balance = accountResource.data.coin.value;
console.log(balance);

getBalance is a behavior that happens frequently, so we should have a function to call separately as an alias,like I see in TokenClient, Aptos Team did for createToken, createCollection

// get balance
const balance = await web3.action.getBalance();
console.log(balance);

We defined a standard:

// Copyright 2022 Fewcha. All rights reserved.

import { MaybeHexString, TxnBuilderTypes } from "aptos";
import { Account, AccountResource, Event, HexEncodedBytes, LedgerInfo, LedgerVersion, MoveModule, MoveStructTagId, OnChainTransaction, PendingTransaction, SubmitTransactionRequest, TableItemRequest, Token, TokenData, TokenId, Transaction, TransactionPayload, UserTransactionRequest } from "aptos/dist/api/data-contracts";
import { RequestParams } from "aptos/dist/api/http-client";

export type PublicAccount = {
  address: string;
  publicKey: string;
};

export interface Web3ProviderType {
  connect(): Promise<PublicAccount>;
  disconnect(): Promise<void>;
  isConnected(): Promise<boolean>;

  generateTransaction(payload: TransactionPayload, options?: Partial<UserTransactionRequest>): Promise<UserTransactionRequest>;

  signAndSubmitTransaction(txnRequest: UserTransactionRequest): Promise<PendingTransaction>;
  signTransaction(txnRequest: UserTransactionRequest): Promise<SubmitTransactionRequest>;
  signMessage(message: string): Promise<string>;
  submitTransaction(signedTxnRequest: SubmitTransactionRequest): Promise<PendingTransaction>;

  simulateTransaction(txnRequest: UserTransactionRequest): Promise<OnChainTransaction>;

  generateBCSTransaction(rawTxn: TxnBuilderTypes.RawTransaction): Promise<Uint8Array>;
  generateBCSSimulation(rawTxn: TxnBuilderTypes.RawTransaction): Promise<Uint8Array>;

  submitSignedBCSTransaction(signedTxn: Uint8Array): Promise<PendingTransaction>;
  submitBCSSimulation(bcsBody: Uint8Array): Promise<OnChainTransaction>;

  account(): Promise<PublicAccount>;
  getNetwork(): Promise<string>;
  getBalance(): Promise<string>;

  sdk: Web3SDK;
  token: Web3Token;
}

export type Web3SDK = {
  getAccount(accountAddress: MaybeHexString): Promise<Account>;
  getAccountTransactions(accountAddress: MaybeHexString, query?: { start?: number; limit?: number }): Promise<OnChainTransaction[]>;
  getAccountModules(accountAddress: MaybeHexString, query?: { version?: LedgerVersion }): Promise<MoveModule[]>;
  getAccountModule(accountAddress: MaybeHexString, moduleName: string, query?: { version?: LedgerVersion }): Promise<MoveModule>;
  getAccountResources(accountAddress: MaybeHexString, query?: { version?: LedgerVersion }): Promise<AccountResource[]>;
  getAccountResource(accountAddress: MaybeHexString, resourceType: string, query?: { version?: LedgerVersion }): Promise<AccountResource>;

  getEventsByEventKey(eventKey: HexEncodedBytes): Promise<Event[]>;
  getEventsByEventHandle(address: MaybeHexString, eventHandleStruct: MoveStructTagId, fieldName: string, query?: { start?: number; limit?: number }): Promise<Event[]>;

  getTransactions(query?: { start?: number; limit?: number }): Promise<OnChainTransaction[]>;
  getTransaction(txnHashOrVersion: string): Promise<Transaction>;

  transactionPending(txnHash: HexEncodedBytes): Promise<boolean>;
  waitForTransaction(txnHash: HexEncodedBytes): Promise<void>;
  getLedgerInfo(params: RequestParams): Promise<LedgerInfo>;
  getChainId(params: RequestParams): Promise<number>;
  getTableItem(handle: string, data: TableItemRequest, params?: RequestParams): Promise<any>;
};

export type Web3Token = {
  createCollection(name: string, description: string, uri: string): Promise<string>;
  createToken(collectionName: string, name: string, description: string, supply: number, uri: string, royalty_points_per_million: number): Promise<string>;
  offerToken(receiver: MaybeHexString, creator: MaybeHexString, collectionName: string, name: string, amount: number): Promise<HexEncodedBytes>;
  claimToken(sender: MaybeHexString, creator: MaybeHexString, collectionName: string, name: string): Promise<HexEncodedBytes>;
  cancelTokenOffer(receiver: MaybeHexString, creator: MaybeHexString, collectionName: string, name: string): Promise<HexEncodedBytes>;

  getCollectionData(creator: MaybeHexString, collectionName: string): Promise<any>;
  getTokenData(creator: MaybeHexString, collectionName: string, tokenName: string): Promise<TokenData>;
  getTokenBalance(creator: MaybeHexString, collectionName: string, tokenName: string): Promise<Token>;
  getTokenBalanceForAccount(account: MaybeHexString, tokenId: TokenId): Promise<Token>;
};

And the Fewcha Wallet implemented that standard at version 0.3.4

The sdk functions all take in an account address (not the full AptosAccount), thats why the wallet is returning the account address in the dapp api’s I listed.

If it happens frequently enough, then the sdk should have a function dedicated to it. Currently it’s pretty simple to get balance from the sdk - aptos-core/index.ts at main · aptos-labs/aptos-core · GitHub.

it’s precisely what I wrote above at Wallet dApp API Standards - #23 by pierreneter

// get balance
const data = await web3.action.sdk.getAccountResources(address);
const accountResource = data.find((r) => r.type === "0x1::Coin::CoinStore<0x1::TestCoin::TestCoin>");
const balance = accountResource.data.coin.value;
console.log(balance);

that means we should edit some functions that currently require full AptosAccount at param to PublicAccount, right?

What functions currently require the full AptosAccount?

1 Like

sdk functions all take full AptosAccount at type bro, you can easy to check in the Aptos code base

Example:

 async simulateTransaction(
    accountFrom: AptosAccount,
    txnRequest: Types.UserTransactionRequest,
  ): Promise<Types.OnChainTransaction> {

That is why I list it here:

Sorry, maybe I wasn’t super clear. The functions that don’t need a private key should not take in the full AptosAccount (most sdk functions follow this idea). In the case of simulateTransaction I think you are right, we can get by without the private key and should change it.

If you see any others feel free to file an issue.

1 Like

Looks good for one private key wallet! For multisig wallet, there are some functions that need to be updated.

A. function signMessage

In general, the return type string means the signature in Ehtereum and Solana, e.g. “0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a4370854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c”. But in Aptos blockchain, there is a K-of-N multisig wallet. Should we change the return type to Promise<string[]>? Because it should require at least K signatures to verify. It’s more like Flow blockchain. Ref: Signing and Verifying Arbitrary Data - Flow Documentation

B. type PublicAccount
For K-of-N multisig wallet, there is more than one public key. So it can be changed to:

  • address: string
  • publicKeys: string[]
  • threshold: number

After A & B change, we have enough information to verify signatures on the backend. The steps are the following:

  1. Get signatures by calling signMessage. There are one or more signatures.
  2. Get all public keys by calling account(). There are one or more public keys.
  3. Send signatures and public keys to the backend.
  4. Get Authentication Key from api and compare it with the key derived from all public keys.
  5. Check at least K signatures are valid.
4 Likes

Perfect, thanks @kent for starting this thread.

Actually, another question is concerning dApps that would like to interact with wallets. Shall there be also a guide for dApps to implement standard request sending message so it’s all same among all applications in development?

If we can combine dApps standard requests and standard wallet responds under one umbrella assume Wallet Standard is complete and could be posted as a guideline @ https://aptos.dev/

2 Likes