Giter Site home page Giter Site logo

magicblock-labs / solana.unity-core Goto Github PK

View Code? Open in Web Editor NEW
40.0 1.0 12.0 4.11 MB

Solana's Unity SDK and integration library.

Home Page: https://solana.unity-sdk.gg

License: MIT License

C# 99.98% PowerShell 0.01% Shell 0.01%
rpc solana solana-client unity unity3d

solana.unity-core's Introduction

Solana.Unity Core

Solana.Unity integration Framework

Build Release Coverage Status Code License Follow on Twitter Discord

Introduction

Solana.Unity is a ported version of Solnet, compatible with .net standard 2.0. It can be used with Unity.

If you are looking for the installable version via Unity Package Manager with examples, see here: https://github.com/magicblock-labs/Solana.Unity-SDK.

Several changes have been made to make it compatible with Unity, incompatible libraries have been replaced and external dependencies have been reduced. The API and documentation remain unchanged. The original tests are run on every commit.

Solnet

Solnet is Solana's .NET SDK to integrate with the .NET ecosystem. Wherever you are developing for the Web or Desktop, we are here to help. Learn more about the provided samples, documentation, integrating the SDK into your app, and more here.

Features

  • Full JSON RPC API coverage
  • Full Streaming JSON RPC API coverage
  • Wallet and accounts (sollet and solana-keygen compatible)
  • Keystore (sollet and solana-keygen compatible)
  • Transaction decoding from base64 and wire format and encoding back into wire format
  • Message decoding from base64 and wire format and encoding back into wire format
  • Instruction decompilation
  • TokenWallet object to send SPL tokens and JIT provisioning of Associated Token Accounts
  • Programs
    • Native Programs
      • System Program
      • Stake Program
    • Solana Program Library (SPL)
      • Memo Program
      • Token Program
      • Token Swap Program
      • Associated Token Account Program
      • Name Service Program
      • Shared Memory Program

For the sake of maintainability and sometimes due to the size and complexity of some other programs, this repository will only contain Solana's Native Programs and Programs which are part of the SPL, for a list of other commonly needed programs see below:

Requirements

  • net standard 2.0

Dependencies

  • Chaos.NaCl.Standard
  • Portable.BouncyCastle

Examples

The Solana.Unity.Examples project contains some code examples, essentially we're trying very hard to make it intuitive and easy to use the library. When trying to run these examples they might lead to errors in cases where they create new accounts, in these cases, the response from the RPC contains an and the transaction simulation logs which state that account address is ... already in use, all you need to do is increment the value that is used to derive that account from the seed being used, i.e wallet.GetAccount(value+1).

Wallets

The Solana.Unity.Wallet project implements wallet and key generation features, these were made compatible with both the keys generated using solana-keygen and the keys generated using the popular browser wallet sollet.io.

Initializing a wallet compatible with sollet

// To initialize a wallet and have access to the same keys generated in sollet (the default)
var sollet = new Wallet("mnemonic words ...", WordList.English);

// Retrieve accounts by derivation path index
var account = sollet.GetAccount(10);

Initializing a wallet compatible with solana-keygen

// To initialize a wallet and have access to the same keys generated in solana-keygen
var wallet = new Wallet("mnemonic words ...", WordList.English, "passphrase", SeedMode.Bip39);

// Retrieve the account
var account = wallet.Account; // the solana-keygen mechanism does not allow account retrieval by derivation path index

Generating new wallets

// Generate a new mnemonic
var newMnemonic = new Mnemonic(WordList.English, WordCount.Twelve);

var wallet = new Wallet(newMnemonic);

KeyStore

The Solana.Unity.KeyStore project implements functionality to be able to securely store keys, seeds, mnemonics, and whatever you so desire. It contains an implementation of the Web3 Secret Storage Definition as well as a SolanaKeyStoreService which can be used to read keys generated by solana-keygen.

Secret KeyStore Service

// Initialize the KeyStore
var secretKeyStoreService = new SecretKeyStoreService();

// Encrypt a private key, seed or mnemonic associated with a certain address
var jsonString = secretKeyStoreService.EncryptAndGenerateDefaultKeyStoreAsJson(password, data, address);

// Or decrypt a web3 secret storage encrypted json data
byte[] data = null;
try { 
    data = KeyStore.DecryptKeyStoreFromJson(password, jsonString);
} catch (Exception) {
    Console.WriteLine("Invalid password!");
}

