Skip to content

Commit

Permalink
Merge pull request #3 from kadena-php/feature/refactor-and-add-capabi…
Browse files Browse the repository at this point in the history
…lity-support

Refactor and add capability support
  • Loading branch information
HergenD authored Jan 9, 2023
2 parents ac3544a + 06df730 commit ba0b603
Show file tree
Hide file tree
Showing 76 changed files with 1,825 additions and 1,658 deletions.
111 changes: 63 additions & 48 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ Using this package allows you to call things like admin functions in Pact from y

> If your users have to sign a command, something like Kadena.js would still be required on the frontend. This is not a complete replacement.
> ⚠️ This package is under active development and has only been released under versions 0.x.x to allow for testing of the Package. During development, breaking changes might be made in minor version upgrades.
>
> Some features like adding capabilities are still missing.
> ⚠️ This package is under active development and has no stable production release yet
## Installation

Expand All @@ -26,22 +24,11 @@ composer require kadena-php/client
Key Pairs are used to sign your Pact commands. You can generate a new KeyPair using

```php
$keyPair = \Kadena\Crypto\KeyPair::generate();
$keyPair = KeyFactory::generate();
```
### Commands
Commands are requests sent to the Pact API.
They contain a payload, metadata, signers, network id, and a nonce.
When creating a command, only the metadata and payload are required.
The `networkId` is `null` by default, and when no nonce is set, it will default to the current time.

Signers must be present in a signed command, but using the `getSignedCommand` will take care of that for you.

First, let's create metadata to construct our command:

```php
$metadata = Meta::create();
```
The `create()` method takes an optional array of options, options with their default values are:
### Metadata
Every command sent to the Kadena API requires a metadata object to be present. This object can be created manually, or be constructed using the `MetadataFactory`.
The factory will set predefined defaults if certain options are no provided. The defaults and options are as follows:
```php
creationTime: Carbon::now(),
ttl: 7200,
Expand All @@ -50,62 +37,90 @@ chainId: '0',
gasPrice: 1e-8,
sender: ''
```
Then create a payload:
If we want to create an object with the default options but on a different chain, we can do it like this:
```php
$executePayload = new Payload(
payloadType: PayloadType::EXECUTE,
executePayload: new ExecutePayload(
code: '(+ 1 2)'
)
);
$factory = new MetadataFactory();
$metadata = $factory->withOptions([
'chainId' => '1',
])->make();
```
If no custom options are required, you can just call `$factory->make()` to create your `Metadata` object.
### Signers
Commands have to be signed before sending them to the Kadena API. To support this, a `Signer` (one or many) has to be created. A signer consists of a public key and optionally a list of capabilities.

