Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #9 from exzachlyvv/main
Browse files Browse the repository at this point in the history
Port of Javascript Solana library
  • Loading branch information
mattstauffer authored Oct 14, 2021
2 parents 0021f09 + 76383aa commit e796788
Show file tree
Hide file tree
Showing 37 changed files with 2,821 additions and 126 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Latest Version on Packagist](https://img.shields.io/packagist/v/tightenco/solana-php-sdk.svg?style=flat-square)](https://packagist.org/packages/tightenco/solana-php-sdk)
[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/tighten/solana-php-sdk/run-tests?label=tests)](https://github.com/tighten/solana-php-sdk/actions?query=workflow%3Arun-tests+branch%3Amain)

> :warning: This is an alpha release; functionality may change.
Simple PHP SDK for Solana.

Expand Down Expand Up @@ -55,6 +56,40 @@ $accountInfoBody = $accountInfoResponse->json();
$accountInfoStatusCode = $accountInfoResponse->getStatusCode();
``````

### Transactions

Here is working example of sending a transfer instruction to the Solana blockchain:

```php
$client = new SolanaRpcClient(SolanaRpcClient::DEVNET_ENDPOINT);
$connection = new Connection($client);
$fromPublicKey = KeyPair::fromSecretKey([...]);
$toPublicKey = new PublicKey('J3dxNj7nDRRqRRXuEMynDG57DkZK4jYRuv3Garmb1i99');
$instruction = SystemProgram::transfer(
$fromPublicKey->getPublicKey(),
$toPublicKey,
6
);

$transaction = new Transaction(null, null, $fromPublicKey->getPublicKey());
$transaction->add($instruction);

$txHash = $connection->sendTransaction($transaction, $fromPublicKey);
```

Note: This project is in alpha, the code to generate instructions is still being worked on `$instruction = SystemProgram::abc()`

## Roadmap

1. Borsh serialize and deserialize.
2. Improved documentation.
3. Build out more of the Connection, SystemProgram, TokenProgram, MetaplexProgram classes.
4. Improve abstractions around working with binary data.
5. Optimizations:
1. Leverage PHP more.
2. Better cache `$recentBlockhash` when sending transactions.
6. Suggestions? Open an issue or PR :D

## Testing

```bash
Expand All @@ -72,6 +107,7 @@ If you discover any security related issues, please email hello@tighten.co inste
## Credits

- [Matt Stauffer](https://github.com/mattstauffer)
- [Zach Vander Velden](https://github.com/exzachlyvv)
- [All Contributors](../../contributors)

## License
Expand Down
11 changes: 10 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@
"email": "matt@tighten.co",
"homepage": "https://tighten.co",
"role": "Developer"
},
{
"name": "Zach Vander Velden",
"email": "zachvv11@gmail.com",
"homepage": "https://zachvv.me",
"role": "Developer"
}
],
"require": {
"php": "^7.4 || ~8.0",
"ext-sodium": "*",
"guzzlehttp/guzzle": "^7.3",
"illuminate/http": "~8.0"
"illuminate/http": "~8.0",
"illuminate/support": "^8.0",
"stephenhill/base58": "^1.1"
},
"require-dev": {
"mockery/mockery": "^1.4",
Expand Down
42 changes: 42 additions & 0 deletions src/Account.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Tighten\SolanaPhpSdk;

use Tighten\SolanaPhpSdk\Util\Buffer;
use Tighten\SolanaPhpSdk\Util\HasPublicKey;
use Tighten\SolanaPhpSdk\Util\HasSecretKey;

class Account implements HasPublicKey, HasSecretKey
{
protected Keypair $keypair;

/**
* @param $secretKey
*/
public function __construct($secretKey = null)
{
if ($secretKey) {
$secretKeyString = Buffer::from($secretKey)->toString();

$this->keypair = Keypair::fromSecretKey($secretKeyString);
} else {
$this->keypair = Keypair::generate();
}
}

/**
* @return PublicKey
*/
public function getPublicKey(): PublicKey
{
return $this->keypair->getPublicKey();
}

/**
* @return Buffer
*/
public function getSecretKey(): Buffer
{
return $this->keypair->getSecretKey();
}
}
93 changes: 93 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Tighten\SolanaPhpSdk;

use Tighten\SolanaPhpSdk\Exceptions\AccountNotFoundException;
use Tighten\SolanaPhpSdk\Util\Commitment;

class Connection extends Program
{
/**
* @param string $pubKey
* @return array
*/
public function getAccountInfo(string $pubKey): array
{
$accountResponse = $this->client->call('getAccountInfo', [$pubKey, ["encoding" => "jsonParsed"]])['value'];

if (! $accountResponse) {
throw new AccountNotFoundException("API Error: Account {$pubKey} not found.");
}

return $accountResponse;
}

/**
* @param string $pubKey
* @return float
*/
public function getBalance(string $pubKey): float
{
return $this->client->call('getBalance', [$pubKey])['value'];
}

/**
* @param string $transactionSignature
* @return array
*/
public function getConfirmedTransaction(string $transactionSignature): array
{
return $this->client->call('getConfirmedTransaction', [$transactionSignature]);
}

/**
* NEW: This method is only available in solana-core v1.7 or newer. Please use getConfirmedTransaction for solana-core v1.6
*
* @param string $transactionSignature
* @return array
*/
public function getTransaction(string $transactionSignature): array
{
return $this->client->call('getTransaction', [$transactionSignature]);
}

/**
* @param Commitment|null $commitment
* @return array
* @throws Exceptions\GenericException|Exceptions\MethodNotFoundException|Exceptions\InvalidIdResponseException
*/
public function getRecentBlockhash(?Commitment $commitment = null): array
{
return $this->client->call('getRecentBlockhash', array_filter([$commitment]))['value'];
}

/**
* @param Transaction $transaction
* @param Keypair $signer
* @param array $params
* @return array|\Illuminate\Http\Client\Response
* @throws Exceptions\GenericException
* @throws Exceptions\InvalidIdResponseException
* @throws Exceptions\MethodNotFoundException
*/
public function sendTransaction(Transaction $transaction, Keypair $signer, $params = [])
{
if (! $transaction->recentBlockhash) {
$transaction->recentBlockhash = $this->getRecentBlockhash()['blockhash'];
}

$transaction->sign($signer);

$rawBinaryString = $transaction->serialize(false);

$hashString = sodium_bin2base64($rawBinaryString, SODIUM_BASE64_VARIANT_ORIGINAL);

return $this->client->call('sendTransaction', [
$hashString,
[
'encoding' => 'base64',
'preflightCommitment' => 'confirmed',
],
]);
}
}
10 changes: 10 additions & 0 deletions src/Exceptions/BaseSolanaPhpSdkException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tighten\SolanaPhpSdk\Exceptions;

use Exception;

class BaseSolanaPhpSdkException extends Exception
{

}
8 changes: 8 additions & 0 deletions src/Exceptions/InputValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Tighten\SolanaPhpSdk\Exceptions;

class InputValidationException extends BaseSolanaPhpSdkException
{

}
14 changes: 14 additions & 0 deletions src/Exceptions/TodoException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Tighten\SolanaPhpSdk\Exceptions;

use Exception;
use Throwable;

class TodoException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message . " | Help is appreciated: https://github.com/tighten/solana-php-sdk", $code, $previous);
}
}
116 changes: 116 additions & 0 deletions src/Keypair.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Tighten\SolanaPhpSdk;

use SodiumException;
use Tighten\SolanaPhpSdk\Util\Buffer;
use Tighten\SolanaPhpSdk\Util\HasPublicKey;
use Tighten\SolanaPhpSdk\Util\HasSecretKey;

/**
* An account keypair used for signing transactions.
*/
class Keypair implements HasPublicKey, HasSecretKey
{
public Buffer $publicKey;
public Buffer $secretKey;

/**
* @param array|string $publicKey
* @param array|string $secretKey
*/
public function __construct($publicKey = null, $secretKey = null)
{
if ($publicKey == null && $secretKey == null) {
$keypair = sodium_crypto_sign_keypair();

$publicKey = sodium_crypto_sign_publickey($keypair);
$secretKey = sodium_crypto_sign_secretkey($keypair);
}

$this->publicKey = Buffer::from($publicKey);
$this->secretKey = Buffer::from($secretKey);
}

/**
* @return Keypair
* @throws SodiumException
*/
public static function generate(): Keypair
{
$keypair = sodium_crypto_sign_keypair();

return static::from($keypair);
}

/**
* @param string $keypair
* @return Keypair
* @throws SodiumException
*/
public static function from(string $keypair): Keypair
{
return new static(
sodium_crypto_sign_publickey($keypair),
sodium_crypto_sign_secretkey($keypair)
);
}

/**
* Create a keypair from a raw secret key byte array.
*
* This method should only be used to recreate a keypair from a previously
* generated secret key. Generating keypairs from a random seed should be done
* with the {@link Keypair.fromSeed} method.
*
* @param $secretKey
* @return Keypair
*/
static public function fromSecretKey($secretKey): Keypair
{
$secretKey = Buffer::from($secretKey)->toString();

$publicKey = sodium_crypto_sign_publickey_from_secretkey($secretKey);

return new static(
$publicKey,
$secretKey
);
}

/**
* Generate a keypair from a 32 byte seed.
*
* @param string|array $seed
* @return Keypair
* @throws SodiumException
*/
static public function fromSeed($seed): Keypair
{
$seed = Buffer::from($seed)->toString();

$keypair = sodium_crypto_sign_seed_keypair($seed);

return static::from($keypair);
}

/**
* The public key for this keypair
*
* @return PublicKey
*/
public function getPublicKey(): PublicKey
{
return new PublicKey($this->publicKey);
}

/**
* The raw secret key for this keypair
*
* @return Buffer
*/
public function getSecretKey(): Buffer
{
return Buffer::from($this->secretKey);
}
}
Loading

0 comments on commit e796788

Please sign in to comment.