npm install @neonevm/call-solana
Once you have installed the package, you can use the Solidity libraries by importing them in your contracts:
pragma solidity 0.8.28;
import { LibSPLTokenData } from "@neonevm/call-solana/composability/libraries/spl-token-program/LibSPLTokenData.sol";
contract CallSPLTokenProgram {
/// @param tokenAccount The 32 bytes SPL token account public key
/// @return token account balance as uint64
function getSPLTokenAccountBalance(bytes32 tokenAccount) external view returns(uint64) {
return LibSPLTokenData.getSPLTokenAccountBalance(tokenAccount);
}
}
NeonEVM is a Solana network extension enabling EVM dApps to tap into Solana's user base and liquidity. It comes with a set of precompiled smart contracts acting as an interface between EVM dApps on NeonEVM and Solana's accounts and programs.
The composability feature allows EVM dApps deployed on NeonEVM to interact with Solana programs, which involves formatting Solana instructions in ways that are specific to each program.
Here we provide a set of Solidity libraries which make it possible to easily implement secure interactions with the following Solana programs:
-
System program:
LibSystemProgram
,LibSystemData
andLibSystemErrors
libraries -
SPL Token program:
LibSPLTokenProgram
,LibSPLTokenData
andLibSPLTokenErrors
libraries -
Associated Token program: :
LibAssociatedTokenProgram
andLibAssociatedTokenData
libraries -
Metaplex program:
LibMetaplexProgram
,LibMetaplexData
andLibMetaplexErrors
libraries -
Raydium program:
LibRaydiumProgram
,LibRaydiumData
andLibRaydiumErrors
libraries
We also provide a set of example smart-contracts implementing typical use cases for these libraries and best practices when it comes to user authentication and Solana accounts management.
[!CAUTION] The following contracts have not been audited yet and are here for educational purposes.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import { CallSolanaHelperLib } from "@neonevm/call-solana/utils/CallSolanaHelperLib.sol";
import { Constants } from "@neonevm/call-solana/composability/libraries/Constants.sol";
import { LibSystemData } from "@neonevm/call-solana/composability/libraries/system-program/LibSystemData.sol";
import { LibSPLTokenData } from "@neonevm/call-solana/composability/libraries/spl-token-program/LibSPLTokenData.sol";
import { LibSPLTokenProgram } from "@neonevm/call-solana/composability/libraries/spl-token-program/LibSPLTokenProgram.sol";
import { ICallSolana } from '@neonevm/call-solana/precompiles/ICallSolana.sol';
contract CallSPLTokenProgram {
ICallSolana public constant CALL_SOLANA = ICallSolana(0xFF00000000000000000000000000000000000006);
function createInitializeTokenMint(bytes memory seed, uint8 decimals) external {
// Create SPL token mint account: msg.sender and a seed are used to calculate the salt used to derive the token
// mint account, allowing for future authentication when interacting with this token mint. Note that it is
// entirely possible to calculate the salt in a different manner and to use a different approach for
// authentication
bytes32 tokenMint = CALL_SOLANA.createResource(
sha256(abi.encodePacked(
msg.sender, // msg.sender is included here for future authentication
seed // using different seeds allows msg.sender to create different token mint accounts
)), // salt
LibSPLTokenData.SPL_TOKEN_MINT_SIZE, // space
LibSystemData.getRentExemptionBalance(
LibSPLTokenData.SPL_TOKEN_MINT_SIZE,
LibSystemData.getSystemAccountData(
Constants.getSysvarRentPubkey(),
LibSystemData.getSpace(Constants.getSysvarRentPubkey())
)
), // lamports
Constants.getTokenProgramId() // Owner must be SPL Token program
);
// This contract is mint/freeze authority
bytes32 authority = CALL_SOLANA.getNeonAddress(address(this));
// Format initializeMint2 instruction
( bytes32[] memory accounts,
bool[] memory isSigner,
bool[] memory isWritable,
bytes memory data
) = LibSPLTokenProgram.formatInitializeMint2Instruction(
decimals,
tokenMint,
authority,
authority
);
// Prepare initializeMint2 instruction
bytes memory initializeMint2Ix = CallSolanaHelperLib.prepareSolanaInstruction(
Constants.getTokenProgramId(),
accounts,
isSigner,
isWritable,
data
);
// Execute initializeMint2 instruction
CALL_SOLANA.execute(0, initializeMint2Ix);
}
function mint(
bytes memory seed,
bytes32 recipientATA,
uint64 amount
) external {
// Authentication: we derive the token mint account from msg.sender and seed
bytes32 tokenMint = getTokenMintAccount(msg.sender, seed);
// Format mintTo instruction
( bytes32[] memory accounts,
bool[] memory isSigner,
bool[] memory isWritable,
bytes memory data
) = LibSPLTokenProgram.formatMintToInstruction(
tokenMint,
recipientATA,
amount
);
// Prepare mintTo instruction
bytes memory mintToIx = CallSolanaHelperLib.prepareSolanaInstruction(
Constants.getTokenProgramId(),
accounts,
isSigner,
isWritable,
data
);
// Execute mintTo instruction
CALL_SOLANA.execute(0, mintToIx);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import { Constants } from "@neonevm/call-solana/composability/libraries/Constants.sol";
import { CallSolanaHelperLib } from "@neonevm/call-solana/utils/CallSolanaHelperLib.sol";
import { LibAssociatedTokenData } from "@neonevm/call-solana/composability/libraries/associated-token-program/LibAssociatedTokenData.sol";
import { LibRaydiumProgram } from "@neonevm/call-solana/composability/libraries/raydium-cpmm-program/LibRaydiumCPMMProgram.sol";
import { ICallSolana } from "@neonevm/call-solana/precompiles/ICallSolana.sol";
interface IERC20ForSpl {
function transferSolana(bytes32 to, uint64 amount) external returns(bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external;
function tokenMint() external view returns(bytes32);
}
contract CallRaydiumProgram {
ICallSolana public constant CALL_SOLANA = ICallSolana(0xFF00000000000000000000000000000000000006);
error InvalidTokens();
function createPool(
address tokenA,
address tokenB,
uint64 mintAAmount,
uint64 mintBAmount,
uint64 startTime
) public returns(bytes32) {
bytes32 tokenAMint = IERC20ForSpl(tokenA).tokenMint();
bytes32 tokenBMint = IERC20ForSpl(tokenB).tokenMint();
bytes32 payerAccount = CALL_SOLANA.getPayer();
bytes32 tokenA_ATA = LibAssociatedTokenData.getAssociatedTokenAccount(tokenAMint, payerAccount);
bytes32 tokenB_ATA = LibAssociatedTokenData.getAssociatedTokenAccount(tokenBMint, payerAccount);
IERC20ForSpl(tokenA).transferFrom(msg.sender, address(this), mintAAmount);
IERC20ForSpl(tokenA).transferSolana(
tokenA_ATA,
mintAAmount
);
IERC20ForSpl(tokenB).transferFrom(msg.sender, address(this), mintBAmount);
IERC20ForSpl(tokenB).transferSolana(
tokenB_ATA,
mintBAmount
);
bytes32[] memory premadeAccounts = new bytes32[](20);
premadeAccounts[0] = payerAccount;
premadeAccounts[7] = tokenA_ATA;
premadeAccounts[8] = tokenB_ATA;
(
uint64 lamports,
bytes32[] memory accounts,
bool[] memory isSigner,
bool[] memory isWritable,
bytes memory data
) = LibRaydiumProgram.createPoolInstruction(tokenAMint, tokenBMint, mintAAmount, mintBAmount, startTime, 0, true, premadeAccounts);
CALL_SOLANA.execute(
lamports,
CallSolanaHelperLib.prepareSolanaInstruction(
Constants.getCreateCPMMPoolProgramId(),
accounts,
isSigner,
isWritable,
data
)
);
return accounts[3]; // poolId
}
}
-
-
CallSystemProgram example contract demonstrating how the System program Solidity libraries can be used in practice to interact with Solana's System program.
-
-
CallSPLTokenProgram example contract demonstrating how the SPL Token program Solidity libraries can be used in practice to interact with Solana's SPL Token program.
-
-
CallMetaplexProgram example contract demonstrating how the Metaplex program Solidity libraries can be used in practice to interact with Solana's Metaplex program.
-
-
CallAssociatedTokenProgram example contract demonstrating how the Associated Token program Solidity libraries can be used in practice to interact with Solana's Associated Token program.
-
-
CallRaydiumProgram example contract demonstrating how the Raydium program Solidity libraries can be used in practice to interact with Solana's Raydium program.
- Constants.sol provides commonly used constants for formatting instructions to be executed by Solana programs
- CallSolanaHelperLib.sol provides helper functions to prepare formatted instructions right before they are executed on Solana
- SolanaDataConverterLib.sol provides helper functions for casting data to and from various types commonly used on Solana
-
ICallSolana.sol provides an interfacte to the
CallSolana
precompiled contract which is the cornerstone of NeonEVM's composability with Solana. See: ICallSolana interface documentation . - QueryAccount.sol provides a set of getter function for reading Solana's state by querying data stored on Solana accounts
See: Common Solana terminology
Arbitrary token accounts are derived using a seed
which includes the token account owner
's public key and an
arbitrary nonce
(among other parameters). By using different nonce
values it is possible to derive different
arbitrary token accounts for the same owner
which can be useful for some use cases.
The CallSPLTokenProgram contract provides its users with methods to create and initialize SPL token mints and arbitrary token accounts as well as to mint and transfer tokens using those accounts. It features a built-in authentication logic ensuring that users remain in control of created accounts.
However, there exists a canonical way of deriving a SPL token account for a specific owner
and this token account is
called an Associated Token account. Associated Token accounts are used widely by application s running on Solana
and it is generally expected that token transfers are made to and from Associated Token accounts.
The CallAssociatedTokenProgram contract provides a method to create and initialize canonical Associated Token accounts for third party Solana users. This method can also be used to create and initialize canonical Associated Token accounts owned by this contract.
The CallSPLTokenProgram.createInitializeTokenMint
function takes a seed
parameter as input which is used along with
msg.sender
to derive the created token mint account. While the CallSPLTokenProgram contract is given mint/freeze
authority on the created token mint account, the mintTokens
function grants msg.sender
permission to mint tokens
by providing the seed
that was used to create the token mint account.
The CallMetaplexProgram.createTokenMetadataAccount
function takes a seed
parameter as input which is used along with
msg.sender
to derive a token mint account. Created token metadata account is associated with this token mint account
which must have been created and initialized beforehand by the same msg.sender
. That same msg.sender
is also granted
permission to update the token metadata account in the future, provided that it is set as mutable upon creation.
Using arbitrary SPL Token accounts created via the CallSPLTokenProgram
contract deployed on NeonEVM allows for
cheap and easy authentication of NeonEVM users to let them interact with and effectively control those token accounts
securely via this contract while this contract is the actual owner of those token accounts on Solana. It is also
possible to create and initialize an arbitrary SPL Token accounts for third party Solana users, granting them full
ownership of created accounts on Solana.
The CallSPLTokenProgram.createInitializeArbitraryTokenAccount
function can be used for three different purposes:
-
To create and initialize an arbitrary token account to be used by
msg.sender
to send tokens through the CallSPLTokenProgram contract. In this case, both theowner
andtokenOwner
parameters passed to the function should be left empty. The arbitrary token account to be created is derived frommsg.sender
and anonce
(that can be incremented to create different arbitrary token accounts). Onlymsg.sender
is allowed to perform state changes to the created token account via this contract. ThetransferTokens
function grantsmsg.sender
permission to transfer tokens from this arbitrary token account by providing thenonce
that was used to create the arbitrary token account. -
To create and initialize an arbitrary token account to be used by a third party
user
NeonEVM account through the CallSPLTokenProgram contract. In this case, theowner
parameter passed to the function should be
CallSPLTokenProgram.getNeonAddress(user)
and thetokenOwner
parameter should be left empty. The arbitrary token account to be created is derived from theuser
account and anonce
(that can be incremented to create different arbitrary token accounts). Only thatuser
is allowed to perform state changes to the created token account via this contract. ThetransferTokens
function grantsuser
permission to transfer tokens from this arbitrary token account by providing thenonce
that was used to create the arbitrary token account. -
To create and initialize an arbitrary token account to be used by a third party
solanaUser
Solana account to send tokens directly on Solana without interacting with the CallSPLTokenProgram contract. In this case, both theowner
and thetokenOwner
parameters passed to the function should besolanaUser
. The arbitrary token account to be created is derived from thesolanaUser
account and anonce
(that can be incremented to create different arbitrary token accounts). The owner of the arbitrary token account is thesolanaUser
account. ThesolanaUser
account cannot transfer tokens from this arbitrary token account by interacting with the CallSPLTokenProgram contract, instead it must interact directly with the SPL Token program on Solana by signing and executing atransfer
instruction.
This software is licensed under the MIT license