Solana KeyStore Service

// Initialize the KeyStore
var secretKeyStoreService = new SolanaKeyStoreService();

// Restore a wallet from the json file generated by solana-keygen,
// with the same passphrase used when generating the keys
var wallet = secretKeyStoreService.RestoreKeystore(filePath, passphrase);

RPC and Streaming RPC

The Solana.Unity.Rpc project contains a full-fidelity implementation of the Solana JSON RPC, this implementation is compatible with both the methods expected to be removed in v1.8 and the methods which were added on v1.7 to replace them.

ClientFactory pattern

The client factory allows you to pass a Logger which implements the Microsoft.Extensions.Logging.ILogger interface.

var rpcClient = ClientFactory.GetClient(Cluster.MainNet, logger);
var streamingRpcClient = ClientFactory.GetStreamingClient(Cluster.MainNet, logger);

Using the RPC

// Get a certain account's info
var accountInfo = rpcClient.GetAccountInfo("5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj");

// Or get the token accounts owned by a certain account
var tokenAccounts = rpcClient.GetTokenAccountsByOwner("5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj");

// Or even filter the token accounts by the token's mint
var wrappedSolAccounts = rpcClient.GetTokenAccountsByOwner("5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj", "So11111111111111111111111111111111111111112");

// The following address represents Serum's address and it can be used for a number of things
var serumAddress = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";

// You can query the accounts owned by a certain program, and filter based on their account data!
var programAccounts = rpcClient.GetProgramAccounts(serumAddress);
var filters = new List<MemCmp>(){ new MemCmp{ Offset = 45, Bytes = OwnerAddress } };

var filteredProgramAccounts = rpcClient.GetProgramAccounts(serumAddress, memCmpList: filters);

Using the Streaming RPC

// After having sent a transaction
var txSig = rpcClient.SendTransaction(tx);

// You can subscribe to that transaction's signature to be finalized
var subscription = streaminRpcClient.SubscribeSignature(txSig.Result, (subscriptionState, response) => {
    // do something
}, Commitment.Finalized);

Sending a transaction

// Initialize the rpc client and a wallet
var rpcClient = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet();
// Get the source account
var fromAccount = wallet.GetAccount(0);
// Get the destination account
var toAccount = wallet.GetAccount(1);
// Get a recent block hash to include in the transaction
var blockHash = rpcClient.GetRecentBlockHash();

// Initialize a transaction builder and chain as many instructions as you want before building the message
var tx = new TransactionBuilder().
        SetRecentBlockHash(blockHash.Result.Value.Blockhash).
        SetFeePayer(fromAccount).
        AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).
        AddInstruction(SystemProgram.Transfer(fromAccount, toAccount.GetPublicKey, 100000)).
        Build(fromAccount);

var firstSig = rpcClient.SendTransaction(tx);

Create, Initialize and Mint

var wallet = new Wallet.Wallet(MnemonicWords);

var blockHash = rpcClient.GetRecentBlockHash();
var minBalanceForExemptionAcc = rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.TokenAccountDataSize).Result;

var minBalanceForExemptionMint =rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result;

var mintAccount = wallet.GetAccount(21);
var ownerAccount = wallet.GetAccount(10);
var initialAccount = wallet.GetAccount(22);

var tx = new TransactionBuilder().
    SetRecentBlockHash(blockHash.Result.Value.Blockhash).
    SetFeePayer(ownerAccount).
    AddInstruction(SystemProgram.CreateAccount(
        ownerAccount,
        mintAccount,
        minBalanceForExemptionMint,
        TokenProgram.MintAccountDataSize,
        TokenProgram.ProgramIdKey)).
    AddInstruction(TokenProgram.InitializeMint(
        mintAccount.PublicKey,
        2,
        ownerAccount.PublicKey,
        ownerAccount.PublicKey)).
    AddInstruction(SystemProgram.CreateAccount(
        ownerAccount,
        initialAccount,
        minBalanceForExemptionAcc,
        TokenProgram.TokenAccountDataSize,
        TokenProgram.ProgramIdKey)).
    AddInstruction(TokenProgram.InitializeAccount(
        initialAccount.PublicKey,
        mintAccount.PublicKey,
        ownerAccount.PublicKey)).
    AddInstruction(TokenProgram.MintTo(
        mintAccount.PublicKey,
        initialAccount.PublicKey,
        25000,
        ownerAccount)).
    AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net")).
    Build(new List<Account>{ ownerAccount, mintAccount, initialAccount });

