solanasolana-web3jssolana-program-library

How to integrate contract (program) with web3 in Solana?


I am using @solana/web3.js library and have tested to create new account and get balance. Now I am going to integrate the contract (Raydium Liqudity PoolV4) with web3.

I have googled and can not find good material to learn. Can you help me how to integrate the contract in solana using web3? Thanks.


Solution

  • If you want to integrate with a smart contract, you have to know the source of the smart contract. The program will give you information on how to create the instructions for your transactions.

    Let's look at Raydium. They're closed source program-side, but from their client-side code.

    We can see that the dataLayout is:

    const dataLayout = struct([u8('instruction'), nu64('amountIn'), nu64('minAmountOut')])
    

    That tells us the the swap instruction takes in a u64 amountIn and u64 minAmountOut. nu64 is u64 on the program-side.

    Likely a lot like the spl-swap swap instruction

    pub struct Swap {
        /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate
        pub amount_in: u64,
        /// Minimum amount of DESTINATION token to output, prevents excessive slippage
        pub minimum_amount_out: u64,
    }
    

    We can then see that the data is encoded with instruction: 9, meaning the swap instruction is the 9th instruction on the program.

    const data = Buffer.alloc(dataLayout.span)
    dataLayout.encode(
      {
        instruction: 9,
        amountIn,
        minAmountOut
      },
      data
    )
    

    What the above does is allocates the data buffer and encodes it with your parameters so that the transaction executes the right transaction.

    So the Rust program would look something like this:

    pub enum RaydiumInstructions {
        /** 0 **/Instruction0 (/**/),
        /** 1 **/Instruction1 (/**/),
        /** 2 **/Instruction2 (/**/),
        /** 3 **/Instruction3 (/**/),
        /** 4 **/Instruction4 (/**/),
        /** 5 **/Instruction5 (/**/),
        /** 6 **/Instruction6 (/**/),
        /** 7 **/Instruction7 (/**/),
        /** 8 **/Instruction8 (/**/),
        /** 9 **/Swap (/**/)
    }
    

    We need to know the accounts required by this instruction to send with the transaction. If you check spl-swap, it lays out exactly what accounts, order, and whether they're a signer or writable. 11 accounts.

    ///   0. `[]` Token-swap
    ///   1. `[]` swap authority
    ///   2. `[]` user transfer authority
    ///   3. `[writable]` token_(A|B) SOURCE Account, amount is transferable by user transfer authority,
    ///   4. `[writable]` token_(A|B) Base Account to swap INTO.  Must be the SOURCE token.
    ///   5. `[writable]` token_(A|B) Base Account to swap FROM.  Must be the DESTINATION token.
    ///   6. `[writable]` token_(A|B) DESTINATION Account assigned to USER as the owner.
    ///   7. `[writable]` Pool token mint, to generate trading fees
    ///   8. `[writable]` Fee account, to receive trading fees
    ///   9. `[]` Token program id
    ///   10. `[optional, writable]` Host fee account to receive additional trading fees
        Swap(Swap),
    

    You can see in Raydium's front end they list all the accounts for the transaction:

    const keys = [
      // spl token
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      // amm
      { pubkey: ammId, isSigner: false, isWritable: true },
      { pubkey: ammAuthority, isSigner: false, isWritable: false },
      { pubkey: ammOpenOrders, isSigner: false, isWritable: true },
      { pubkey: ammTargetOrders, isSigner: false, isWritable: true },
      { pubkey: poolCoinTokenAccount, isSigner: false, isWritable: true },
      { pubkey: poolPcTokenAccount, isSigner: false, isWritable: true },
      // serum
      { pubkey: serumProgramId, isSigner: false, isWritable: false },
      { pubkey: serumMarket, isSigner: false, isWritable: true },
      { pubkey: serumBids, isSigner: false, isWritable: true },
      { pubkey: serumAsks, isSigner: false, isWritable: true },
      { pubkey: serumEventQueue, isSigner: false, isWritable: true },
      { pubkey: serumCoinVaultAccount, isSigner: false, isWritable: true },
      { pubkey: serumPcVaultAccount, isSigner: false, isWritable: true },
      { pubkey: serumVaultSigner, isSigner: false, isWritable: false },
      { pubkey: userSourceTokenAccount, isSigner: false, isWritable: true },
      { pubkey: userDestTokenAccount, isSigner: false, isWritable: true },
      { pubkey: userOwner, isSigner: true, isWritable: false }
    ]
    

    So the next step is to track down all these accounts to send via the transaction.

    Finally we add these to a transaction object and broadcast!

    let transaction = new Transaction().add(new TransactionInstruction({
        keys,
        programId,
        data
      })
    await sendTransaction(connection, wallet, transaction)