Wallet API
Building a Web3 Wallet

Building a Web3 Wallet#

Please ensure that you have completed the preparation work. You can use this case to familiarize yourself with wallet management, querying data from various blockchains, and transaction construction and broadcasting.

Step 1: Generate Wallet Mnemonic and Address#

You can use the Signature SDK we provide to create a wallet mnemonic and address.

npm install#

First, you need to install the latest version of the Signature SDK. For EVM networks, for example:

npm install @okxweb3/crypto-lib
npm install @okxweb3/coin-base
npm install @okxweb3/coin-ethereum

Local Build#

  1. Download the project source code
git clone https://github.com/okx/js-wallet-sdk.git
  1. Run the build script
sh build.sh

Taking the ETH network as an example, you can use the Signing SDK to create an ETH wallet object and derive the corresponding address.

import { bip39, BigNumber } from "@okxweb3/crypto-lib";
import { EthWallet,MessageTypes } from "@okxweb3/coin-ethereum";

// eth wallet
let wallet = new EthWallet();

// get mnemonic
let mnemonic = await bip39.generateMnemonic();
console.log("generate mnemonic:", mnemonic);

// get derived key
const hdPath = await wallet.getDerivedPath({ index: 0 });
let derivePrivateKey = await wallet.getDerivedPrivateKey({ mnemonic: mnemonic, hdPath: hdPath });
console.log("generate derived private key:", derivePrivateKey, ",derived path: ", hdPath);

// get new address
let newAddress = await wallet.getNewAddress({ privateKey: derivePrivateKey });
console.log("generate new address:", newAddress.address);

// get the public key
let publicKey = newAddress.publicKey;
console.log("the corresponding public key:", publicKey);

Demo Program#

We have prepared an open-source demo program to demonstrate the features of the Signing SDK mentioned above. Get the demo program source code here.


Step 2: Create an Account#

You have already generated the mnemonic and address in the previous step. Now you can create an account (AccountId)to aggregate multiple addresses for batch querying of token balances and transaction history.

Note

Typically, a Web3 wallet involves three concepts:

- Wallet, Whoever owns the mnemonic owns the wallet.
- Account, Based on the BIP-44 standard, multiple accounts can be derived from a single set of mnemonics.
- Address, Each account has one address per chain.

Call the interface to create a wallet account.

For example, subscribing to the same address on different chains is implemented as follows:

// Define your parameters
const addresses = [
    {
        "chainIndex":"1",
        "address":"0x561815e02bac6128bbbbc551005ddfd92a5c24db",
        "publicKey":"02012db63bf0380294a6ecf87615fe869384b0510cb910a094254b6844af023ee2",
        "signature":"62acda5e471d9bf0099add50f4845256868d980821c161095651a918d3ef8a6b2286f512028172eabbe46ec2c9c2c20e5c40ff1fb23e1cdfdbed033ad924ce521b"
    },
    {
        "chainIndex":"10",
        "address":"0x561815e02bac6128bbbbc551005ddfd92a5c24db",
        "publicKey":"02012db63bf0380294a6ecf87615fe869384b0510cb910a094254b6844af023ee2",
        "signature":"62acda5e471d9bf0099add50f4845256868d980821c161095651a918d3ef8a6b2286f512028172eabbe46ec2c9c2c20e5c40ff1fb23e1cdfdbed033ad924ce521b"
    }
];

const getCreateAccountBody = {
    addresses: addresses,
    signMessage: '1717062864123', // UNIX Timestamp in millisecond
};

// Define auxiliary function
const getCreateAccountData = async () => {
    const apiRequestUrl = getRequestUrl(
      apiBaseUrl,
      '/api/v5/wallet/account/create-account'
    );
    return fetch(apiRequestUrl, {
      method: 'post',
      headers: headersParams,
      body: JSON.stringify(getCreateAccountBody),
    })
      .then((res) => res.json())
      .then((res) => {
        return res;
    });
};

const { data: createAccountData } = await getCreateAccountData();

Beside, if you want to create a watch-only account, call the POST /api/v5/wallet/account/create-watch-only-account. Click here for details.


Step 3: Construct and Send Transactions#

Get Signing Information#

First, call the POST /api/v5/wallet/pre-transaction/sign-info interface to query the data required for the transaction, such as gas price, gas limit, nonce. Take ETH for an example:

// Define your parameters
const postSignInfoBody = {
    chainIndex: '1',
    fromAddr: '0xdf54b6c6195ea4d948d03bfd818d365cf175cfc2',
    toAddr: '0x1e80c39051f078ee34763282cbb36ffd88b40c65',
    txAmount: '123000000000000',
    extJson: {
        inputData: '041bbc6fa102394773c6d8f6d634320773af4'
    }
};

// Define auxiliary function
const signInfoData = async () => {
    const apiRequestUrl = getRequestUrl(
        apiBaseUrl,
        '/api/v5/wallet/pre-transaction/sign-info'
    );
    return fetch(apiRequestUrl, {
         method: 'post',
         headers: headersParams,
         body: JSON.stringify(postSignInfoBody),
    })
    .then((res) => res.json())
    .then((res) => {
        return res;
    });
  };