Transfer a Token to a new Token Account

// Initialize the rpc client and a wallet
var rpcClient = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet();

var blockHash = rpcClient.GetRecentBlockHash();
var minBalanceForExemptionAcc =
    rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.TokenAccountDataSize).Result;

var mintAccount = wallet.GetAccount(21);
var ownerAccount = wallet.GetAccount(10);
var initialAccount = wallet.GetAccount(22);
var newAccount = wallet.GetAccount(23);

var tx = new TransactionBuilder().
    SetRecentBlockHash(blockHash.Result.Value.Blockhash).
    SetFeePayer(ownerAccount).
    AddInstruction(SystemProgram.CreateAccount(
        ownerAccount,
        newAccount,
        minBalanceForExemptionAcc,
        TokenProgram.TokenAccountDataSize,
        TokenProgram.ProgramIdKey)).
    AddInstruction(TokenProgram.InitializeAccount(
        newAccount.PublicKey,
        mintAccount.PublicKey,
        ownerAccount.PublicKey)).
    AddInstruction(TokenProgram.Transfer(
        initialAccount.PublicKey,
        newAccount.PublicKey,
        25000,
        ownerAccount)).
    AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net")).
    Build(new List<Account>{ ownerAccount, newAccount });

Transaction and Message decoding

// Given a message or transaction encoded as base64 or a byte array, you can decode it into their structures
var tx = Transaction.Deserialize(txData);
var msg = Message.Deserialize(msgData)

// This allows you to sign messages crafted by other components using Solnet
var signedTx = Transaction.Populate(msg, new List<byte[]> { account.Sign(msgData) });

Programs

The Solana.Unity.Programs project contains our implementation of several Native and SPL programs, for brevity, they are not exemplified in depth here, but you should check out Solana.Unity.Examples which contains numerous examples, such as how to do multi signature operations.

Hello Solana World

var memoInstruction = MemoProgram.NewMemo(wallet.Account, "Hello Solana World, using Solnet :)");

var recentHash = rpcClient.GetRecentBlockHash();

var tx = new TransactionBuilder().
    SetFeePayer(wallet.Account).
    AddInstruction(memoInstruction).
    SetRecentBlockHash(recentHash.Result.Value.Blockhash).
    Build(wallet.Account);

Creating and sending tokens to an Associated Token Account

var recentHash = rpcClient.GetRecentBlockHash();

// By taking someone's address, derive the associated token account for their address and a corresponding mint
// NOTE: You should check if that person already has an associated token account for that mint!
PublicKey associatedTokenAccountOwner = new ("65EoWs57dkMEWbK4TJkPDM76rnbumq7r3fiZJnxggj2G");
PublicKey associatedTokenAccount =
    AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(associatedTokenAccountOwner, mintAccount);

byte[] txBytes = new TransactionBuilder().
    SetRecentBlockHash(recentHash.Result.Value.Blockhash).
    SetFeePayer(ownerAccount).
    AddInstruction(AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(
        ownerAccount,
        associatedTokenAccountOwner,
        mintAccount)).
    AddInstruction(TokenProgram.Transfer(
        initialAccount,
        associatedTokenAccount,
        25000,
        ownerAccount)).// the ownerAccount was set as the mint authority
    AddInstruction(MemoProgram.NewMemo(ownerAccount, "Hello from Sol.Net")).
    Build(new List<Account> {ownerAccount});

string signature = rpcClient.SendTransaction(txBytes)

Instruction decoding

// For more advanced usage, this package also has an instruction decoder
// You can deserialize a transaction's message similar to how you would using web3.js
var msg = Message.Deserialize(msgBase64);

// And you can decode all of the instructions in that message into a friendly structure
// which holds the program name, the instruction name, and parameters relevant to the instruction itself
var decodedInstructions = InstructionDecoder.DecodeInstructions(msg);

Display token balances of a wallet

// load Solana token list and get RPC client
var tokens = TokenInfoResolver.Load();
var client = ClientFactory.GetClient(Cluster.MainNet);

// load snapshot of wallet and sub-accounts
TokenWallet tokenWallet = TokenWallet.Load(client, tokens, ownerAccount);
var balances = tokenWallet.Balances();

