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");
}
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());
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';
}
}
Let's dive into code step by step
$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.
$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.
$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.
$sendResponse = $server->sendTransaction($transaction);
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';
}
}
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:
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);
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);
- 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);
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();
}
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:
And that's all, I hope it can serve you to invoke your smart contract functions :)