const { data: signInfoData } = await signInfoData();

When the relevant information is queried, you will receive a response like this:

{
    "code": 0,
    "msg": "success",
    "data": [
        {
            "gasLimit": "21000",
            "nonce": "0",
            "gasPrice": {
                "normal": "12200500000",
                "min": "9130000000",
                "max": "16801000000",
                "supportEip1559": true,
                "erc1599Protocol": {
                    "suggestBaseFee": "8630000000",
                    "proposePriorityFee": "550000000",
                    "safePriorityFee": "500000000",
                    "fastPriorityFee": "2130000000",
                    "baseFee": "8630000000"
                }
            }
        },
    ]
}

Verify Address and Build Transaction#

Use the Signing SDK to verify the address and build the transaction.

// Verify address
let valid = await wallet.validAddress({
    address: newAddress.address
});
console.log("verify address isValid:", valid.isValid);

// Sign the transaction
let signParams = {
    privateKey: derivePrivateKey,
    data: {
        to: newAddress.address,
        value: new BigNumber(0),
        nonce: 5,
        gasPrice: new BigNumber(100 * 1000000000),
        gasLimit: new BigNumber(21000),
        chainId: 42
    }
};

let signedTx = await wallet.signTransaction(signParams);
console.log("signed tx:", signedTx);

Broadcast Transaction#

Call the interface to broadcast the transaction to the chain.

// Define your parameters
const postSendTransactionBody = {
   "signedTx": "0x02f8720182029f8411e1a3008504bbe2e94682520894d73c5e36d47eed32f0327f9d3f04b49384e90fab85e8d4a5100080c080a0ba380a9be31efabc4c8cfccbb555f8af3c21073d6eca01f180ebebb8942c6f30a0779e6e16a504c413cb9902ceb33ab7e89a533bc13b74477dfa6b6c76dc41fac2
   "accountId": "5f45a951-4a8b-4cd9-b20a-3b337b71efcc",
   "chainIndex": "1",
   "address": "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3",
 };

// Define auxiliary function
const sendTransactionData = async () => {
    const apiRequestUrl = getRequestUrl(
        apiBaseUrl,
        'api/v5/wallet/pre-transaction/broadcast-transaction'
    );
    return fetch(apiRequestUrl, {
        method: 'post',
        headers: headersParams,
        body: JSON.stringify(postSendTransactionBody),
    })
    .then((res) => res.json())
    .then((res) => {
        return res;
    });
};

const { data: sendTransactionData } = await sendTransactionData();

After broadcasting, you will receive a response like this:

{
    "code": 0,
    "msg": "success",
    "data": [{
        "orderId": "469356614923743232"
    }]
}
Note
Click to view the definition of orderId.

Step 4: Query Transaction Details#

After sending the transaction, use the interface get the transaction details.You can also query by transaction hash, see here.

// Define your parameters
const params = {
    accountId: '13886e05-1265-4b79-8ac3-b7ab46211001',
    orderId: '469356614923743232',
    chainIndex: '1'
  };

// Define auxiliary function
const getTransactionDetailData = async () => {
    const apiRequestUrl = getRequestUrl(
        apiBaseUrl,
        '/api/v5/wallet/post-transaction/orders',
        params
    );
    return fetch(apiRequestUrl, {
        method: 'get',
        headers: headersParams,
    })
    .then((res) => res.json())
    .then((res) => {
        return res;
    });
};

const { data: transactionDetailData } = await getTransactionDetailData();

When the details are successfully queried, you will receive a response like this:

{
    "code": 0,
    "msg": "success",
    "data": [
        {
            "chainIndex": "1",
            "orderId": "469356614923743232",
            "txhash": "0xcbf411766d65f3cf92839ababa73c4afec69a83442e8b67a68b5104b50a04ejb",
            "blockHash" : "0x3ee63382b883fc40e35da6023fb341dd01cd2ec011f992bb54cf312f517740c9",
            "blockHeight" : "19235519",
            "blockTime" : "1708026551000",
            "feeUsdValue" : "138.846919",
            "fees" : "49102595635945621",
            "gasLimit" : "2000000",
            "gasPrice" : "87504603347",
            "gasUsed" : "561143",
            "txType": "0",  
            "txTime": "0", 
            "txStatus": "0",
            "txDetail": [
                {
                    "iType" : "0",  
                    "txAmount": "0",
                    "tokenAddress":""
                    "contractCall" : true,
                    "fromAddr" : "0x3ee63382b883fc40e35da6023fb341dd01cd2ec011f992bb54cf312f517740c9",
                    "toAddr" : "0x3ee63382b883fc40e35da6023fb341dd01cd2ec011f992bb54cf312f517740c9",
                    "logIndex" : "-1",
                    "status" : "SUCCESS"
                }
            ]
        }
    ]
}

Additionally, you can subscribe to Webhooks to get real-time and accurate information about the status of transactions. Click here for details.


At this point, you have basically implemented the basic functions needed to develop a Web3 wallet.

Additionally, Wallet API provides a rich range of interfaces to fully meet the needs of Web3 wallet developers. For details, please refer to the following content.