// show individual token accounts
var maxsym = balances.Max(x => x.Symbol.Length);
var maxname = balances.Max(x => x.TokenName.Length);
Console.WriteLine("Individual Accounts...");
foreach (var account in tokenWallet.TokenAccounts())
{
    Console.WriteLine($"{account.Symbol.PadRight(maxsym)} {account.BalanceDecimal,14} {account.TokenName.PadRight(maxname)} {account.PublicKey} {(account.IsAssociatedTokenAccount ? "[ATA]" : "")}");
}
Console.WriteLine();

Sending an SPL token

var client = ClientFactory.GetClient(Cluster.MainNet, logger);
var tokens = New TokenInfoResolver();
var wallet = TokenWallet.Load(client, tokens, feePayer);

// find source of funds
var source = wallet.TokenAccounts.ForToken(WellKnownTokens.Serum).WithAtLeast(12.75D).FirstOrDefault();

// single-line SPL send - sends 12.75 SRM to target wallet ATA 
// if required, ATA will be created funded by feePayer
var sig = wallet.Send(source, 12.75D, target, feePayer);

Support

Consider supporting us for maintaining the integration with Unity:

  • Sol Address: 59JofSV1DiU2rrhFRghvFY2j8Pmhq4cgbEp6dSTJMiHx

Consider supporting the solnet developers:

  • Sol Address: oaksGKfwkFZwCniyCF35ZVxHDPexQ3keXNTiLa7RCSp

Contribution

We encourage everyone to contribute, submit issues, PRs, discuss. Every kind of help is welcome.

Solana.Unity Maintainers

Solnet Maintainers

See also the list of contributors who participated in this project.

License

This project is licensed under the MIT License - see the LICENSE file for details

solana.unity-core's People

Contributors

aph5nt avatar gabrielepicco avatar hoakbuilds avatar liquizard avatar lucacillario avatar martinwhitman avatar mcanerizci avatar nazbrok avatar neo-vortex avatar nicoeft avatar omahs avatar skynode avatar supermarioblock avatar thesoftwarejedi avatar tiago18c avatar woody4618 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

solana.unity-core's Issues

[Bug] Solana.Unity.Rpc.SolanaRpcClient.SendTransactionAsync doesn't block until the required completion status

Describe the bug
Regarding class Solana.Unity.Rpc.SolanaRpcClient, which is an implementation of IRpcClient: Its SendTransactionAsync method takes in a parameter of type Commitment. It may be expected that, if the transaction is successfully sent, the method will block the task's thread until the transaction's status has reached the specified commitment level.

Example:
SendTransactionAsync(buffer, true, Commitment.Finalized)

In the call above, it may be reasonably expected that by the time the method returns, the transaction (if it was successfully sent and not reverted) is Finalized.

However, no blocking occurs.

To Reproduce

  • use the method Solana.Unity.Rpc.SolanaRpcClient.SendTransactionAsync to send a transaction. Specify the Commitment.Finalized value for the "commitment" parameter.
  • inspect the transaction immediately after execution completes.
  • note that the transaction is (probably) not finalized until a few seconds later.

Expected behavior
My understanding is that the expected behavior is for the SendTransactionAsync method to block its task thread until the transaction's status has reached the specified commitment level.

Desktop (please complete the following information):

  • OS: Ubuntu
  • Version 20.04

[Bug] Endless loop when logging public or private key with null value

Describe the bug
When you call toString on a public or private key which _key or _keyBytes are null there is an endless loop from the properties calling each other.

To Reproduce
PublicKey pubKey = new PublicKey("");
pubKey.Key = null;
Debug.Log(pubKey);

Expected behavior
There is no endless loop but instead returned null.

Additional context
Ill make a pr for a fix

[Bug]Unity stucks

Describe the bug
I wrote some simple code as the doc saids.

  1. create a wallet with Mnemonic
  2. try to get balance
  3. Unity sutcks and I heve to kill it

here is the code:

        Mnemonic newMnemonic = new Mnemonic(WordList.English, WordCount.Twelve);
        // generate a wallet
        Wallet wallet = new Wallet(newMnemonic);
        CurrentWallet = wallet;
        Debug.Log("words:" + wallet.Mnemonic);
        Debug.Log("pubkey:" + wallet.Account.PublicKey);
        Debug.Log("privatekey:" + wallet.Account.PrivateKey);
        // It's ok to get here ↑↑↑
        //try to get balance
        IRpcClient rpcClient = ClientFactory.GetClient(Cluster.DevNet);
        var balance = rpcClient.GetBalance(wallet.Account.PublicKey);
        //unity get stuck here ↑↑↑
        Debug.Log($"Balance: {balance.Result.Value}");

