Invoking soroban smart-contracts functions using PHP-SDK
Tags: soroban, stellar, php, smartcontract
Published: 2023-04-28 16:56:26

In my [last post](https://dev.to/icolomina/building-a-ballot-contract-using-soroban-plataform-and-rust-sdk-1hg1) about [Soroban](https://soroban.stellar.org/) smart-contract platform i tried to show you how to use [Soroban Rust SDK](https://docs.rs/soroban-sdk/latest/soroban_sdk/) to write and build an smart-contract to participate in a ballot process. 

In this post, I'm trying to show how to invoke a [Soroban](https://soroban.stellar.org/) smart-contract function from an already deployed and installed contract using [Soroban PHP SDK](https://soroban.stellar.org/docs/reference/sdks/php-sdk).

> For more information about deploying and installing contracts, refer to [soroban docs](https://soroban.stellar.org/docs/category/getting-started).

### The basics

Before invoking any function we have to get an instance of _Soneso\StellarSDK\Soroban\SorobanServer_ which will allow us to connect to Soroban Rpc server and be able to invoke functions.

```php
$server = new SorobanServer("https://rpc-futurenet.stellar.org:443");
$server->acknowledgeExperimental = true;
$server->enableLogging = true;

$healthResponse = $server->getHealth();
if (GetHealthResponse::HEALTHY == $healthResponse->status) {
    throw new \Exception("Server unavailable");
}
```

In the code we can see above, we first get a _SorobanServer_ instance. Then we set true flags _acknowledgeExperimental_ and _enableLogging_. 

- _acknowledgeExperimental_ is required to work so far.
- _enableLogging_ will show us call logs.

Now we have server instance, let's see how to get an account to invoke functions.

### Getting an account on futurenet

We need an stellar account to be able to sign transactions (invoking an smart-contract function is an stellar operation which is sent through a transaction). To get an stellar account we will need a key pair. You can get a key pair from [stellar laboratory](https://laboratory.stellar.org/#account-creator?network=futurenet).
Assuming we already have our key pair, let's get the account instance in php.

```php
$futurenet = StellarSDK::getFutureNetInstance();
$accountKeyPair = KeyPair::fromSeed('mysecretkey');
$account   = $futurenet->requestAccount($accountKeyPair->getAccountId());
```
As you can see, with only three code lines we can get the account instance:

- Get futurenet instance by using Soneso\StellarSDK\StellarSDK_ class
- Get key pair instance by using _Soneso\StellarSDK\Crypto\KeyPair_ class
- Finally request account to futurenet through account id

### Invoking non authenticated function

Now we have account and server instance and assuming we've deployed our contract to futurnet previously and have a contract id, let's invoke a non authenticated function:

The contract from the last post we referenced at the begining of the article, have a non authenticated function called _count_ which received no parameters. Let's try to invoke it.

```php
$operation = InvokeHostFunctionOperationBuilder::forInvokingContract($contractId, 'count');
$transaction = (new TransactionBuilder($account))->addOperation($operation)->build();

$simulateResponse = $server->simulateTransaction($transaction);
$footprint = $simulateResponse->getFootprint();

$transaction->setFootprint($footprint);
$transaction->sign($accountKeyPair, Network::futurenet());

$sendResponse = $server->sendTransaction($transaction);
if ($sendResponse->error == null) {
     $result = null;
     $failedStatus = null;
     while (empty($result) && empty($failedStatus)) {
         $transactionResponse = $server->getTransaction($sendResponse->hash);
         if (GetTransactionResponse::STATUS_NOT_FOUND == $transactionResponse->status) {
              sleep(1);
         } 
         else if (GetTransactionResponse::STATUS_SUCCESS == $transactionResponse->status) {
              $result = $transactionResponse->getResultValue();
         } 
         else if (GetTransactionResponse::STATUS_FAILED == $transactionResponse->status) {
              $failedStatus = $transactionResponse->error->getMessage();
         }
     }

     if ($result) {
          foreach ($result->getMap() as $partyInfo) {
             echo 'Result for ' . $partyInfo->key->getSym() . ': ' . $partyInfo->val->getU32() . PHP_EOL;
          }
          return Command::SUCCESS;
     }

     if ($failedStatus) {
         echo 'Invokation has failed';
     }
}
```

Let's dive into code step by step

```php
$operation = InvokeHostFunctionOperationBuilder::forInvokingContract($contractId, 'count');
$transaction = (new TransactionBuilder($account))->addOperation($operation)->build();
```

First of all, we create an stellar operation for invoking a contract function and then add it to a new transaction.

```php
$simulateResponse = $server->simulateTransaction($transaction);
$footprint = $simulateResponse->getFootprint();
```
Next, we simulate transaction and gets a footprint. We will need the footprint to send the transaction later.

```php
$transaction->setFootprint($footprint);
$transaction->sign($accountKeyPair, Network::futurenet());
```

After getting the footprint, we set it to the transaction and sign it. In order to sign the transaction we need the key pair instance we got before.

```php
$sendResponse = $server->sendTransaction($transaction);
```
We have the transaction ready so we send it using our server instance.

```php
if ($sendResponse->error == null) {
     $result = null;
     $failedStatus = null;
     while (empty($result) && empty($failedStatus)) {
         $transactionResponse = $server->getTransaction($sendResponse->hash);
         if (GetTransactionResponse::STATUS_NOT_FOUND == $transactionResponse->status) {
              sleep(1);
         } 
         else if (GetTransactionResponse::STATUS_SUCCESS == $transactionResponse->status) {
              $result = $transactionResponse->getResultValue();
         } 
         else if (GetTransactionResponse::STATUS_FAILED == $transactionResponse->status) {
              $failedStatus = $transactionResponse->error->getMessage();
         }
     }

     if ($result) {
         foreach ($result->getMap() as $partyInfo) {
            echo 'Result for ' . $partyInfo->key->getSym() . ': ' . $partyInfo->val->getU32() . PHP_EOL;
         }
         return Command::SUCCESS;
     }

     if ($failedStatus) {
         echo 'Invokation has failed';
     }
}
```
In this last part, if there is no error response, we proceed as follows:

- Looks into transaction response status
- If transaction's still not found, we wait one second and try again.
- If transaction is success, we get result value.
- If transaction is failed, we get failed message. 

If we execute this piece of code, we will get the following result:

![Count function result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hir8dbocmh74nnfty0s6.png)

### Invoking an authenticated function

Now let's see how to invoke _add_party_ function which requires two parameters:

- An address which invokes the function (will be authenticated on contract part)
- A party to add to the ballot

```php
$invokerAddress = new Address(Address::TYPE_ACCOUNT, accountId: $account->getAccountId());
$argsContract[] = $invokerAddress->toXdrSCVal();
$argsContract[] = XdrSCVal::forSymbol('Laborist');

$authInvocation = new AuthorizedInvocation($contractId, $function, args: $argsContract);
$contractAuth = new ContractAuth($authInvocation);

$invokeOp = InvokeHostFunctionOperationBuilder::forInvokingContract($contractId, $function, $argsContract, auth: [$contractAuth])->build();

$transaction = (new TransactionBuilder($account))->addOperation($invokeOp)->build();
$simulateResponse = $server->simulateTransaction($transaction);
```

As we did in the last section, let's dive into code step by step:

```php
$invokerAddress = new Address(Address::TYPE_ACCOUNT, accountId: $account->getAccountId());
$argsContract[] = $invokerAddress->toXdrSCVal();
$argsContract[] = XdrSCVal::forSymbol('Laborist');

$authInvocation = new AuthorizedInvocation($contractId, 'add_party', args: $argsContract);
$contractAuth = new ContractAuth($authInvocation);
```

- We create an _address_ ot type "account" by using Soneso\StellarSDK\Soroban\Address_ class.
- We add address and Laborist party to args contract

> Arguments are encoded using [XDR](https://developers.stellar.org/docs/encyclopedia/xdr) which is a binary format used by stellar to communicate transacctions on the network.

- We create an authorized invocation passing contract id, function name and arguments. Then we wrap it into a ContractAuth instance.

> As function invocator and transaction submitter are the same, we can avoid signing contract auth since it will be inferred from transaction. More information [here](https://soroban.stellar.org/docs/learn/authorization#transaction-invoker).

```php
$invokeOp = InvokeHostFunctionOperationBuilder::forInvokingContract($contractId, $function, $argsContract, auth: [$contractAuth])->build();

$transaction = (new TransactionBuilder($account))->addOperation($invokeOp)->build();
$simulateResponse = $server->simulateTransaction($transaction);
```
Now we create the invocation operation and simulate the transaction first. 

> The code for analize the transaction status is the same as before so let's go directly to gets the function response

```php
if($result) {
    echo 'Number of parties: ' . $result->getU32();
}
```
> If you take a look to [ballot contract article](https://dev.to/icolomina/building-a-ballot-contract-using-soroban-plataform-and-rust-sdk-1hg1) you will see that _add_party_ function returns the number of parties registered at the contract.

After invoking the function you would see the following result:

![add_party_function_result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eab10a48kiwiw2gcvzst.png)

And that's all, I hope it can serve you to invoke your smart contract functions :)