$continuePayload = new Payload(
payloadType: PayloadType::CONTINUE,
executePayload: new ContinuePayload(
pactId: 'pact-id',
rollback: false,
step: 0
)
Let's create a signer with a public key of `example-key` and the `coin.transfer` capability. As a signer can have multiple or no capabilities, all `Capability` objects should be wrapped in a `CapabilityCollection` object:
```php
// Just as an example, keys should be created using the KeyFactory
$publicKey = new PublicKey(new SignaturePublicKey(new HiddenString('example-key')));

$transferCapability = new Capability(
name: 'coin.transfer',
arguments: [
'address-from',
'address-to',
5
]
);

$signer = new Signer(
publicKey: $publicKey,
capabilities: new CapabilityCollection($transferCapability)
)
```
As you can see there are two payload types, an execute and a continue payload. With these metadata and payload classes, we can construct our commands:
Multiple signers can be wrapped in the `SignerCollection` object.

### Payloads
Payloads are the code to be executed by pact. There are two types of payloads: and execute and a continue payload.
```php
$executeCommand = new Command(
meta: $metadata,
payload: $executePyaload
$executePayload new ExecutePayload(
code: '(+ 1 2)'
);

$continueCommand = new Command(
meta: $metadata,
payload: $continuePyaload
$continuePayload = new ContinuePayload(
pactId: 'pact-id',
rollback: false,
step: 0
);
```

### Signing Commands
After creating a command, you can sign it using any number of key pairs. To do this, first, create a `KeyPairCollection` from the key pairs you have. This can be a single key pair or many.
### Commands
Commands wrap all data sent to the Kadena API, a `Command` object can be created manually, but it is recommended to use the `CommandFactory` for this.
The factory will set certain defaults, and can be used like this:
```php
$kp1 = KeyPair::generate();
$kp2 = KeyPair::generate();
$factory = new CommandFactory();

$keyPairCollection = new KeyPairCollection($kp1, $kp2);
$factory->withExecutePayload($executePayload)
->withMetadata($metadata)
->withSigners(new SignerCollection($signer))
->withNetworkId('mainnet0')
->withNonce('nonce-string')
->make();
```
The `withExecutePayload` or the `withContinuePayload` options are always required to create a `Command` object, but all others are optional.

### Signing Commands
After creating a command, you can sign it using any number of key pairs. To do this, first, create a `KeyPairCollection` from the key pairs you have.
These key pairs should correspond to the signers you added to your account.
```php
$kpc = new KeyPairCollection($keypair);
```

Now using these key pairs, we can sign the previously created command
```php
$signedCommand = $command->getSignedCommand($keyPairCollection);
$signedCommand = CommandSigner::sign($command, $kpc);
```
This returns a new instance of a `SignedCommand`

### Constructing commands from a string
Instead of signing the command in the backend, a command might be signed elsewhere (user wallet).
A signed command can be reconstructed from a valid Pact command string using:
```php
$signedCommand = SignedCommand::fromString($commandString)
$signedCommand = SignedCommandMapper::fromString($commandString)
```
A signed command can also be cast to a string or an array using
```php
$signedCommand->toString();
$signedCommand->toArray();
$commandString = SignedCommandMapper::toString($signedCommand);
$commandArray = SignedCommandMapper::toArray($signedCommand);
```

### Using the Client
Expand Down
19 changes: 11 additions & 8 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
namespace Kadena;

use JsonException;
use Kadena\Contracts\Pact as PactContract;
use Kadena\Pact\RequestKey;
use Kadena\Pact\RequestKeyCollection;
use Kadena\Pact\SignedCommand;
use Kadena\Pact\SignedCommandCollection;
use Kadena\Contracts\Client as ClientContract;
use Kadena\DataMappers\SignedCommandCollectionMapper;
use Kadena\DataMappers\SignedCommandMapper;
use Kadena\ValueObjects\Command\SignedCommand;
use Kadena\ValueObjects\Command\SignedCommandCollection;
use Kadena\ValueObjects\RequestKey\RequestKey;
use Kadena\ValueObjects\RequestKey\RequestKeyCollection;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
Expand All @@ -17,7 +19,7 @@
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class Client implements PactContract
class Client implements ClientContract
{
private HttpClientInterface $client;

Expand All @@ -36,11 +38,12 @@ public function __construct(private readonly string $apiUrl, HttpClientInterface
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws JsonException
*/
public function send(SignedCommandCollection $commands): RequestKeyCollection
{
$response = $this->client->request('POST', $this->apiUrl . '/api/v1/send', [
'json' => $commands->toPayload(),
'json' => SignedCommandCollectionMapper::toArray($commands),
]);

$requestKeys = array_map(static function (string $requestKey) {
Expand All @@ -57,7 +60,7 @@ public function send(SignedCommandCollection $commands): RequestKeyCollection
public function local(SignedCommand $command): ResponseInterface
{
return $this->client->request('POST', $this->apiUrl . '/api/v1/local', [
'json' => $command->toArray(),
'json' => SignedCommandMapper::toArray($command),
]);
}

Expand Down
10 changes: 5 additions & 5 deletions src/Contracts/Pact.php → src/Contracts/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Kadena\Contracts;

use Kadena\Pact\RequestKey;
use Kadena\Pact\RequestKeyCollection;
use Kadena\Pact\SignedCommand;
use Kadena\Pact\SignedCommandCollection;
use Kadena\ValueObjects\Command\SignedCommand;
use Kadena\ValueObjects\Command\SignedCommandCollection;
use Kadena\ValueObjects\RequestKey\RequestKey;
use Kadena\ValueObjects\RequestKey\RequestKeyCollection;
use Symfony\Contracts\HttpClient\ResponseInterface;

interface Pact
interface Client
{
public function send(SignedCommandCollection $commands): RequestKeyCollection;

Expand Down
10 changes: 10 additions & 0 deletions src/Contracts/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts;

interface Collection
{
public function toArray(): array;
public function first(): mixed;
public function get(int $offset): mixed;
}
12 changes: 12 additions & 0 deletions src/Contracts/Crypto/CommandSigner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\Crypto;

use Kadena\ValueObjects\Command\Command;
use Kadena\ValueObjects\Command\SignedCommand;
use Kadena\ValueObjects\Signer\KeyPairCollection;

interface CommandSigner
{
public static function sign(Command $command, KeyPairCollection $keyPairs): SignedCommand;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Kadena\Crypto\Contracts;
namespace Kadena\Contracts\Crypto;

interface Hash
{
Expand Down
10 changes: 10 additions & 0 deletions src/Contracts/Crypto/KeyFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\Crypto;

use Kadena\ValueObjects\Signer\KeyPair;

interface KeyFactory
{
public static function generate(): KeyPair;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<?php declare(strict_types=1);

namespace Kadena\Crypto\Contracts;
namespace Kadena\Contracts\Crypto;

use Kadena\Crypto\KeyPair;
use Kadena\Crypto\Signature;
use Kadena\Pact\Command;
use Kadena\ValueObjects\Signer\KeyPair;
use Kadena\ValueObjects\Signer\Signature;
use ParagonIE\Halite\Asymmetric\SignaturePublicKey;

interface Signer
interface MessageSigner
{
public static function sign(string $message, KeyPair $keyPair): Signature;

public static function signCommand(Command $command, KeyPair $keyPair): Signature;

public static function signHash(string $hash, KeyPair $keyPair): Signature;

public static function verifySignature(string $message, string $signature, SignaturePublicKey $publicKey): bool;
Expand Down
12 changes: 12 additions & 0 deletions src/Contracts/DataMappers/CommandMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\DataMappers;

use Kadena\ValueObjects\Command\Command;

interface CommandMapper
{
public static function toArray(Command $command): array;
public static function toString(Command $command): string;
public static function fromString(string $commandJson): Command;
}
11 changes: 11 additions & 0 deletions src/Contracts/DataMappers/SignedCommandCollectionMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\DataMappers;

use Kadena\ValueObjects\Command\SignedCommandCollection;

interface SignedCommandCollectionMapper
{
public static function toArray(SignedCommandCollection $commands): array;
public static function toString(SignedCommandCollection $commands): string;
}
12 changes: 12 additions & 0 deletions src/Contracts/DataMappers/SignedCommandMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\DataMappers;

use Kadena\ValueObjects\Command\SignedCommand;

interface SignedCommandMapper
{
public static function toArray(SignedCommand $command): array;
public static function toString(SignedCommand $command): string;
public static function fromString(string $commandJson): SignedCommand;
}
23 changes: 23 additions & 0 deletions src/Contracts/Pact/CommandFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\Pact;

use Kadena\ValueObjects\Command\Command;
use Kadena\ValueObjects\Command\Metadata;
use Kadena\ValueObjects\Command\Payload\ContinuePayload;
use Kadena\ValueObjects\Command\Payload\ExecutePayload;
use Kadena\ValueObjects\Signer\Signer;
use Kadena\ValueObjects\Signer\SignerCollection;

interface CommandFactory
{
public static function load(Command $command): self;
public function addSigner(Signer $signer): self;
public function withMetadata(Metadata $metadata): self;
public function withExecutePayload(ExecutePayload $payload): self;
public function withContinuePayload(ContinuePayload $payload): self;
public function withNonce(string $nonce): self;
public function withNetworkId(string $networkId): self;
public function withSigners(SignerCollection $signers): self;
public function make(): Command;
}
11 changes: 11 additions & 0 deletions src/Contracts/Pact/MetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace Kadena\Contracts\Pact;

use Kadena\ValueObjects\Command\Metadata;

interface MetadataFactory
{
public function withOptions(array $options): self;
public function make(): Metadata;
}
Loading

0 comments on commit ba0b603

Please sign in to comment.