Integrate changes from latest version of Solnet & Investigate automated pull request

Problem description

  • Solnet has integrated new features that are not present in our ported latest version.
  • This situation is recurring, investigate if it is possible to open a pull request automatically every time there are new commits in the main branch of Solnet repo, so that the changes can be integrated progressively

Describe the solution you'd like

  • Integrate the changes and update the Solana.Unity Core
  • Use a Github action to check daily if there are changes in main. If there are, it opens a pull request with the files that should be updated.

Additional context
Solnet: https://github.com/bmresearch/Solnet

[Bug] PriceMath.TickIndexToPrice and PriceMath.SqrtPriceX64ToPrice Wrong price calculations

Describe the bug
Wrong price calculations

To Reproduce

Console.WriteLine(PriceMath.TickIndexToPrice(1, 9, 9));
Console.WriteLine(PriceMath.TickIndexToPrice(100, 9, 9));
10.00150003749937502188359611493754442728685383249508961171928583303106051744175749673006360257982567683817469619873
10.15112303331957826695058866288058596662162812280744838892230990557699568227283140711413108281156799322006970554316890624

Expected behavior

1.0001
1.0100496620928754

[Bug]rentEpoc > ulong.MaxValue

Describe the bug
I encountered an overflow error when converting the result from JSON using the rpc.GetTokenAccountsByOwnerAsync method, specifically when the rentEpoch value exceeds ulong.MaxValue. This issue arises when processing rentEpoch values larger than what ulong can represent.

To Reproduce
Steps to reproduce the behavior:

  1. Call the rpc.GetTokenAccountsByOwnerAsync method to retrieve information about token accounts associated with an owner.
  2. Identify a scenario where the rentEpoch value in the returned JSON data exceeds ulong.MaxValue.
  3. Observe an overflow error during the conversion of JSON to .NET objects when encountering large rentEpoch values.

Expected behavior
I expected the library to either handle rentEpoch values of any size properly or provide a clear error message indicating that the value exceeds the supported range. Ideally, measures such as using a different data type or implementing a validation mechanism should be taken to accommodate scenarios where rentEpoch could surpass ulong limits.

Additional context
Error json excerpt

{"id":1,"jsonrpc":"2.0","result":{"context":{"apiVersion":"1.17.18","slot":246405854},"value":[{"account":{"data":{"parsed":{"info":{"isNative":false,"mint":"4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj","owner":"xxxxxxxxxxxxxxxxxx","state":"initialized","tokenAmount":{"amount":"100754","decimals":6,"uiAmount":0.100754,"uiAmountString":"0.100754"}},"type":"account"},"program":"spl-token","space":165},"executable":false,"lamports":2039280,"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","rentEpoch":18446744073709552000,"space":165},"pubkey":"RVM2fCzme8o13BcyuZ2Ja1U7e6bHkf43Fze3iQNgJ1e"}....

Note: This issue report was created with the assistance of ChatGPT.

SendTransactionAsync does not add a 'commitment' parameter before sending the transaction

Note that SendTransactionAsync in SolanaRpcClient (Solana.Unity.Rpc/SolanaRpcClient.cs, line 985) doesn't add a "commitment" parameter to the outgoing request; it only adds the "preflightCommitment", which is a different parameter.

From the Solana docs: "For preflight checks and transaction processing," .... "The commitment parameter should be included as the last element in the params array"
https://docs.solana.com/developing/clients/jsonrpc-api

There is a HandleCommitment method (non-public) which conditionally adds a 'commitment' parameter with value to the Parameters collection. It isn't called in SendTransactionAsync, but it's called in similar methods.

Note that the only Commitment argument accepted by SendTransactionAsync is called "preflightCommitment", and it's used to set the preflightCommitment value. It might do to change that parameter to "commitment", and use it for both preflight commitment and commitment. Or else, add a second parameter for commitment.

I'm creating a pull request with the former (one parameter used for both preflight commitment and commitment)

Add configurable default commitment for RPC calls

Is your feature request related to a problem? Please describe.
Finalized is not always desirable as default commitment

Describe the solution you'd like
Add configurable default commitment for RPC calls

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.