Operating Multichain tokens on a unilateral token bridge (BSC <> SOLANA)
Tags: blockchain, web3, webdev, typescript
Published: 2024-03-14 04:44:11

It's almost like we hear of new blockchains every other day now and they are often architecturally incompatible. It could be overwhelming to keep up with the technological innovation.

Luckily, tools are constantly being created to match the requirements of clients so that developers can make their technical wishes come true (at least before we are all replaced by AI 🤖 👨🏾‍💻) 

In this article, I am going to show you can build a conceptual token bridge that achieves 1:1 parity between tokens on Binance Smart Chain (BSC) and Solana Blockchain. 

## Considerations:
**- Token Name and Token Symbol**
Since we will work with the assumption that this token is owned by the same project, I've the same name and symbol to represent both. The token name is "Useless Token" and symbol is "USELESS".

**- Token Standard**
USELESS (BSC) version will be BEP20
USELESS (Solana) token will be SPL-20

**- Smart contracts**
- USELESS (BSC) will be designed using [Openzeppelin's library](https://docs.openzeppelin.com/contracts/5.x/erc20) and deployed via remix
- USELESS (Solana) will be deployed to the blockchain using [Metaboss tool](https://metaboss.rs)

**- Token Interaction**
USELESS (BSC) will be interacted with using [Ethers.js](https://docs.ethers.io)

USELESS (Solana) will be interacted with using [Solana Web3 Javascript SDK](https://www.npmjs.com/package/@solana/web3.js) and [Solana Token Program Library](https://www.npmjs.com/package/@solana/spl-token)

### Steps
1. Creating USELESS (Solana) token deploying it on solana chain.

The first step to do this is to [install solana-cli](https://solana.com/developers/guides/getstarted/setup-local-development#3-install-the-solana-cli).
I used the command 
`solana-keygen grind --starts-with use:1 --ignore-case` to create a vanity address keypair with the "use" prefix because I think it's sounds cool since our token will be "USE-less". It generated the address [uSEZ...5zSo](https://solscan.io/account/uSEZwk76nxzBCbbPzD9Yc191wYdkQu5wN8GmV7p5zSo) and its keypair will be saved in the working directory, I sent some SOL to it from another wallet which will be used for transaction fees later.

I created my token metadata and stored it on gitlab as a snippet, then stored it on gitlab as a [snippet](https://gitlab.com/-/snippets/3686959/raw/main/useless.json) although it is advisable to store metadata on immutable storages like [arweave](https://arweave.org). Regarding the token metadata json structure, you can read more about the different token standards well-explained on [metaplex's website](https://developers.metaplex.com/token-metadata/token-standard)

By using metaboss, you need to create another metadata file in the directory you are working from with the fields "name", "symbol" and "uri". This uri must point to the raw token standard metadata file hosted on arweave or in this case, gitlab's snippet.
```
{
    "name": "Useless Token",
    "symbol": "USELESS",
    "uri": "https://gitlab.com/-/snippets/3686959/raw/main/useless.json"
}
```

AGAIN: You will create two metadata files: one that conforms with that of the spl-token [token standard](https://developers.metaplex.com/token-metadata/token-standard) that would be stored in an immutable storage, and another one (above) to be used while creating our token below.

Since, I have both keypair and the "metadata pointer" files in the same directory, I used this command to mint a new token with 100m supply and 9 decimals.

```
metaboss create fungible -k uSEZwk76nxzBCbbPzD9Yc191wYdkQu5wN8GmV7p5zSo.json -d 9 --initial-supply 100000000 -m token.json -r https://api.mainnet-beta.solana.com
```
The token minted successfully.

Note: If you get the error `Error: RPC response error -32002: Transaction simulation failed: Blockhash not found`, just keep trying or try other node endpoints like [getblock.io](https://account.getblock.io/sign-in?ref=MTE5MDQ2YWQtM2JmNi01N2FlLWE1ZmYtYjhjNWZiM2ExZjEw) with my affiliate link (thank you).

2. Scripting with ~~NodeJS~~ Bun
I am beginning to prefer Bun because of its native typescript support, so I used bun to initialize my project and installed necessary dependencies
```
bunx init
bun add @solana/web3.js
bun add @solana/spl-token
```

I created a file "useless.ts" and added the following code
```
// useless.ts
import { PublicKey, Connection, clusterApiUrl, Keypair, Transaction, sendAndConfirmTransaction } from '@solana/web3.js'
import { getOrCreateAssociatedTokenAccount, TOKEN_PROGRAM_ID, createTransferCheckedInstruction } from '@solana/spl-token'

const connection = new Connection(clusterApiUrl('mainnet-beta'), 'confirmed') // connection to mainnet
const secretKeyArray = await Bun.file('./uSEZwk76nxzBCbbPzD9Yc191wYdkQu5wN8GmV7p5zSo.json').json() // generated key is in the same directory as this file
const secretKey = Uint8Array.from(secretKeyArray);
const signer = Keypair.fromSecretKey(secretKey); // derived signer https://solana.com/docs/clients/javascript#quickstart


const decimals = 9 // token decimals during token creation
const uselessTokenAddressSPL = '6QWe8neoMdYUcv7LRnaBguVHTEKxC59j6Fhw8XT2pxqW' // address generated after token was minted
const mySOLWalletAddress = 'uSEZwk76nxzBCbbPzD9Yc191wYdkQu5wN8GmV7p5zSo'

// function to send solana token to given receiver and amount 
const sendSolanaToken = async (solReceiver: string, amount: number) => {
  const uselessToken = new PublicKey(uselessTokenAddressSPL)
  const mySOLWallet = new PublicKey(mySOLWalletAddress)
  const receiver = new PublicKey(solReceiver)

  // this is important because solana spl-tokens use different accounts
  // to hold tokens for different wallets, so if the receiving account
  // doesn't already have an associated account for this token, we will have to create it for them
  // so that they can receive it (Read more at https://spl.solana.com/token#example-transferring-tokens-to-another-user)
  const fromAccount = await getOrCreateAssociatedTokenAccount(connection, signer, uselessToken, mySOLWallet)
  const toAccount = await getOrCreateAssociatedTokenAccount(connection, signer, uselessToken, receiver)

  try {
    // to ensure we are attempting to add our transaction after a previously finalized block
    const latestBlock = await connection.getLatestBlockhash('finalized');
    const tx = new Transaction().add(
      createTransferCheckedInstruction(
        fromAccount.address, uselessToken, toAccount.address, mySOLWallet, amount, decimals, undefined, TOKEN_PROGRAM_ID
      )
    )
    tx.recentBlockhash = latestBlock.blockhash
    tx.lastValidBlockHeight = latestBlock.lastValidBlockHeight

    // broadcast the transaction
    const txHash = await sendAndConfirmTransaction(connection, tx, [signer])
    // log transaction hash
    console.log(txHash)
    return txHash;
  } catch (e) {
    // log error
    console.log('error occurred here', e)
  }
}
```

My script can now send "USELESS" token to any valid solana address but I wanted it to send whenever I receive a similar amount on BSC. And to achieve this, it will lead us to...

3. Create USELESS (BSC) token and deploy it on Binance Smart Chain network.

![Useless Token on BSC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1x2g2338q6un9lcne8na.png)

As you see in the image above, I have imported the ERC20 libraries I wanted. (ERC20Permit is optional but recommended) then deployed it on the network at [0x84109ff145Df1F2f3df12AD57BC104d96E034f58](https://bscscan.com/token/0x84109ff145df1f2f3df12ad57bc104d96e034f58).

But this is not enough as I need the users to submit their solana address whenever they are making a deposit so that my bridge script can pick it up automatically and send then the equivalent spl-token. I deployed the contract for this successfully at [UselessPurseBSC](https://bscscan.com/address/0x3B156913F104287eb1cA166C3cf5F8F0f59e561E#code)

![UselessPurseBSC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9qd44x4gkewez6pcsfr.png)

And then I modified my "useless.ts" script accordingly by including the new lines
```
// useless.ts
...
import { Contract, JsonRpcProvider } from 'ethers'
const uselessPurseABI = [	{
  "anonymous": false,
  "inputs": [
    {
      "indexed": false,
      "internalType": "address",
      "name": "bscSender",
      "type": "address"
    },
    {
      "indexed": false,
      "internalType": "uint256",
      "name": "amount",
      "type": "uint256"
    },
    {
      "indexed": false,
      "internalType": "string",
      "name": "solReceiver",
      "type": "string"
    }
  ],
  "name": "Deposit",
  "type": "event"
}]
const uselessTokenPurseBSC = '0x3B156913F104287eb1cA166C3cf5F8F0f59e561E'
const bscConnection = new JsonRpcProvider('https://go.getblock.io/<api-key>')
const uselessPurse = new Contract(uselessTokenPurseBSC, uselessPurseABI, bscConnection)
...

// this will watch for the deposit event on our UselessPurse contract 
// this is the entry point to the bridge
uselessPurse.on('Deposit', async(_: string, amount: string, receiver: string) =>  {
  // a good idea would be to queue responses first or store them in a database and then fulfill them automatically at intervals later on
  // because of many potential problems including as rate-limiting on the node, invalid solana address,
// exhausted funds for solana token transfer, 
// any other unforeseen technical error

  // fulfill the token transfer
  await sendSolanaToken(receiver, Number(amount))
})
```
With the above done, I could now run my bridge script using 
```
bun --watch useless.ts
```

Using a client side dApp or bscscan explorer, users can deposit tokens into the UselessPurse contract and they will get the equivalent in their provided solana wallet.


Conclusion:
The use of crypto bridge is important because of the consistent evolution of blockchain technology and the need for projects and startups to make their utility tokens interoperable with multiple chains. In this article, we built a simple unilateral bridge that accepts tokens deposits on BSC network and automatically transfer equivalent amount on solana network. 

**IMPORTANT** None of the contracts in this tutorial has been audited and therefore cannot be guaranteed to be safe to use but serve well as good reference for educational material.  

Further reading:
[A crash course in crypto currency bridges](https://www.cryptovantage.com/guides/a-guide-to-cryptocurrency-bridges-and-how-they-work)
[Wormhole Blog](https://wormhole.com/blog/)

References:
[Solana Clients](https://solana.com/docs/clients/javascript)
[SPL Token](https://spl.solana.com/)
[Metaboss](https://metaboss.rs/overview.html)

Node (Affiliate Link)
[Getblock](https://account.getblock.io/sign-in?ref=MTE5MDQ2YWQtM2JmNi01N2FlLWE1ZmYtYjhjNWZiM2ExZ)

Repo:
[cross chain token repo](https://github.com/rexdavinci/useless-cross-chain-token)