Wallet API
Building a Web3 Wallet

Building a Web3 Wallet#

Step 1: Create a Project and Configure#

Before starting, some preparation work is required:

Create a Project and API Key#

Before using the wallet API, create a project and generate an API key on the developer portal:

  1. Log in to the Developer Portal.
  2. Create a new project.
  3. Generate an API key in the project settings.

Next, some configuration work is needed to be done by the developer.

REST Request Authentication Configuration#

When sending REST requests, authentication is required. Please refer to the REST Request Authentication Guide.

Node.js Environment Setup#

Import the necessary Node.js libraries and set your environment variables Node.js Environment Setup.


Step 2: Generate Wallet Private Key and Address#

You can use our Signing SDK to create wallet mnemonics and addresses.

npm install#

First, you need to install the latest version of the Signing SDK,take EVM network as an 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 } 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 3: 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.

Generate UNIX Timestamp Message Signature#

let now = new Date();

let timestamp = now.getTime();

let timestampString = timestamp.toString();

console.log(timestampString);

let signParams = {
    privateKey: derivePrivateKey,
    data: timestampString
}

let signature = await wallet.signMessage(signParams);
console.log(signature);

Create a Wallet Account#

Using the obtained address, publicKey, signature, signMessage, and chainIndex, call the POST /api/v5/waas/account/create-account 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/waas/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/waas/account/create-watch-only-account. Click here for details.


Step 4: Compose and Send Transactions#

This step is mainly achieved in three parts:

Obtain Signature Information#

First, call the POST /api/v5/waas/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/waas/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 POST /api/v5/waas/wallet/pre-transaction/send-transaction interface to broadcast the transaction to the chain.

// Define your parameters
const postSendTransactionBody = {
    signedTx: "0xf8aa6a84ee6b280083012e389455d398326f99059ff775485246999027b319795580b844a9059cbb000000000000000000000000e0f7a45f1aa6cfff6fbbb1049f7a8c9248312c2e00000000000000000000000000000000000000000000000000000002540be4008193a0dfecbffac07db267e7438fe94f48c0d30bb17c3d86774bd445a9d70d06a974e9a0363c583a80dfc099d3a4186ce943d4bb38420dd52e23d26d30a73bfa8e291ed9",
    chainId: "1",
    accountId:"13886e05-1265-4b79-8ac3-b7ab46211001",
    fromAddr: "0x238193be9e80e68eace3588b45d8cf4a7eae0fa3",
    toAddr: "0xe0f7a45f1aa6cfff6fbbb1049f7a8c9248312c2e",
    txHash: "0x585efb06371c2827a8da7b3dc2f0d3daf4f61cd14959c928bc698c43822881c3",
    txAmount: "10000000000",
    serviceCharge: "4128241194000",
    tokenAddress: "0x55d398326f99059ff775485246999027b3197955",
    txType: "transfer",
    extJson: {
        gasPrice: "196582914",
        gasLimit: "21000",
        nonce: "578"
    }
};

// Define auxiliary function
const sendTransactionData = async () => {
    const apiRequestUrl = getRequestUrl(
        apiBaseUrl,
        'api/v5/waas/wallet/pre-transaction/send-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 5: Query Transaction Details#

After sending the transaction, use the GET /api/v5/waas/wallet/post-transaction/transaction-detail-by-ordid with the orderId to 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/waas/wallet/post-transaction/transaction-detail-by-ordid',
        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 API Reference.