DEV Community

Nacho Colomina Torregrosa
Nacho Colomina Torregrosa

Posted on

Invoking soroban smart-contracts functions using PHP-SDK

In my last post about Soroban smart-contract platform i tried to show you how to use Soroban Rust 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 smart-contract function from an already deployed and installed contract using Soroban PHP SDK.

For more information about deploying and installing contracts, refer to soroban docs.

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.

$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");
}
Enter fullscreen mode Exit fullscreen mode

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.
Assuming we already have our key pair, let's get the account instance in php.

$futurenet = StellarSDK::getFutureNetInstance();
$accountKeyPair = KeyPair::fromSeed('mysecretkey');
$account   = $futurenet->requestAccount($accountKeyPair->getAccountId());
Enter fullscreen mode Exit fullscreen mode

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.

$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';
     }
}
Enter fullscreen mode Exit fullscreen mode

Let's dive into code step by step

$operation = InvokeHostFunctionOperationBuilder::forInvokingContract($contractId, 'count');
$transaction = (new TransactionBuilder($account))->addOperation($operation)->build();
Enter fullscreen mode Exit fullscreen mode

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

$simulateResponse = $server->simulateTransaction($transaction);
$footprint = $simulateResponse->getFootprint();
Enter fullscreen mode Exit fullscreen mode

Next, we simulate transaction and gets a footprint. We will need the footprint to send the transaction later.

$transaction->setFootprint($footprint);
$transaction->sign($accountKeyPair, Network::futurenet());
Enter fullscreen mode Exit fullscreen mode

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.

$sendResponse = $server->sendTransaction($transaction);
Enter fullscreen mode Exit fullscreen mode

We have the transaction ready so we send it using our server instance.

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';
     }
}
Enter fullscreen mode Exit fullscreen mode

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

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
$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);
Enter fullscreen mode Exit fullscreen mode

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

$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);
Enter fullscreen mode Exit fullscreen mode
  • 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 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.

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

$transaction = (new TransactionBuilder($account))->addOperation($invokeOp)->build();
$simulateResponse = $server->simulateTransaction($transaction);
Enter fullscreen mode Exit fullscreen mode

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

if($result) {
    echo 'Number of parties: ' . $result->getU32();
}
Enter fullscreen mode Exit fullscreen mode

If you take a look to ballot contract article 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

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

Top comments (0)