From 035f38d9e649fb0894bb36b987a29e6ebdd76ceb Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Wed, 10 Jul 2024 14:55:30 +0100 Subject: [PATCH 01/15] initial commit --- .../working-with-contract-specs.mdx | 459 ++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 docs/build/guides/conventions/working-with-contract-specs.mdx diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx new file mode 100644 index 000000000..7f1e5ec12 --- /dev/null +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -0,0 +1,459 @@ +--- +title: Work with ContractSpecs in Other Languages +hide_table_of_contents: true +draft: true +--- + +# Stellar Network and Soroban Smart Contracts Documentation + +## Table of Contents + +1. [Introduction](#introduction) +2. [Prerequisites](#prerequisites) +3. [Tutorials](#tutorials) 3.1 [Reading from a Contract](#reading-from-a-contract) 3.2 [Writing to a Contract](#writing-to-a-contract) 3.3 [Handling Contract Events](#handling-contract-events) +4. [Advanced Topics](#advanced-topics) 4.1 [Upgrading Contracts](#upgrading-contracts) 4.2 [Interacting with Multiple Contracts](#interacting-with-multiple-contracts) + +## Introduction + +The Stellar network is an open-source, distributed, and community-owned network used to facilitate cross-asset transfers of value. Soroban is Stellar's smart contract platform, designed to be safe, scalable, and easy to use. + +A contract specification typically includes: + +1. **Contract Address**: The unique identifier for the deployed contract on the Stellar network. +2. **ABI (Application Binary Interface)**: Defines the methods and their parameters that can be called on the contract. +3. **State Variables**: The data stored within the contract. +4. **Events**: Notifications emitted by the contract when certain actions occur. +5. **Functions**: The callable methods of the contract, including their inputs and outputs. + +These details guide the interaction with contracts through your chosen programming language by providing a clear interface for communication between your application and the smart contract on the Stellar network. + +## Prerequisites + +To interact with Soroban smart contracts on the Stellar network, you'll need the following tools and libraries for each language: + +### Go + +- Go 1.16+ +- Stellar SDK for Go: `go get github.com/stellar/go` + +### Java + +- Java 8+ +- Gradle or Maven for dependency management +- Stellar SDK for Java: Add to your `build.gradle` or `pom.xml` + +### Python + +- Python 3.7+ +- `stellar-sdk`: `pip install stellar-sdk` + +## Tutorials + +### Reading from a Contract + +Here are examples of reading data from a Soroban smart contract in Go, Java, and Python: + +#### Go + +```go +package main + +import ( + "fmt" + "log" + + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/txnbuild" +) + +func main() { + client := horizonclient.DefaultTestNetClient + contractID := "CC..." // Replace with actual contract ID + + // Create a transaction to invoke the contract + sourceAccount, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: "G..."}) // Replace with your account + if err != nil { + log.Fatalf("Error getting account details: %v", err) + } + + op := txnbuild.InvokeHostFunction{ + HostFunction: txnbuild.HostFunction{ + Function: txnbuild.HostFunctionTypeContractCall, + Parameters: []txnbuild.HostFunctionParameter{ + {Type: txnbuild.HostFunctionParameterTypeContractAddress, Value: contractID}, + {Type: txnbuild.HostFunctionParameterTypeString, Value: "get"}, + }, + }, + } + + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + IncrementSequenceNum: true, + Operations: []txnbuild.Operation{&op}, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewTimeout(300)}, + }, + ) + if err != nil { + log.Fatalf("Error creating transaction: %v", err) + } + + // Sign and submit the transaction + // (Signing step omitted for brevity) + + resp, err := client.SubmitTransaction(tx) + if err != nil { + log.Fatalf("Error submitting transaction: %v", err) + } + + fmt.Printf("Contract value: %s\n", resp.ResultXdr) +} +``` + +#### Java + +```java +import org.stellar.sdk.*; +import org.stellar.sdk.responses.AccountResponse; +import org.stellar.sdk.responses.SubmitTransactionResponse; + +public class ContractReader { + public static void main(String[] args) { + Server server = new Server("https://horizon-testnet.stellar.org"); + String contractId = "CC..."; // Replace with actual contract ID + + try { + AccountResponse sourceAccount = server.accounts().account("G..."); // Replace with your account + + InvokeHostFunctionOperation operation = new InvokeHostFunctionOperation.Builder() + .setFunction( + InvokeHostFunctionOperation.Function.contractCall( + contractId, + "get", + new org.stellar.sdk.xdr.ScVal[0] + ) + ) + .build(); + + Transaction transaction = new TransactionBuilder(sourceAccount, Network.TESTNET) + .addOperation(operation) + .setTimeout(30) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + // Sign the transaction + // (Signing step omitted for brevity) + + SubmitTransactionResponse response = server.submitTransaction(transaction); + + if (response.isSuccess()) { + System.out.println("Contract value: " + response.getResultXdr()); + } else { + System.err.println("Transaction failed: " + response.getExtras().getResultCodes().getTransactionResultCode()); + } + } catch (Exception e) { + System.err.println("Error reading from contract: " + e.getMessage()); + } + } +} +``` + +#### Python + +```python +from stellar_sdk import Server, Network, Keypair, TransactionBuilder +from stellar_sdk.soroban import Contract +from stellar_sdk.exceptions import BadRequestError, BadResponseError + +async def read_contract(): + server = Server("https://horizon-testnet.stellar.org") + network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + contract_id = "CC..." # Replace with actual contract ID + + source_keypair = Keypair.from_secret("S...") # Replace with your secret key + source_account = await server.load_account(source_keypair.public_key) + + contract = Contract(contract_id) + + try: + transaction = ( + TransactionBuilder( + source_account=source_account, + network_passphrase=network_passphrase, + base_fee=100) + .add_operation(contract.call("get")) + .set_timeout(30) + .build() + ) + + transaction.sign(source_keypair) + response = await server.submit_transaction(transaction) + print(f"Contract value: {response['result_xdr']}") + except (BadRequestError, BadResponseError) as e: + print(f"Error reading from contract: {e}") + +# Run the async function +import asyncio +asyncio.run(read_contract()) +``` + +### Writing to a Contract + +Examples of writing to a contract (incrementing a counter) in each language: + +#### Go + +```go +package main + +import ( + "fmt" + "log" + + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/network" + "github.com/stellar/go/txnbuild" +) + +func main() { + client := horizonclient.DefaultTestNetClient + contractID := "CC..." // Replace with actual contract ID + secretKey := "S..." // Replace with actual secret key + + kp, err := keypair.Parse(secretKey) + if err != nil { + log.Fatalf("Error parsing secret key: %v", err) + } + + sourceAccount, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: kp.Address()}) + if err != nil { + log.Fatalf("Error getting account details: %v", err) + } + + op := txnbuild.InvokeHostFunction{ + HostFunction: txnbuild.HostFunction{ + Function: txnbuild.HostFunctionTypeContractCall, + Parameters: []txnbuild.HostFunctionParameter{ + {Type: txnbuild.HostFunctionParameterTypeContractAddress, Value: contractID}, + {Type: txnbuild.HostFunctionParameterTypeString, Value: "increment"}, + }, + }, + } + + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + IncrementSequenceNum: true, + Operations: []txnbuild.Operation{&op}, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewTimeout(300)}, + }, + ) + if err != nil { + log.Fatalf("Error creating transaction: %v", err) + } + + tx, err = tx.Sign(network.TestNetworkPassphrase, kp.(*keypair.Full)) + if err != nil { + log.Fatalf("Error signing transaction: %v", err) + } + + resp, err := client.SubmitTransaction(tx) + if err != nil { + log.Fatalf("Error submitting transaction: %v", err) + } + + fmt.Printf("Transaction successful. New counter value: %v\n", resp.ResultXdr) +} +``` + +#### Java + +```java +import org.stellar.sdk.*; +import org.stellar.sdk.responses.AccountResponse; +import org.stellar.sdk.responses.SubmitTransactionResponse; + +public class ContractWriter { + public static void main(String[] args) { + Server server = new Server("https://horizon-testnet.stellar.org"); + String contractId = "CC..."; // Replace with actual contract ID + String secretKey = "S..."; // Replace with actual secret key + + try { + KeyPair sourceKeypair = KeyPair.fromSecretSeed(secretKey); + AccountResponse sourceAccount = server.accounts().account(sourceKeypair.getAccountId()); + + InvokeHostFunctionOperation operation = new InvokeHostFunctionOperation.Builder() + .setFunction( + InvokeHostFunctionOperation.Function.contractCall( + contractId, + "increment", + new org.stellar.sdk.xdr.ScVal[0] + ) + ) + .build(); + + Transaction transaction = new TransactionBuilder(sourceAccount, Network.TESTNET) + .addOperation(operation) + .setTimeout(30) + .setBaseFee(Transaction.MIN_BASE_FEE) + .build(); + + transaction.sign(sourceKeypair); + SubmitTransactionResponse response = server.submitTransaction(transaction); + + if (response.isSuccess()) { + System.out.println("Transaction successful. New counter value: " + response.getResultXdr()); + } else { + System.err.println("Transaction failed: " + response.getExtras().getResultCodes().getTransactionResultCode()); + } + } catch (Exception e) { + System.err.println("Error writing to contract: " + e.getMessage()); + } + } +} +``` + +#### Python + +```python +from stellar_sdk import Server, Network, Keypair, TransactionBuilder +from stellar_sdk.soroban import Contract +from stellar_sdk.exceptions import BadRequestError, BadResponseError + +async def increment_counter(secret_key): + server = Server("https://horizon-testnet.stellar.org") + network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + contract_id = "CC..." # Replace with actual contract ID + + source_keypair = Keypair.from_secret(secret_key) + source_account = await server.load_account(source_keypair.public_key) + + contract = Contract(contract_id) + + try: + transaction = ( + TransactionBuilder( + source_account=source_account, + network_passphrase=network_passphrase, + base_fee=100) + .add_operation(contract.call("increment")) + .set_timeout(30) + .build() + ) + + transaction.sign(source_keypair) + response = await server.submit_transaction(transaction) + print(f"Transaction successful. New counter value: {response['result_xdr']}") + except (BadRequestError, BadResponseError) as e: + print(f"Error writing to contract: {e}") + +# Run the async function +import asyncio +asyncio.run(increment_counter("S...")) # Replace with actual secret key +``` + +### Handling Contract Events + +Examples of listening for events from a contract in each language: + +#### Go + +```go +package main + +import ( + "fmt" + "log" + + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/protocols/horizon" +) + +func main() { + client := horizonclient.DefaultTestNetClient + contractID := "CC..." // Replace with actual contract ID + + // Create an operation request to listen for contract events + opRequest := horizonclient.OperationRequest{ + ForAccount: contractID, + Cursor: "now", + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go client.StreamOperations(ctx, opRequest, func(op horizon.Operation) { + event, ok := op.(horizon.ContractEvent) + if !ok { + return + } + fmt.Printf("New event: %+v\n", event) + // Handle the event based on its type and data + }) + + // Keep the program running + select {} +} +``` + +#### Java + +```java +import org.stellar.sdk.*; +import org.stellar.sdk.requests.EventsRequestBuilder; +import org.stellar.sdk.responses.operations.OperationResponse; + +public class ContractEventListener { + public static void main(String[] args) { + Server server = new Server("https://horizon-testnet.stellar.org"); + String contractId = "CC..."; // Replace with actual contract ID + + try { + EventsRequestBuilder eventsRequest = server.events(); + eventsRequest.forContract(contractId); + eventsRequest.cursor("now"); + + eventsRequest.stream(new EventListener() { + @Override + public void onEvent(OperationResponse event) { + System.out.println("New event: " + event); + // Handle the event based on its type and data + } + + @Override + public void onFailure(Optional error) { + System.err.println("Error in event stream: " + error.orElse(new Exception("Unknown error"))); + } + }); + + // Keep the program running + while (true) { + Thread.sleep(1000); + } + } catch (Exception e) { + System.err.println("Error setting up event listener: " + e.getMessage()); + } + } +} +``` + +#### Python + +```python +from stellar_sdk import Server +from stellar_sdk.soroban import Contract + +async def listen_for_events(): + server = Server("https://horizon-testnet.stellar.org") + contract_id = "CC..." # Replace with actual contract ID + + contract = Contract(contract_id) + + async for event in contract.events(server): + print(f"New event: {event}") +``` From 5ba54d6c2cd1a1ec113464548ff33a3ed42ad19f Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Wed, 31 Jul 2024 23:52:16 +0100 Subject: [PATCH 02/15] setup layout --- .../working-with-contract-specs.mdx | 539 +++++------------- 1 file changed, 140 insertions(+), 399 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 7f1e5ec12..ded58a4bf 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -1,459 +1,200 @@ --- -title: Work with ContractSpecs in Other Languages -hide_table_of_contents: true -draft: true +title: Work with Contract Specs in Other Languages +description: A guide to understanding and interacting with Soroban smart contracts in different programming languages. +hide_table_of_contents: false --- -# Stellar Network and Soroban Smart Contracts Documentation - -## Table of Contents - -1. [Introduction](#introduction) -2. [Prerequisites](#prerequisites) -3. [Tutorials](#tutorials) 3.1 [Reading from a Contract](#reading-from-a-contract) 3.2 [Writing to a Contract](#writing-to-a-contract) 3.3 [Handling Contract Events](#handling-contract-events) -4. [Advanced Topics](#advanced-topics) 4.1 [Upgrading Contracts](#upgrading-contracts) 4.2 [Interacting with Multiple Contracts](#interacting-with-multiple-contracts) +# Interacting with Soroban Smart Contracts ## Introduction -The Stellar network is an open-source, distributed, and community-owned network used to facilitate cross-asset transfers of value. Soroban is Stellar's smart contract platform, designed to be safe, scalable, and easy to use. +Soroban smart contracts are powerful tools for building decentralized applications on the Stellar network. To interact with these contracts effectively, it's crucial to understand their specifications and how to use them in your programming language of choice. -A contract specification typically includes: +A typical contract specification (spec) includes: -1. **Contract Address**: The unique identifier for the deployed contract on the Stellar network. -2. **ABI (Application Binary Interface)**: Defines the methods and their parameters that can be called on the contract. -3. **State Variables**: The data stored within the contract. -4. **Events**: Notifications emitted by the contract when certain actions occur. -5. **Functions**: The callable methods of the contract, including their inputs and outputs. +1. Data types used by the contract +2. Function definitions with their inputs and outputs +3. Error types that the contract may return -These details guide the interaction with contracts through your chosen programming language by providing a clear interface for communication between your application and the smart contract on the Stellar network. +These details guide how you interact with the contract, regardless of the programming language you're using. ## Prerequisites -To interact with Soroban smart contracts on the Stellar network, you'll need the following tools and libraries for each language: - -### Go - -- Go 1.16+ -- Stellar SDK for Go: `go get github.com/stellar/go` - -### Java +Before diving into contract interactions, ensure you have the following: -- Java 8+ -- Gradle or Maven for dependency management -- Stellar SDK for Java: Add to your `build.gradle` or `pom.xml` +- Stellar CLI ([`stellar`](/docs/build/smart-contracts/getting-started/setup#install-the-stellar-cli)) installed +- A Soroban-compatible SDK for your programming language. View the [list of available SDKs](/docs/tools/sdks/library) to find one that suits your needs. +- Access to a Soroban [RPC server](/docs/learn/fundamentals/networks) (local or on a test network) -### Python +For this Guide, we will focus on the Java, and PHP SDKs for reference but the concepts can be applied to other languages as well. -- Python 3.7+ -- `stellar-sdk`: `pip install stellar-sdk` +## What are Contract Specs? -## Tutorials +A contract spec is just like an ABI (Application Binary Interface) in Ethereum, it is a standardized description of a smart contract's interface, typically in JSON format. It defines the contract's functions, data structures, events, and errors in a way that external applications can understand and use. -### Reading from a Contract +This specification serves as a crucial bridge between the smart contract and client applications, enabling them to interact without needing to know the contract's internal implementation details. -Here are examples of reading data from a Soroban smart contract in Go, Java, and Python: +## Generating Contract Specs -#### Go +The stellar CLI provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the WASM binary of the contract. -```go -package main +Sometimes you may not have access to the contract's source code or the ability to compile it. In such cases, you need to use the [`stellar contract fetch`](/docs/tools/stellar-cli#stellar-contract-fetch) command to download the contract's WASM binary and generate the spec. -import ( - "fmt" - "log" +Finally, we use the [`stellar bindings`](/docs/tools/stellar-cli#stellar-contract-bindings-json) comand to generate the contract spec from the WASM binary. - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/txnbuild" -) +#### Fetching the Contract Binary -func main() { - client := horizonclient.DefaultTestNetClient - contractID := "CC..." // Replace with actual contract ID - - // Create a transaction to invoke the contract - sourceAccount, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: "G..."}) // Replace with your account - if err != nil { - log.Fatalf("Error getting account details: %v", err) - } - - op := txnbuild.InvokeHostFunction{ - HostFunction: txnbuild.HostFunction{ - Function: txnbuild.HostFunctionTypeContractCall, - Parameters: []txnbuild.HostFunctionParameter{ - {Type: txnbuild.HostFunctionParameterTypeContractAddress, Value: contractID}, - {Type: txnbuild.HostFunctionParameterTypeString, Value: "get"}, - }, - }, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &sourceAccount, - IncrementSequenceNum: true, - Operations: []txnbuild.Operation{&op}, - BaseFee: txnbuild.MinBaseFee, - Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewTimeout(300)}, - }, - ) - if err != nil { - log.Fatalf("Error creating transaction: %v", err) - } - - // Sign and submit the transaction - // (Signing step omitted for brevity) +```bash +stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' --rpc-url https://soroban-testnet.stellar.org --id CONTRACT_ID --out-file contract.wasm +``` - resp, err := client.SubmitTransaction(tx) - if err != nil { - log.Fatalf("Error submitting transaction: %v", err) - } +#### Generating the Contract Spec from WASM - fmt.Printf("Contract value: %s\n", resp.ResultXdr) -} +```bash +stellar contract bindings json --wasm contract.wasm > abi.json ``` -#### Java - -```java -import org.stellar.sdk.*; -import org.stellar.sdk.responses.AccountResponse; -import org.stellar.sdk.responses.SubmitTransactionResponse; - -public class ContractReader { - public static void main(String[] args) { - Server server = new Server("https://horizon-testnet.stellar.org"); - String contractId = "CC..."; // Replace with actual contract ID - - try { - AccountResponse sourceAccount = server.accounts().account("G..."); // Replace with your account - - InvokeHostFunctionOperation operation = new InvokeHostFunctionOperation.Builder() - .setFunction( - InvokeHostFunctionOperation.Function.contractCall( - contractId, - "get", - new org.stellar.sdk.xdr.ScVal[0] - ) - ) - .build(); - - Transaction transaction = new TransactionBuilder(sourceAccount, Network.TESTNET) - .addOperation(operation) - .setTimeout(30) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - // Sign the transaction - // (Signing step omitted for brevity) - - SubmitTransactionResponse response = server.submitTransaction(transaction); - - if (response.isSuccess()) { - System.out.println("Contract value: " + response.getResultXdr()); - } else { - System.err.println("Transaction failed: " + response.getExtras().getResultCodes().getTransactionResultCode()); - } - } catch (Exception e) { - System.err.println("Error reading from contract: " + e.getMessage()); +## Understanding Contract Specs + +The contract spec we generated earlier might look something like this: + +```json +[ + { + "type": "function", + "doc": "Return the admin address.", + "name": "admin", + "inputs": [], + "outputs": [ + { + "type": "address" + } + ] + }, + { + "type": "function", + "doc": "Set the config of a minter for the given contract. Requires auth from\nthe admin.", + "name": "set_minter", + "inputs": [ + { + "doc": "", + "name": "contract", + "value": { + "type": "address" } - } -} + }, + { + "doc": "", + "name": "minter", + "value": { + "type": "address" + } + }, + { + "doc": "", + "name": "config", + "value": { + "type": "custom", + "name": "MinterConfig" + } + } + ], + "outputs": [] + } +] ``` -#### Python - -```python -from stellar_sdk import Server, Network, Keypair, TransactionBuilder -from stellar_sdk.soroban import Contract -from stellar_sdk.exceptions import BadRequestError, BadResponseError - -async def read_contract(): - server = Server("https://horizon-testnet.stellar.org") - network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE - contract_id = "CC..." # Replace with actual contract ID - - source_keypair = Keypair.from_secret("S...") # Replace with your secret key - source_account = await server.load_account(source_keypair.public_key) - - contract = Contract(contract_id) - - try: - transaction = ( - TransactionBuilder( - source_account=source_account, - network_passphrase=network_passphrase, - base_fee=100) - .add_operation(contract.call("get")) - .set_timeout(30) - .build() - ) - - transaction.sign(source_keypair) - response = await server.submit_transaction(transaction) - print(f"Contract value: {response['result_xdr']}") - except (BadRequestError, BadResponseError) as e: - print(f"Error reading from contract: {e}") - -# Run the async function -import asyncio -asyncio.run(read_contract()) -``` +This spec defines a `mint` function that takes four parameters and returns a result that's either an empty tuple or an error. -### Writing to a Contract +## Interacting with Contracts -Examples of writing to a contract (incrementing a counter) in each language: +### Reading from a Contract -#### Go +To read data from a contract, you typically use view functions. Here's an example using Python: -```go -package main +```python +from stellar_sdk import Server, Network, Keypair +from stellar_sdk.soroban import SorobanServer, Contract -import ( - "fmt" - "log" +server = Server("https://soroban-testnet.stellar.org") +soroban_server = SorobanServer("https://soroban-testnet.stellar.org") +network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/network" - "github.com/stellar/go/txnbuild" -) +contract_id = "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2" +contract = Contract(contract_id) -func main() { - client := horizonclient.DefaultTestNetClient - contractID := "CC..." // Replace with actual contract ID - secretKey := "S..." // Replace with actual secret key - - kp, err := keypair.Parse(secretKey) - if err != nil { - log.Fatalf("Error parsing secret key: %v", err) - } - - sourceAccount, err := client.AccountDetail(horizonclient.AccountRequest{AccountID: kp.Address()}) - if err != nil { - log.Fatalf("Error getting account details: %v", err) - } - - op := txnbuild.InvokeHostFunction{ - HostFunction: txnbuild.HostFunction{ - Function: txnbuild.HostFunctionTypeContractCall, - Parameters: []txnbuild.HostFunctionParameter{ - {Type: txnbuild.HostFunctionParameterTypeContractAddress, Value: contractID}, - {Type: txnbuild.HostFunctionParameterTypeString, Value: "increment"}, - }, - }, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &sourceAccount, - IncrementSequenceNum: true, - Operations: []txnbuild.Operation{&op}, - BaseFee: txnbuild.MinBaseFee, - Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewTimeout(300)}, - }, - ) - if err != nil { - log.Fatalf("Error creating transaction: %v", err) - } - - tx, err = tx.Sign(network.TestNetworkPassphrase, kp.(*keypair.Full)) - if err != nil { - log.Fatalf("Error signing transaction: %v", err) - } - - resp, err := client.SubmitTransaction(tx) - if err != nil { - log.Fatalf("Error submitting transaction: %v", err) - } - - fmt.Printf("Transaction successful. New counter value: %v\n", resp.ResultXdr) -} +# Call a view function +result = contract.admin() +print(f"Admin address: {result}") ``` -#### Java - -```java -import org.stellar.sdk.*; -import org.stellar.sdk.responses.AccountResponse; -import org.stellar.sdk.responses.SubmitTransactionResponse; - -public class ContractWriter { - public static void main(String[] args) { - Server server = new Server("https://horizon-testnet.stellar.org"); - String contractId = "CC..."; // Replace with actual contract ID - String secretKey = "S..."; // Replace with actual secret key - - try { - KeyPair sourceKeypair = KeyPair.fromSecretSeed(secretKey); - AccountResponse sourceAccount = server.accounts().account(sourceKeypair.getAccountId()); - - InvokeHostFunctionOperation operation = new InvokeHostFunctionOperation.Builder() - .setFunction( - InvokeHostFunctionOperation.Function.contractCall( - contractId, - "increment", - new org.stellar.sdk.xdr.ScVal[0] - ) - ) - .build(); - - Transaction transaction = new TransactionBuilder(sourceAccount, Network.TESTNET) - .addOperation(operation) - .setTimeout(30) - .setBaseFee(Transaction.MIN_BASE_FEE) - .build(); - - transaction.sign(sourceKeypair); - SubmitTransactionResponse response = server.submitTransaction(transaction); - - if (response.isSuccess()) { - System.out.println("Transaction successful. New counter value: " + response.getResultXdr()); - } else { - System.err.println("Transaction failed: " + response.getExtras().getResultCodes().getTransactionResultCode()); - } - } catch (Exception e) { - System.err.println("Error writing to contract: " + e.getMessage()); - } - } -} -``` +### Writing to a Contract -#### Python +Writing to a contract involves creating and submitting a transaction. Here's how you might call the `mint` function: ```python -from stellar_sdk import Server, Network, Keypair, TransactionBuilder -from stellar_sdk.soroban import Contract -from stellar_sdk.exceptions import BadRequestError, BadResponseError - -async def increment_counter(secret_key): - server = Server("https://horizon-testnet.stellar.org") - network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE - contract_id = "CC..." # Replace with actual contract ID - - source_keypair = Keypair.from_secret(secret_key) - source_account = await server.load_account(source_keypair.public_key) - - contract = Contract(contract_id) - - try: - transaction = ( - TransactionBuilder( - source_account=source_account, - network_passphrase=network_passphrase, - base_fee=100) - .add_operation(contract.call("increment")) - .set_timeout(30) - .build() - ) - - transaction.sign(source_keypair) - response = await server.submit_transaction(transaction) - print(f"Transaction successful. New counter value: {response['result_xdr']}") - except (BadRequestError, BadResponseError) as e: - print(f"Error writing to contract: {e}") - -# Run the async function -import asyncio -asyncio.run(increment_counter("S...")) # Replace with actual secret key -``` +from stellar_sdk import TransactionBuilder, Account, Asset -### Handling Contract Events +source_keypair = Keypair.from_secret("YOUR_SECRET_KEY") +source_account = server.load_account(source_keypair.public_key) -Examples of listening for events from a contract in each language: +transaction = ( + TransactionBuilder( + source_account=source_account, + network_passphrase=network_passphrase, + base_fee=100000, + ) + .append_invoke_contract_function_op( + contract_id=contract_id, + function_name="mint", + parameters=[ + Address(contract_id), + Address(source_keypair.public_key), + Address("GBCR..."), # 'to' address + ScVal.to_int128(1000000), # amount + ], + ) + .set_timeout(30) + .build() +) + +transaction.sign(source_keypair) +response = soroban_server.send_transaction(transaction) +print(f"Transaction response: {response}") +``` -#### Go +### Handling Contract Events -```go -package main +Contracts can emit events that you might want to listen for and handle. Here's a basic example of how to stream events: -import ( - "fmt" - "log" +```python +def process_event(event): + print(f"Received event: {event}") - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/protocols/horizon" +soroban_server.stream_events( + start_ledger=12345, # Replace with appropriate start ledger + contract_ids=[contract_id], + callback=process_event ) +``` -func main() { - client := horizonclient.DefaultTestNetClient - contractID := "CC..." // Replace with actual contract ID - - // Create an operation request to listen for contract events - opRequest := horizonclient.OperationRequest{ - ForAccount: contractID, - Cursor: "now", - } +## Advanced Topics - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() +### Upgrading Contracts - go client.StreamOperations(ctx, opRequest, func(op horizon.Operation) { - event, ok := op.(horizon.ContractEvent) - if !ok { - return - } - fmt.Printf("New event: %+v\n", event) - // Handle the event based on its type and data - }) +Upgrading a contract typically involves deploying a new version and updating any necessary pointers or storage. The exact process depends on how the contract was designed for upgrades. - // Keep the program running - select {} -} -``` +### Interacting with Multiple Contracts -#### Java - -```java -import org.stellar.sdk.*; -import org.stellar.sdk.requests.EventsRequestBuilder; -import org.stellar.sdk.responses.operations.OperationResponse; - -public class ContractEventListener { - public static void main(String[] args) { - Server server = new Server("https://horizon-testnet.stellar.org"); - String contractId = "CC..."; // Replace with actual contract ID - - try { - EventsRequestBuilder eventsRequest = server.events(); - eventsRequest.forContract(contractId); - eventsRequest.cursor("now"); - - eventsRequest.stream(new EventListener() { - @Override - public void onEvent(OperationResponse event) { - System.out.println("New event: " + event); - // Handle the event based on its type and data - } - - @Override - public void onFailure(Optional error) { - System.err.println("Error in event stream: " + error.orElse(new Exception("Unknown error"))); - } - }); - - // Keep the program running - while (true) { - Thread.sleep(1000); - } - } catch (Exception e) { - System.err.println("Error setting up event listener: " + e.getMessage()); - } - } -} -``` +When working with multiple contracts, you might need to coordinate calls between them. Always be mindful of the order of operations and potential inter-contract dependencies. -#### Python +## Conclusion -```python -from stellar_sdk import Server -from stellar_sdk.soroban import Contract +This guide has covered the basics of interacting with Soroban smart contracts. Remember to always refer to the specific contract's documentation and spec for accurate interaction details. As you become more comfortable with these concepts, you'll be able to build more complex and powerful decentralized applications on the Stellar network. -async def listen_for_events(): - server = Server("https://horizon-testnet.stellar.org") - contract_id = "CC..." # Replace with actual contract ID +For more detailed information, check out the [Stellar documentation](https://developers.stellar.org) and the [Soroban documentation](https://soroban.stellar.org). - contract = Contract(contract_id) +``` - async for event in contract.events(server): - print(f"New event: {event}") +This draft provides a structured guide that covers the requested points, including an introduction to contract specs, prerequisites, tutorials on interacting with contracts (reading, writing, and handling events), and touches on some advanced topics. You can expand on each section as needed, adding more code examples or detailed explanations based on your specific requirements. ``` From e2b2e9806b9d666be6fe8ada7e1da9a58caa36a6 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Fri, 2 Aug 2024 02:53:02 +0100 Subject: [PATCH 03/15] contract spec complex and simple examples --- .../working-with-contract-specs.mdx | 962 ++++++++++++++++-- 1 file changed, 888 insertions(+), 74 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index ded58a4bf..b78c654a5 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -4,6 +4,8 @@ description: A guide to understanding and interacting with Soroban smart contrac hide_table_of_contents: false --- +import { CodeExample } from "@site/src/components/CodeExample"; + # Interacting with Soroban Smart Contracts ## Introduction @@ -26,7 +28,7 @@ Before diving into contract interactions, ensure you have the following: - A Soroban-compatible SDK for your programming language. View the [list of available SDKs](/docs/tools/sdks/library) to find one that suits your needs. - Access to a Soroban [RPC server](/docs/learn/fundamentals/networks) (local or on a test network) -For this Guide, we will focus on the Java, and PHP SDKs for reference but the concepts can be applied to other languages as well. +For this Guide, we will focus on the Java, Python and PHP SDKs for reference but the concepts can be applied to other languages as well. ## What are Contract Specs? @@ -54,139 +56,951 @@ stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' stellar contract bindings json --wasm contract.wasm > abi.json ``` -## Understanding Contract Specs +## Soroban types + +Before we dive into interacting with Soroban smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: + +- `u32`: Unsigned 32-bit integer +- `u64`: Unsigned 64-bit integer +- `i32`: Signed 32-bit integer +- `i64`: Signed 64-bit integer +- `u128`: Unsigned 128-bit integer +- `i128`: Signed 128-bit integer +- `bool`: Boolean +- `string`: UTF-8 encoded string +- `vec`: Variable-length array +- `address`: Stellar account address +- `map`: Key-value map +- `symbol`: A small string used mainly for function name and map keys + +In this guide and in the SDKs, these types are represented as `ScU32`, `ScU64`, `ScI32`, `ScI64`, `ScU128`, `ScI128`, `ScBool`, `ScString`, `ScVec`, `ScAddress`, `ScMap`, and `ScSymbol` respectively. + +Every other complex type can be derived using these basic types but these types do not really map to values in the programming languages. The Soroban SDKs provide helper classes to work with these types. + +## Working with Native Soroban Types + +One of the most common tasks when working with Soroban smart contracts is converting between Soroban types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used in invoking contracts with the help of the contract spec. + +In most SDKs, the `ScVal` calss or function is used to convert between Soroban types and native types. + +:::note + +The JSON code block shows the contract spec while RUST code blocks show the contract for each example. -The contract spec we generated earlier might look something like this: +::: + +### 1. Invoking a Contract Function with no Parameters + +We will be using the `increment` function of the sample [increment contract](https://github.com/stellar/soroban-examples/tree/main/increment) contract to exemplify this. The `increment` function takes no parameters and increments the counter by 1. + +In this scenario, there is no need for conversions and passing the value `null` as contract arguments is sufficient in most SDKs. + + + +```rust +#[contractimpl] +impl IncrementContract { + /// Increment increments an internal counter, and returns the value. + pub fn increment(env: Env) -> u32 { + // Get the current count. + let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0); // If no value set, assume 0. + log!(&env, "count: {}", count); + // Increment the count. + count += 1; + + // Save the count. + env.storage().instance().set(&COUNTER, &count); + env.storage().instance().extend_ttl(50, 100); + + // Return the count to the caller. + count + } +} +``` ```json [ { "type": "function", - "doc": "Return the admin address.", - "name": "admin", + "doc": "Increment increments an internal counter, and returns the value.", + "name": "increment", "inputs": [], "outputs": [ { - "type": "address" + "type": "u32" } ] - }, + } +] +``` + +```python +# pip install --upgrade stellar-sdk +from stellar_sdk import SorobanServer, soroban_rpc, Account, Asset, Keypair, Network, TransactionBuilder + +def send_transaction() -> soroban_rpc.SendTransactionResponse: + server = SorobanServer(server_url='https://soroban-testnet.stellar.org', client=None) + + root_keypair = Keypair.from_secret( + "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + ) + root_account = server.load_account("GBSBL6FBPX5UHKL4AZCPUU6PXKUBYMKRUN3L4YQ4V2CCWSE7YMN2HYPB") + contract_id = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE" + transaction = ( + TransactionBuilder( + source_account=root_account, + network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE, + base_fee=100, + ) + .append_invoke_contract_function_op(contract_id,"increment") + # mark this transaction as valid only for the next 30 seconds + .set_timeout(30) + .build() + ) + transaction.sign(root_keypair) + response = server.send_transaction(transaction) + return response + +response = send_transaction() + +print("status", response.status) +print("hash:", response.hash); +print("status:", response.status); +print("errorResultXdr:", response.error_result_xdr); +``` + +```java +// implementation 'network.lightsail:stellar-sdk:0.44.0' +import org.stellar.sdk.AccountConverter; +import org.stellar.sdk.InvokeHostFunctionOperation; +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.Network; +import org.stellar.sdk.SorobanServer; +import org.stellar.sdk.Transaction; +import org.stellar.sdk.TransactionBuilder; +import org.stellar.sdk.TransactionBuilderAccount; +import org.stellar.sdk.responses.sorobanrpc.SendTransactionResponse; + +public class SendTransactionExample { + + public static void main(String[] args) { + SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org"); + try { + TransactionBuilderAccount account = server.getAccount("GBSBL6FBPX5UHKL4AZCPUU6PXKUBYMKRUN3L4YQ4V2CCWSE7YMN2HYPB"); + KeyPair sourceKeyPair = KeyPair.fromSecretSeed("SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + String contractId = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"; + InvokeHostFunctionOperation operation = InvokeHostFunctionOperation.invokeContractFunctionOperationBuilder(contractId, "increment", null).build(); + // Build the transaction + Transaction transaction = new TransactionBuilder(AccountConverter.enableMuxed(), account, Network.TESTNET) + .addOperation(operation) + .build(); + + // Sign the transaction + transaction.sign(sourceKeyPair); + + // Send the transaction using the SorobanServer + SendTransactionResponse response = server.sendTransaction(transaction); + System.out.println(response.getStatus()); + System.out.println(response.getHash()); + System.out.println(response.getLatestLedger()); + System.out.println(response.getLatestLedgerCloseTime()); + } catch (Exception e) { + System.err.println("An error has occurred:"); + e.printStackTrace(); + } + } +} + +``` + +```php +getAccount($accountAId); + $network = Network::testnet(); + $contractId = "CAMN24E6KNIXQBYPJJ4K7XRCUUJUMYSJRCSLZ2WOO6WJTSKIXAZWNYHK"; + + $invokeContractHostFunction = new InvokeContractHostFunction($contractId, "increment", null); + $builder = new InvokeHostFunctionOperationBuilder($invokeContractHostFunction); + $op = $builder->build(); + + $transaction = (new TransactionBuilder($accountA)) + ->addOperation($op)->build(); + + $request = new SimulateTransactionRequest($transaction); + $simulateResponse = $server->simulateTransaction($request); + + $transaction->setSorobanTransactionData($simulateResponse->getTransactionData()); + $transaction->addResourceFee($simulateResponse->minResourceFee); + $transaction->sign($accountAKeyPair, $network); + + $server->sendTransaction($transaction); + } +} +``` + + + +:::info + +subsequent examples will show code blocks for using the contract spec only to reduce redundancy + +::: + +### 2. Invoking a Contract Function with one or more Parameters + +Generally, this involves passing in a native `array` (not a `ScVec`) of parameters to the contract function. + +We will be using the `hello` function of the sample [Hello World Contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) contract to exemplify this. + +We know from the Spec that the `hello` function takes a string parameter and returns a vector of strings. In this scenario, we need to convert the string parameter to a `ScString` type before passing it to the contract. + +This process is convenient using the `ScVal` class or function in most SDKs. + + + +```rust +#[contract] +pub struct HelloContract; + +#[contractimpl] +impl HelloContract { + pub fn hello(env: Env, to: String) -> Vec { + vec![&env, String::from_str(&env, "Hello"), to] + } +} +``` + +```json +[ { "type": "function", - "doc": "Set the config of a minter for the given contract. Requires auth from\nthe admin.", - "name": "set_minter", + "doc": "", + "name": "hello", "inputs": [ { "doc": "", - "name": "contract", + "name": "to", "value": { - "type": "address" + "type": "string" } - }, + } + ], + "outputs": [ + { + "type": "vec", + "element": { + "type": "string" + } + } + ] + } +] +``` + +```python +from stellar_sdk import Keypair, Network, SorobanServer, TransactionBuilder, scval +..... + tx = ( + TransactionBuilder(source, network_passphrase, base_fee=100) + .set_timeout(300) + .append_invoke_contract_function_op( + contract_id=contract_id, + function_name="hello", + parameters=[ + scval.to_string("John"), + ], + ) + .build() +``` + +```java +import org.stellar.sdk.scval.Scv; +import org.stellar.sdk.xdr.SCVal; +// ..... +List contractArgs = new ArrayList(); +contractArgs.add(Scv.toString("John")); +InvokeHostFunctionOperation operation = InvokeHostFunctionOperation + .invokeContractFunctionOperationBuilder(contractId, "hello", contractArgs).build(); +TransactionBuilder transaction = new TransactionBuilder(source, Network.TESTNET); +Transaction tx = transaction.addOperation(operation).build(); +``` + +```php + $arg = \Soneso\StellarSDK\Xdr\XdrSCVal::forString("John"); + $invokeContractHostFunction = new InvokeContractHostFunction($contractId, "hello", [$arg]); + $builder = new InvokeHostFunctionOperationBuilder($invokeContractHostFunction); + $op = $builder->build(); + + $transaction = (new TransactionBuilder($accountA)) + ->addOperation($op)->build(); + +``` + + + +### 3. Getting Responses from Contracts + +Data returned from contracts are also in `ScVal` format and need to be converted to native types in your programming language. + +We will still be using the `hello` function of the sample [Hello World Contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) contract to exemplify this. + +We know from the Spec that the `hello` function takes a string parameter and returns a vec of strings. In this scenario, we need to convert the value returned from an `ScVec` of `ScString` type to `array` of `string` before making use of it. + +Steps: + +- Extract an `ScVec` from the return value +- Extract each `ScString` from the `ScVec` +- Convert each `ScString` to a native string + +This process is convenient using the `ScVal` class or function in most SDKs. + +Ideally, to retrieve this value, we need to use the [getTransaction](/docs/data/rpc/api-reference/methods/getTransaction) RPC method using the response hash of the transaction that invoked the contract function. + + + +```rust +#[contract] +pub struct HelloContract; + +#[contractimpl] +impl HelloContract { + pub fn hello(env: Env, to: String) -> Vec { + vec![&env, String::from_str(&env, "Hello"), to] + } +} +``` + +```json +[ + { + "type": "function", + "doc": "", + "name": "hello", + "inputs": [ { "doc": "", - "name": "minter", + "name": "to", "value": { - "type": "address" + "type": "string" + } + } + ], + "outputs": [ + { + "type": "vec", + "element": { + "type": "string" + } + } + ] + } +] +``` + +```python +from stellar_sdk import SorobanServer, soroban_rpc +from stellar_sdk import xdr as stellar_xdr +from stellar_sdk.soroban_rpc import GetTransactionStatus + +def get_transaction(hash: str) -> soroban_rpc.GetTransactionResponse: + server = SorobanServer(server_url='https://soroban-testnet.stellar.org', client=None) + tx = server.get_transaction(hash) + return tx + +get_transaction_data = get_transaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121") + +if get_transaction_data.status == GetTransactionStatus.SUCCESS: + transaction_meta = stellar_xdr.TransactionMeta.from_xdr( + get_transaction_data.result_meta_xdr + ) + result = transaction_meta.v3.soroban_meta.return_value + output = [] + for x in result.vec.sc_vec: + decoded_string = x.str.sc_string.decode() + output.append(decoded_string) + print(f"transaction result: {output}") +else: + print(f"Transaction failed: {get_transaction_data.result_xdr}") +``` + +```java +import org.stellar.sdk.responses.sorobanrpc.GetTransactionResponse; +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.xdr.TransactionMeta; + +public static void main(String[] args) { + SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org"); + try { + GetTransactionResponse tx = server + .getTransaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121"); + if (tx.getStatus() == GetTransactionResponse.GetTransactionStatus.SUCCESS) { + List output = new ArrayList(); + String base64Xdr = tx.getResultMetaXdr(); + // convert the string to a result + SCVal[] result = TransactionMeta.fromXdrBase64(base64Xdr).getV3() + .getSorobanMeta().getReturnValue().getVec() + .getSCVec(); + for (SCVal x : result) { + output.add(x.getStr().getSCString().toString()); + } + System.out.println("transaction result: " + output.toString()); + + } else { + System.out.println("Transaction failed: " + tx.getStatus()); + } + } catch (Exception e) { + System.err.println("An error has occurred:"); + e.printStackTrace(); + } + } +} +``` + +```php +getTransaction($txhash); + $status = $statusResponse->status; + $resultArr = []; + if ($status == GetTransactionResponse::STATUS_FAILED) { + print ("Transaction failed: " . $statusResponse->error . PHP_EOL); + } else if ($status == GetTransactionResponse::STATUS_SUCCESS) { + $resultValue = $statusResponse->getResultValue(); + $resVec = $resultValue->vec; + foreach ($resVec as $strVal) { + $resultArr[] = $strVal->str; + } + print_r($resultArr); + } + return $resultArr; + } +} +``` + + + +## Working with Complex Data Types + +As described in [this guide](/docs/learn/encyclopedia/contract-development/types/custom-types), there are some other variants of data structure supported by Soroban. They are + +- `Struct` with named fields +- `Struct` with unnamed fields +- `Enum` (Unit and Tuple Variants) +- `Enum` (Integer Variants) + +We would be looking at how these variants translate to the Spec and how to construct them in the different SDKs. + +### Struct with Named Fields + +Structs with named values when converted to ABI or Spec are represented as a `ScMap` where each value has the key in `ScSymbol` and the value in the underlying type. + + + +```rust +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct State { + pub count: u32, + pub last_incr: u32, +} +``` + +```json +[ + { + "type": "struct", + "doc": "", + "name": "State", + "fields": [ + { + "doc": "", + "name": "count", + "value": { + "type": "u32" } }, { "doc": "", - "name": "config", + "name": "last_incr", "value": { - "type": "custom", - "name": "MinterConfig" + "type": "u32" } } - ], - "outputs": [] + ] } ] ``` -This spec defines a `mint` function that takes four parameters and returns a result that's either an empty tuple or an error. +```python +from stellar_sdk import scval +scval.to_map( + { + scval.to_symbol("count"): scval.to_u32(0), + scval.to_symbol("last_incr"): scval.to_u32(0), + } +) +``` -## Interacting with Contracts +```java +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.scval.Scv; +import java.util.LinkedHashMap; -### Reading from a Contract +LinkedHashMap map = new LinkedHashMap(); +map.put(Scv.toSymbol("count"), Scv.toUint32(0)); +map.put(Scv.toSymbol("last_incr"), Scv.toUint32(0)); +SCVal val = Scv.toMap(map); +``` + +```php + XdrSCVal::forU32(0), + XdrSCVal::forSymbol("last_incr") => XdrSCVal::forU32(0), + ] +); +``` + + + +### Struct with Unnamed Fields + +Structs with Unnamed values when converted to ABI or Spec are represented as a `ScVal` where each value has the underlying type. + + + +```rust +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct State(pub u32, pub u32); +``` + +```json +[ + { + "type": "struct", + "doc": "", + "name": "State", + "fields": [ + { + "doc": "", + "value": { + "type": "u32" + } + }, + { + "doc": "", + "value": { + "type": "u32" + } + } + ] + } +] +``` ```python -from stellar_sdk import Server, Network, Keypair -from stellar_sdk.soroban import SorobanServer, Contract +from stellar_sdk import scval +scval.to_vec( + [ + scval.to_uint32(0), + scval.to_uint32(0), + ] +) +``` + +```java +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.scval.Scv; +import java.util.LinkedHashMap; -server = Server("https://soroban-testnet.stellar.org") -soroban_server = SorobanServer("https://soroban-testnet.stellar.org") -network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE +List vec = new ArrayList(); +vec.add(Scv.toUint32(0)); +vec.add(Scv.toUint32(0)); +SCVal val = Scv.toVec(vec); +``` -contract_id = "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2" -contract = Contract(contract_id) +```php + -Writing to a contract involves creating and submitting a transaction. Here's how you might call the `mint` function: +### Enum (Unit and Tuple Variants) -```python -from stellar_sdk import TransactionBuilder, Account, Asset +Enums are generally represented with `ScVec`, their unit types are represented as `ScSymbol` and their tuple variants are represented as the underlying types. -source_keypair = Keypair.from_secret("YOUR_SECRET_KEY") -source_account = server.load_account(source_keypair.public_key) + -transaction = ( - TransactionBuilder( - source_account=source_account, - network_passphrase=network_passphrase, - base_fee=100000, - ) - .append_invoke_contract_function_op( - contract_id=contract_id, - function_name="mint", - parameters=[ - Address(contract_id), - Address(source_keypair.public_key), - Address("GBCR..."), # 'to' address - ScVal.to_int128(1000000), # amount - ], - ) - .set_timeout(30) - .build() +```rust +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Enum { + A, + B(u32), +} +``` + +```json +[ + { + "type": "union", + "doc": "", + "name": "Enum", + "cases": [ + { + "doc": "", + "name": "A", + "values": [] + }, + { + "doc": "", + "name": "B", + "values": [ + { + "type": "u32" + } + ] + } + ] + } +] +``` + +```python +from stellar_sdk import scval +scval.to_vec( + [ + scval.to_symbol("A"), + scval.to_map( + { + scval.to_symbol("B"): scval.to_uint32(0), + } + ), + ] ) +``` -transaction.sign(source_keypair) -response = soroban_server.send_transaction(transaction) -print(f"Transaction response: {response}") +```java +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.scval.Scv; +import java.util.LinkedHashMap; + +List vec = new ArrayList(); +vec.add(Scv.toSymbol("A")); +LinkedHashMap map = new LinkedHashMap(); +map.put(Scv.toSymbol("B"), Scv.toUint32(0)); +vec.add(Scv.toMap(map)); +SCVal val = Scv.toVec(vec); ``` -### Handling Contract Events +```php + XdrSCVal::forU32(0), + ] + ), + ] +); +``` -Contracts can emit events that you might want to listen for and handle. Here's a basic example of how to stream events: + -```python -def process_event(event): - print(f"Received event: {event}") +### Enum (Integer Variants) + +Enums are generally represented with `ScVec`, the integer variant has no keys so its just a `ScVec` of the underlying type. -soroban_server.stream_events( - start_ledger=12345, # Replace with appropriate start ledger - contract_ids=[contract_id], - callback=process_event + + +```rust +#[contracttype] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Enum { + A = 1, + B = 2, +} +``` + +```json +[ + { + "type": "struct", + "doc": "", + "name": "Enum", + "fields": [ + { + "doc": "", + "name": "A", + "type": "u32" + }, + { + "doc": "", + "name": "B", + "type": "u32" + } + ] + } +] +``` + +```python +from stellar_sdk import scval +scval.to_vec( + [ + scval.to_uint32(0), + scval.to_uint32(0), + ] ) ``` -## Advanced Topics +```java +import org.stellar.sdk.xdr.SCVal; +import org.stellar.sdk.scval.Scv; +import java.util.LinkedHashMap; + +List vec = new ArrayList(); +vec.add(Scv.toUint32(0)); +vec.add(Scv.toUint32(0)); +SCVal val = Scv.toVec(vec); +``` + +```php + + +### A complex example + +Lets use the [timelock contract](https://github.com/stellar/soroban-examples/blob/main/timelock/src/lib.rs) example to show how to interact with a contract that has complex data types. + +This example uses a `TimeBound` struct that has a `TimeBoundKind` enum as one of its fields. which are parameters to the `deposit` function. This example combines most of the concepts we have discussed so far. + + + +```rust +#[derive(Clone)] +#[contracttype] +pub enum TimeBoundKind { + Before, + After, +} + +#[derive(Clone)] +#[contracttype] +pub struct TimeBound { + pub kind: TimeBoundKind, + pub timestamp: u64, +} + +#[contracttype] +#[contractimpl] +impl ClaimableBalanceContract { + pub fn deposit( + env: Env, + from: Address, + token: Address, + amount: i128, + claimants: Vec
, + time_bound: TimeBound, + ) {} +``` + +```json +[ + { + "type": "union", + "doc": "", + "name": "TimeBoundKind", + "cases": [ + { + "doc": "", + "name": "Before", + "values": [] + }, + { + "doc": "", + "name": "After", + "values": [] + } + ] + }, + { + "type": "struct", + "doc": "", + "name": "TimeBound", + "fields": [ + { + "doc": "", + "name": "kind", + "value": { + "type": "custom", + "name": "TimeBoundKind" + } + }, + { + "doc": "", + "name": "timestamp", + "value": { + "type": "u64" + } + } + ] + }, + { + "type": "function", + "doc": "", + "name": "deposit", + "inputs": [ + { + "doc": "", + "name": "from", + "value": { + "type": "address" + } + }, + { + "doc": "", + "name": "token", + "value": { + "type": "address" + } + }, + { + "doc": "", + "name": "amount", + "value": { + "type": "i128" + } + }, + { + "doc": "", + "name": "claimants", + "value": { + "type": "vec", + "element": { + "type": "address" + } + } + }, + { + "doc": "", + "name": "time_bound", + "value": { + "type": "custom", + "name": "TimeBound" + } + } + ], + "outputs": [] + } +] +``` -### Upgrading Contracts +```python +from stellar_sdk import scval + secret = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + rpc_server_url = "https://soroban-testnet.stellar.org:443" + contract_id = "CAIKIZOT2LXM2WBEPGTZTPHHTGVHGLEOSI4WE6BOHWIBHJOKHPMCOPLO" # + + network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + + kp = Keypair.from_secret(secret) + soroban_server = SorobanServer(rpc_server_url) + source = soroban_server.load_account(kp.public_key) + + # Let's build a transaction that invokes the `deposit` function. + tx = ( + TransactionBuilder(source, network_passphrase, base_fee=1000) + .set_timeout(300) + .append_invoke_contract_function_op( + contract_id=contract_id, + function_name="deposit", + parameters=[ + scval.to_address(kp.public_key), + scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), + scval.to_int128(1), + scval.to_vec( + [ + scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), + ] + ), + scval.to_map( + { + scval.to_symbol("kind"): scval.to_vec( + [ + scval.to_symbol("Before"), + ] + ), + scval.to_symbol("timestamp"): scval.to_uint64(12346), + } + ), + ], + ) + .build() + ) + # tx.sign(kp) + print(f"XDR: {tx.to_xdr()}") +``` -Upgrading a contract typically involves deploying a new version and updating any necessary pointers or storage. The exact process depends on how the contract was designed for upgrades. + -### Interacting with Multiple Contracts +## Reading Contract Events -When working with multiple contracts, you might need to coordinate calls between them. Always be mindful of the order of operations and potential inter-contract dependencies. +The ## Conclusion From 44e1d2e8f8a26319a86f9bcbdd9abf0e12dc56e5 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Fri, 2 Aug 2024 17:47:33 +0100 Subject: [PATCH 04/15] add examples for getting and parsing contract events --- .../working-with-contract-specs.mdx | 114 +++++++++++++++++- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index b78c654a5..2f9b58a02 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -1000,15 +1000,119 @@ from stellar_sdk import scval ## Reading Contract Events -The +Reading contract events is similar to reading transaction results. You can use the [getEvents](/docs/data/rpc/api-reference/methods/getEvents) RPC method to get the list of events associated with a contract. -## Conclusion +One common convention is that small strings like function names, enum keys and event topics are represented as `ScSymbol` in the contract spec. -This guide has covered the basics of interacting with Soroban smart contracts. Remember to always refer to the specific contract's documentation and spec for accurate interaction details. As you become more comfortable with these concepts, you'll be able to build more complex and powerful decentralized applications on the Stellar network. +However, event topics can be any scval type depending on the contract implementation. -For more detailed information, check out the [Stellar documentation](https://developers.stellar.org) and the [Soroban documentation](https://soroban.stellar.org). +In the example below, we will be encoding the mint to `ScSymbol` before querying it, and also encoding the addresses to `ScAddress`. Even after getting the event, we will need to parse the topics and value to get the actual values again from xdr base 64 to their corresponding types before then converting it to native types. + + +```rust +let address_1: &Address = "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".into(); +let address_2: &Address = "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".into(); +let count: i128 = 1; +env.events() + .publish((symbol_short!("mint"), address_1, address_2), count); ``` -This draft provides a structured guide that covers the requested points, including an introduction to contract specs, prerequisites, tutorials on interacting with contracts (reading, writing, and handling events), and touches on some advanced topics. You can expand on each section as needed, adding more code examples or detailed explanations based on your specific requirements. +```python +from stellar_sdk import SorobanServer, scval, stellar_xdr +from stellar_sdk.exceptions import NotFoundError, BadResponseError +from stellar_sdk.soroban_server import EventFilter, EventFilterType + +def get_events(): + server = SorobanServer("https://soroban-testnet.stellar.org") + # Define the request parameters + start_ledger = 835020 + contract_id = "CDLYESZILKBHBRSPKQCQ3Q4K4N6MBI6UIQR3QXJ5L6WSYXC4EMTHSNNX" + topic = [ + scval.to_symbol("mint").to_xdr(), + scval.to_address("GALIALRZJ5EU2IJJSIQEA3D3ZIEHK5HPBHZJFUEPTGQU3MYEKKIUINTY").to_xdr(), + scval.to_address("GC45QSBFYHGQUIWWQEOZ43INQGXX57CSSAABWRZ325H7MNFIFWZ56FD4").to_xdr(), + ] + + try: + # Use the get_events method directly + events_response = server.get_events( + start_ledger=start_ledger, + filters=[ + EventFilter( + event_type=EventFilterType.CONTRACT, + contract_ids=[contract_id], + topics=[topic] + ) + ], + limit=20 + ) + + # Process the response + print(f"Latest ledger: {events_response.latest_ledger}") + for event in events_response.events: + print(f"Event ID: {event.id}") + print(f"Contract ID: {event.contract_id}") + for _topic in event.topic: + if _topic is None: + continue + sc_val = stellar_xdr.SCVal.from_xdr(_topic) + if sc_val.sym is not None: + print(f"Topic: {scval.from_symbol(sc_val)}") + if sc_val.address is not None: + print(f"Topic: {scval.from_address(sc_val).address}") + if event.value is not None: + value_sc_val = stellar_xdr.SCVal.from_xdr(event.value) + if value_sc_val.i128 is not None: + print(f"Value: {scval.from_int128(value_sc_val)}") + print("---") + + except NotFoundError: + print("No events found for the given parameters.") + except BadResponseError as e: + print(f"Error occurred: {str(e)}") ``` + +```bash + +curl -X POST \ + -H 'Content-Type: application/json' \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getEvents", + "params": { + "startLedger": 1190000, + "filters": [ + { + "type": "contract", + "contractIds": [ + "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC" + ], + "topics": [ + [ + "AAAADwAAAARtaW50", + "AAAAEgAAAAAAAAAAFoAuOU9JTSEpkiBAbHvKCHV07wnyktCPmaFNswRSkUQ=", + "AAAAEgAAAAAAAAAAudhIJcHNCiLWgR2ebQ2Br378UpAAG0c710/2NKgts98=", + ] + ] + } + ], + "pagination": { + "limit": 20 + } + } + }' \ + https://soroban-testnet.stellar.org +``` + + + +## Wrapping Up + +As we've seen, working with Soroban smart contracts across different programming languages isn't rocket science, but it does require some careful attention to detail. The key takeaways: + +- Always start with a solid understanding of your contract's spec +- Get comfortable with converting between native types and Soroban's quirky data structures +- Don't be intimidated by complex data types - they're just puzzles waiting to be solved +- When in doubt, consult your SDK's documentation for language-specific nuances From eb656a324385b87cac32d6ea10116424aa8f3b41 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Fri, 2 Aug 2024 17:53:47 +0100 Subject: [PATCH 05/15] add explanation of a contract spec --- .../working-with-contract-specs.mdx | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 2f9b58a02..703951948 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -56,6 +56,154 @@ stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' stellar contract bindings json --wasm contract.wasm > abi.json ``` +## Understanding the Contract Specification + +The ABI (Application Binary Interface) specification for Soroban smart contracts includes several key components that define how to interact with the contract. Let's examine these in detail with examples: + +1. Functions: + Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract. + + Example: + ```json + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "contract", + "value": { "type": "address" } + }, + { + "name": "minter", + "value": { "type": "address" } + }, + { + "name": "to", + "value": { "type": "address" } + }, + { + "name": "amount", + "value": { "type": "i128" } + } + ], + "outputs": [ + { + "type": "result", + "value": { "type": "tuple", "elements": [] }, + "error": { "type": "error" } + } + ] + } + ``` + This defines a `mint` function that takes four parameters and returns either an empty tuple or an error. + Notice the type of each parameter: `address` for Stellar account addresses, `i128` for 128-bit integers, etc. + + +2. Structs: + Structs define complex data types with multiple fields. + + Example: + ```json + { + "type": "struct", + "name": "ClaimableBalance", + "fields": [ + { + "name": "amount", + "value": { "type": "i128" } + }, + { + "name": "claimants", + "value": { + "type": "vec", + "element": { "type": "address" } + } + }, + { + "name": "time_bound", + "value": { + "type": "custom", + "name": "TimeBound" + } + }, + { + "name": "token", + "value": { "type": "address" } + } + ] + } + ``` + This defines a `ClaimableBalance` struct with four fields. + +3. Unions: + Unions represent variables that can be one of several types. + + Example: + ```json + { + "type": "union", + "name": "DataKey", + "cases": [ + { + "name": "Init", + "values": [] + }, + { + "name": "Balance", + "values": [] + } + ] + } + ``` + This defines a `DataKey` union that can be either `Init` or `Balance`. + +4. Custom Types: + Custom types refer to other defined types in the ABI. + + Example: + ```json + { + "name": "time_bound", + "value": { + "type": "custom", + "name": "TimeBound" + } + } + ``` + This refers to a custom `TimeBound` type defined elsewhere in the ABI. + +5. Vector Types: + Vectors represent arrays of a specific type. + + Example: + ```json + { + "name": "claimants", + "value": { + "type": "vec", + "element": { "type": "address" } + } + } + ``` + This defines a vector of addresses. + +6. Primitive Types: + These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc. + + Example: + ```json + { + "name": "amount", + "value": { "type": "i128" } + } + ``` + +These specifications are crucial for encoding and decoding data when interacting with the contract. For example: + +- When calling the `mint` function, you would need to provide four parameters: three addresses and a 128-bit integer. +- If a function returns a `ClaimableBalance`, you would expect to receive a struct with an amount (i128), a vector of addresses (claimants), a TimeBound object, and an address (token). +- If a function could return an `Error`, it will most likely fail at simulation and you won't need to decode the result. + ## Soroban types Before we dive into interacting with Soroban smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: From 813c29b834d2ce47fd037bbe996449690ab5bc74 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Fri, 2 Aug 2024 17:54:14 +0100 Subject: [PATCH 06/15] format mdx --- .../working-with-contract-specs.mdx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 703951948..a8300b479 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -60,10 +60,10 @@ stellar contract bindings json --wasm contract.wasm > abi.json The ABI (Application Binary Interface) specification for Soroban smart contracts includes several key components that define how to interact with the contract. Let's examine these in detail with examples: -1. Functions: - Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract. +1. Functions: Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract. Example: + ```json { "type": "function", @@ -95,14 +95,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts ] } ``` - This defines a `mint` function that takes four parameters and returns either an empty tuple or an error. - Notice the type of each parameter: `address` for Stellar account addresses, `i128` for 128-bit integers, etc. - -2. Structs: - Structs define complex data types with multiple fields. + This defines a `mint` function that takes four parameters and returns either an empty tuple or an error. Notice the type of each parameter: `address` for Stellar account addresses, `i128` for 128-bit integers, etc. + +2. Structs: Structs define complex data types with multiple fields. Example: + ```json { "type": "struct", @@ -133,12 +132,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts ] } ``` + This defines a `ClaimableBalance` struct with four fields. -3. Unions: - Unions represent variables that can be one of several types. +3. Unions: Unions represent variables that can be one of several types. Example: + ```json { "type": "union", @@ -155,12 +155,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts ] } ``` + This defines a `DataKey` union that can be either `Init` or `Balance`. -4. Custom Types: - Custom types refer to other defined types in the ABI. +4. Custom Types: Custom types refer to other defined types in the ABI. Example: + ```json { "name": "time_bound", @@ -170,12 +171,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts } } ``` + This refers to a custom `TimeBound` type defined elsewhere in the ABI. -5. Vector Types: - Vectors represent arrays of a specific type. +5. Vector Types: Vectors represent arrays of a specific type. Example: + ```json { "name": "claimants", @@ -185,12 +187,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts } } ``` + This defines a vector of addresses. -6. Primitive Types: - These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc. +6. Primitive Types: These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc. Example: + ```json { "name": "amount", From 9d3522235abc10a4b349a71622fc01eeb7a3aa29 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Fri, 2 Aug 2024 18:09:37 +0100 Subject: [PATCH 07/15] fix url paths --- .../guides/conventions/working-with-contract-specs.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index a8300b479..4ab639064 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -40,9 +40,9 @@ This specification serves as a crucial bridge between the smart contract and cli The stellar CLI provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the WASM binary of the contract. -Sometimes you may not have access to the contract's source code or the ability to compile it. In such cases, you need to use the [`stellar contract fetch`](/docs/tools/stellar-cli#stellar-contract-fetch) command to download the contract's WASM binary and generate the spec. +Sometimes you may not have access to the contract's source code or the ability to compile it. In such cases, you need to use the [`stellar contract fetch`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-fetch) command to download the contract's WASM binary and generate the spec. -Finally, we use the [`stellar bindings`](/docs/tools/stellar-cli#stellar-contract-bindings-json) comand to generate the contract spec from the WASM binary. +Finally, we use the [`stellar bindings`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-bindings-json) comand to generate the contract spec from the WASM binary. #### Fetching the Contract Binary @@ -209,7 +209,7 @@ These specifications are crucial for encoding and decoding data when interacting ## Soroban types -Before we dive into interacting with Soroban smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: +Before we dive into interacting with Soroban smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](/docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: - `u32`: Unsigned 32-bit integer - `u64`: Unsigned 64-bit integer From 6d4c4ac64faedb0af62b08384ace46bc74c37b83 Mon Sep 17 00:00:00 2001 From: Bri <92327786+briwylde08@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:01:08 -0600 Subject: [PATCH 08/15] Editorial and formatting --- .../working-with-contract-specs.mdx | 110 +++++++++--------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 4ab639064..8be729472 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -1,13 +1,11 @@ --- -title: Work with Contract Specs in Other Languages +title: Work with contract specs in Java, Python, and PHP description: A guide to understanding and interacting with Soroban smart contracts in different programming languages. hide_table_of_contents: false --- import { CodeExample } from "@site/src/components/CodeExample"; -# Interacting with Soroban Smart Contracts - ## Introduction Soroban smart contracts are powerful tools for building decentralized applications on the Stellar network. To interact with these contracts effectively, it's crucial to understand their specifications and how to use them in your programming language of choice. @@ -25,42 +23,42 @@ These details guide how you interact with the contract, regardless of the progra Before diving into contract interactions, ensure you have the following: - Stellar CLI ([`stellar`](/docs/build/smart-contracts/getting-started/setup#install-the-stellar-cli)) installed -- A Soroban-compatible SDK for your programming language. View the [list of available SDKs](/docs/tools/sdks/library) to find one that suits your needs. -- Access to a Soroban [RPC server](/docs/learn/fundamentals/networks) (local or on a test network) +- A Soroban-compatible SDK for your programming language. View the [list of available SDKs](/docs/tools/sdks/library) to find one that suits your needs +- Access to a Stellar [RPC server](/docs/learn/fundamentals/networks) (local or on a test network) -For this Guide, we will focus on the Java, Python and PHP SDKs for reference but the concepts can be applied to other languages as well. +For this guide, we will focus on the Java, Python, and PHP SDKs for reference, but the concepts can also be applied to other languages. -## What are Contract Specs? +## What are contract specs? -A contract spec is just like an ABI (Application Binary Interface) in Ethereum, it is a standardized description of a smart contract's interface, typically in JSON format. It defines the contract's functions, data structures, events, and errors in a way that external applications can understand and use. +A contract spec is just like an ABI (Application Binary Interface) in Ethereum. It is a standardized description of a smart contract's interface, typically in JSON format. It defines the contract's functions, data structures, events, and errors in a way that external applications can understand and use. This specification serves as a crucial bridge between the smart contract and client applications, enabling them to interact without needing to know the contract's internal implementation details. -## Generating Contract Specs +## Generating contract specs -The stellar CLI provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the WASM binary of the contract. +The Stellar CLI provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the Wasm binary of the contract. -Sometimes you may not have access to the contract's source code or the ability to compile it. In such cases, you need to use the [`stellar contract fetch`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-fetch) command to download the contract's WASM binary and generate the spec. +Sometimes, you may not have access to the contract's source code or the ability to compile it. In such cases, you must use the [`stellar contract fetch`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-fetch) command to download the contract's Wasm binary and generate the spec. -Finally, we use the [`stellar bindings`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-bindings-json) comand to generate the contract spec from the WASM binary. +Finally, we use the [`stellar bindings`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-bindings-json) command to generate the contract spec from the Wasm binary. -#### Fetching the Contract Binary +#### Fetching the contract binary ```bash stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' --rpc-url https://soroban-testnet.stellar.org --id CONTRACT_ID --out-file contract.wasm ``` -#### Generating the Contract Spec from WASM +#### Generating the contract spec from Wasm ```bash stellar contract bindings json --wasm contract.wasm > abi.json ``` -## Understanding the Contract Specification +## Understanding the contract specification -The ABI (Application Binary Interface) specification for Soroban smart contracts includes several key components that define how to interact with the contract. Let's examine these in detail with examples: +The ABI (Application Binary Interface) specification for Stellar smart contracts includes several key components that define how to interact with the contract. Let's examine these in detail with examples: -1. Functions: Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract. +1. **Functions:** Functions are defined with their name, inputs, and outputs. They represent the callable methods of the contract. They can be used for writing data to the contract and reading data from the contract. Example: @@ -98,7 +96,7 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts This defines a `mint` function that takes four parameters and returns either an empty tuple or an error. Notice the type of each parameter: `address` for Stellar account addresses, `i128` for 128-bit integers, etc. -2. Structs: Structs define complex data types with multiple fields. +2. **Structs:** Structs define complex data types with multiple fields. Example: @@ -135,7 +133,7 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts This defines a `ClaimableBalance` struct with four fields. -3. Unions: Unions represent variables that can be one of several types. +3. **Unions:** Unions represent variables that can be one of several types. Example: @@ -158,7 +156,7 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts This defines a `DataKey` union that can be either `Init` or `Balance`. -4. Custom Types: Custom types refer to other defined types in the ABI. +4. **Custom Types:** Custom types refer to other defined types in the ABI. Example: @@ -174,7 +172,7 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts This refers to a custom `TimeBound` type defined elsewhere in the ABI. -5. Vector Types: Vectors represent arrays of a specific type. +5. **Vector Types:** Vectors represent arrays of a specific type. Example: @@ -190,7 +188,7 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts This defines a vector of addresses. -6. Primitive Types: These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc. +6. **Primitive Types:** These include basic types like `i128` (128-bit integer), `u64` (64-bit unsigned integer), `address`, etc. Example: @@ -203,13 +201,13 @@ The ABI (Application Binary Interface) specification for Soroban smart contracts These specifications are crucial for encoding and decoding data when interacting with the contract. For example: -- When calling the `mint` function, you would need to provide four parameters: three addresses and a 128-bit integer. +- When calling the `mint` function, you must provide four parameters: three addresses and a 128-bit integer. - If a function returns a `ClaimableBalance`, you would expect to receive a struct with an amount (i128), a vector of addresses (claimants), a TimeBound object, and an address (token). - If a function could return an `Error`, it will most likely fail at simulation and you won't need to decode the result. ## Soroban types -Before we dive into interacting with Soroban smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](/docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: +Before we dive into interacting with Stellar smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](/docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: - `u32`: Unsigned 32-bit integer - `u64`: Unsigned 64-bit integer @@ -222,27 +220,27 @@ Before we dive into interacting with Soroban smart contracts, it is important to - `vec`: Variable-length array - `address`: Stellar account address - `map`: Key-value map -- `symbol`: A small string used mainly for function name and map keys +- `symbol`: A small string used mainly for function names and map keys -In this guide and in the SDKs, these types are represented as `ScU32`, `ScU64`, `ScI32`, `ScI64`, `ScU128`, `ScI128`, `ScBool`, `ScString`, `ScVec`, `ScAddress`, `ScMap`, and `ScSymbol` respectively. +In this guide and the SDKs, these types are represented as `ScU32`, `ScU64`, `ScI32`, `ScI64`, `ScU128`, `ScI128`, `ScBool`, `ScString`, `ScVec`, `ScAddress`, `ScMap`, and `ScSymbol` respectively. -Every other complex type can be derived using these basic types but these types do not really map to values in the programming languages. The Soroban SDKs provide helper classes to work with these types. +Every other complex type can be derived using these basic types but these types do not really map to values in the programming languages. The Stellar SDKs provide helper classes to work with these types. -## Working with Native Soroban Types +## Working with native Soroban types -One of the most common tasks when working with Soroban smart contracts is converting between Soroban types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used in invoking contracts with the help of the contract spec. +One of the most common tasks when working with Soroban smart contracts is converting between Soroban types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used to invoke contracts with the help of the contract spec. -In most SDKs, the `ScVal` calss or function is used to convert between Soroban types and native types. +In most SDKs, the `ScVal` class or function is used to convert between Soroban types and native types. :::note -The JSON code block shows the contract spec while RUST code blocks show the contract for each example. +The JSON code block shows the contract spec, while RUST code blocks show the contract for each example. ::: -### 1. Invoking a Contract Function with no Parameters +### 1. Invoking a contract function with no parameters -We will be using the `increment` function of the sample [increment contract](https://github.com/stellar/soroban-examples/tree/main/increment) contract to exemplify this. The `increment` function takes no parameters and increments the counter by 1. +We will be using the `increment` function of the sample [increment contract](https://github.com/stellar/soroban-examples/tree/main/increment) to exemplify this. The `increment` function takes no parameters and increments the counter by 1. In this scenario, there is no need for conversions and passing the value `null` as contract arguments is sufficient in most SDKs. @@ -413,17 +411,17 @@ class Invoker :::info -subsequent examples will show code blocks for using the contract spec only to reduce redundancy +Subsequent examples will show code blocks for using the contract spec only to reduce redundancy. ::: -### 2. Invoking a Contract Function with one or more Parameters +### 2. Invoking a contract function with one or more parameters Generally, this involves passing in a native `array` (not a `ScVec`) of parameters to the contract function. -We will be using the `hello` function of the sample [Hello World Contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) contract to exemplify this. +We will be using the `hello` function of the sample [Hello World contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) to exemplify this. -We know from the Spec that the `hello` function takes a string parameter and returns a vector of strings. In this scenario, we need to convert the string parameter to a `ScString` type before passing it to the contract. +We know from the spec that the `hello` function takes a string parameter and returns a vector of strings. In this scenario, we need to convert the string parameter to a `ScString` type before passing it to the contract. This process is convenient using the `ScVal` class or function in most SDKs. @@ -509,11 +507,11 @@ Transaction tx = transaction.addOperation(operation).build(); -### 3. Getting Responses from Contracts +### 3. Getting responses from contracts -Data returned from contracts are also in `ScVal` format and need to be converted to native types in your programming language. +Data returned from contracts is also in `ScVal` format and need to be converted to native types in your programming language. -We will still be using the `hello` function of the sample [Hello World Contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) contract to exemplify this. +We will still be using the `hello` function of the sample [Hello World contract](https://github.com/stellar/soroban-examples/tree/main/hello_world) to exemplify this. We know from the Spec that the `hello` function takes a string parameter and returns a vec of strings. In this scenario, we need to convert the value returned from an `ScVec` of `ScString` type to `array` of `string` before making use of it. @@ -525,7 +523,7 @@ Steps: This process is convenient using the `ScVal` class or function in most SDKs. -Ideally, to retrieve this value, we need to use the [getTransaction](/docs/data/rpc/api-reference/methods/getTransaction) RPC method using the response hash of the transaction that invoked the contract function. +Ideally, to retrieve this value, we need to use the [`getTransaction`](/docs/data/rpc/api-reference/methods/getTransaction) RPC method using the response hash of the transaction that invoked the contract function. @@ -657,7 +655,7 @@ class Test { -## Working with Complex Data Types +## Working with complex data types As described in [this guide](/docs/learn/encyclopedia/contract-development/types/custom-types), there are some other variants of data structure supported by Soroban. They are @@ -666,11 +664,11 @@ As described in [this guide](/docs/learn/encyclopedia/contract-development/types - `Enum` (Unit and Tuple Variants) - `Enum` (Integer Variants) -We would be looking at how these variants translate to the Spec and how to construct them in the different SDKs. +We would be looking at how these variants translate to the spec and how to construct them in the different SDKs. -### Struct with Named Fields +### Struct with named fields -Structs with named values when converted to ABI or Spec are represented as a `ScMap` where each value has the key in `ScSymbol` and the value in the underlying type. +Structs with named values when converted to ABI or spec are represented as a `ScMap` where each value has the key in `ScSymbol` and the value in the underlying type. @@ -744,9 +742,9 @@ XdrSCVal::forMap( -### Struct with Unnamed Fields +### Struct with unnamed fields -Structs with Unnamed values when converted to ABI or Spec are represented as a `ScVal` where each value has the underlying type. +Structs with unnamed values when converted to ABI or spec are represented as a `ScVal` where each value has the underlying type. @@ -815,7 +813,7 @@ XdrSCVal::forVec( -### Enum (Unit and Tuple Variants) +### Enum (unit and tuple variants) Enums are generally represented with `ScVec`, their unit types are represented as `ScSymbol` and their tuple variants are represented as the underlying types. @@ -901,9 +899,9 @@ XdrSCVal::forVec( -### Enum (Integer Variants) +### Enum (integer variants) -Enums are generally represented with `ScVec`, the integer variant has no keys so its just a `ScVec` of the underlying type. +Enums are generally represented with `ScVec`, the integer variant has no keys so it's just a `ScVec` of the underlying type. @@ -976,9 +974,9 @@ XdrSCVal::forVec( ### A complex example -Lets use the [timelock contract](https://github.com/stellar/soroban-examples/blob/main/timelock/src/lib.rs) example to show how to interact with a contract that has complex data types. +Let's use the [timelock contract](https://github.com/stellar/soroban-examples/blob/main/timelock/src/lib.rs) example to show how to interact with a contract that has complex data types. -This example uses a `TimeBound` struct that has a `TimeBoundKind` enum as one of its fields. which are parameters to the `deposit` function. This example combines most of the concepts we have discussed so far. +This example uses a `TimeBound` struct that has a `TimeBoundKind` enum as one of its fields, which are parameters to the `deposit` function. This example combines most of the concepts we have discussed so far. @@ -1149,13 +1147,13 @@ from stellar_sdk import scval -## Reading Contract Events +## Reading contract events -Reading contract events is similar to reading transaction results. You can use the [getEvents](/docs/data/rpc/api-reference/methods/getEvents) RPC method to get the list of events associated with a contract. +Reading contract events is similar to reading transaction results. You can use the [`getEvents`](/docs/data/rpc/api-reference/methods/getEvents) RPC method to get the list of events associated with a contract. -One common convention is that small strings like function names, enum keys and event topics are represented as `ScSymbol` in the contract spec. +One common convention is that small strings like function names, enum keys, and event topics are represented as `ScSymbol` in the contract spec. -However, event topics can be any scval type depending on the contract implementation. +However, event topics can be any `scval` type depending on the contract implementation. In the example below, we will be encoding the mint to `ScSymbol` before querying it, and also encoding the addresses to `ScAddress`. Even after getting the event, we will need to parse the topics and value to get the actual values again from xdr base 64 to their corresponding types before then converting it to native types. From acd0af24f3aa51d36a6fe35ee19d35152ede7d61 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Tue, 6 Aug 2024 19:06:47 +0100 Subject: [PATCH 09/15] add support for rust and PHP in code examples --- src/constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index dc6bc3097..6d33dcfa5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,6 +6,8 @@ export const CODE_LANGS = { flutter: 'Flutter', docker: 'Dockerfile', go: 'Go', + rust: 'Rust', + php: 'PHP', html: 'html', kotlin: 'Kotlin', kt: 'Kotlin', From 0a255c46e7e15167056abbfec374fa5d5a810196 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Tue, 6 Aug 2024 19:13:41 +0100 Subject: [PATCH 10/15] fix code samples --- .../working-with-contract-specs.mdx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 8be729472..f19bd7e00 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -469,17 +469,16 @@ impl HelloContract { ```python from stellar_sdk import Keypair, Network, SorobanServer, TransactionBuilder, scval ..... - tx = ( - TransactionBuilder(source, network_passphrase, base_fee=100) - .set_timeout(300) - .append_invoke_contract_function_op( - contract_id=contract_id, - function_name="hello", - parameters=[ - scval.to_string("John"), - ], - ) - .build() +tx = ( + TransactionBuilder(source, network_passphrase, base_fee=100) + .set_timeout(300) + .append_invoke_contract_function_op( + contract_id=contract_id, + function_name="hello", + parameters=[ + scval.to_string("John"), + ] +).build() ``` ```java From 5ff3e2758583e9bbbc95910484050dbbf7f89f90 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Tue, 6 Aug 2024 19:16:53 +0100 Subject: [PATCH 11/15] fix merge conflicts --- src/constants.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 6d33dcfa5..730bd1885 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,8 +6,6 @@ export const CODE_LANGS = { flutter: 'Flutter', docker: 'Dockerfile', go: 'Go', - rust: 'Rust', - php: 'PHP', html: 'html', kotlin: 'Kotlin', kt: 'Kotlin', @@ -19,15 +17,17 @@ export const CODE_LANGS = { python: 'Python', scss: 'SCSS', sql: 'SQL', + rust: 'Rust', + php: 'PHP', toml: 'TOML', ts: 'TypeScript', tsx: 'TSX', typescript: 'TypeScript', yaml: 'YAML', -}; - -export const FUTURENET_DETAILS = { + }; + + export const FUTURENET_DETAILS = { network: "FUTURENET", networkUrl: "https://horizon-futurenet.stellar.org", networkPassphrase: "Test SDF Future Network ; October 2022", - }; + }; \ No newline at end of file From 0f3070efb32de5162813feb51562e463c72fcc83 Mon Sep 17 00:00:00 2001 From: Elliot Voris Date: Mon, 12 Aug 2024 10:37:25 -0500 Subject: [PATCH 12/15] fix some markdown link syntax --- .../working-with-contract-specs.mdx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index f19bd7e00..10f91c865 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -22,9 +22,9 @@ These details guide how you interact with the contract, regardless of the progra Before diving into contract interactions, ensure you have the following: -- Stellar CLI ([`stellar`](/docs/build/smart-contracts/getting-started/setup#install-the-stellar-cli)) installed -- A Soroban-compatible SDK for your programming language. View the [list of available SDKs](/docs/tools/sdks/library) to find one that suits your needs -- Access to a Stellar [RPC server](/docs/learn/fundamentals/networks) (local or on a test network) +- Stellar CLI ([`stellar`](../../smart-contracts/getting-started/setup.mdx#install-the-stellar-cli)) installed +- A Soroban-compatible SDK for your programming language. View the [list of available SDKs](../../../tools/sdks/library.mdx) to find one that suits your needs +- Access to a Stellar [RPC server](../../../learn/fundamentals/networks.mdx) (local or on a test network) For this guide, we will focus on the Java, Python, and PHP SDKs for reference, but the concepts can also be applied to other languages. @@ -38,17 +38,17 @@ This specification serves as a crucial bridge between the smart contract and cli The Stellar CLI provides a command to generate a contract spec from a contract's source code. This process is easy but requires you to have the Wasm binary of the contract. -Sometimes, you may not have access to the contract's source code or the ability to compile it. In such cases, you must use the [`stellar contract fetch`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-fetch) command to download the contract's Wasm binary and generate the spec. +Sometimes, you may not have access to the contract's source code or the ability to compile it. In such cases, you must use the [`stellar contract fetch`](../../../tools/developer-tools/cli/stellar-cli.mdx#stellar-contract-fetch) command to download the contract's Wasm binary and generate the spec. -Finally, we use the [`stellar bindings`](/docs/tools/developer-tools/cli/stellar-cli#stellar-contract-bindings-json) command to generate the contract spec from the Wasm binary. +Finally, we use the [`stellar bindings`](../../../tools/developer-tools/cli/stellar-cli.mdx#stellar-contract-bindings-json) command to generate the contract spec from the Wasm binary. -#### Fetching the contract binary +### Fetching the contract binary ```bash stellar contract fetch --network-passphrase 'Test SDF Network ; September 2015' --rpc-url https://soroban-testnet.stellar.org --id CONTRACT_ID --out-file contract.wasm ``` -#### Generating the contract spec from Wasm +### Generating the contract spec from Wasm ```bash stellar contract bindings json --wasm contract.wasm > abi.json @@ -207,7 +207,7 @@ These specifications are crucial for encoding and decoding data when interacting ## Soroban types -Before we dive into interacting with Stellar smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](/docs/learn/encyclopedia/contract-development/types/built-in-types). Here are some of the common types: +Before we dive into interacting with Stellar smart contracts, it is important to note that Soroban has its own set of types that are used to interact with the contracts as described in [this guide](../../../learn/encyclopedia/contract-development/types/built-in-types.mdx). Here are some of the common types: - `u32`: Unsigned 32-bit integer - `u64`: Unsigned 64-bit integer @@ -522,7 +522,7 @@ Steps: This process is convenient using the `ScVal` class or function in most SDKs. -Ideally, to retrieve this value, we need to use the [`getTransaction`](/docs/data/rpc/api-reference/methods/getTransaction) RPC method using the response hash of the transaction that invoked the contract function. +Ideally, to retrieve this value, we need to use the [`getTransaction`](../../../data/rpc/api-reference/methods/getTransaction.mdx) RPC method using the response hash of the transaction that invoked the contract function. @@ -656,7 +656,7 @@ class Test { ## Working with complex data types -As described in [this guide](/docs/learn/encyclopedia/contract-development/types/custom-types), there are some other variants of data structure supported by Soroban. They are +As described in [this guide](../../../learn/encyclopedia/contract-development/types/custom-types.mdx), there are some other variants of data structure supported by Soroban. They are - `Struct` with named fields - `Struct` with unnamed fields @@ -1148,7 +1148,7 @@ from stellar_sdk import scval ## Reading contract events -Reading contract events is similar to reading transaction results. You can use the [`getEvents`](/docs/data/rpc/api-reference/methods/getEvents) RPC method to get the list of events associated with a contract. +Reading contract events is similar to reading transaction results. You can use the [`getEvents`](../../../data/rpc/api-reference/methods/getEvents.mdx) RPC method to get the list of events associated with a contract. One common convention is that small strings like function names, enum keys, and event topics are represented as `ScSymbol` in the contract spec. From 634f57bc5bbe6fcf05e3bd31b7cdb18cd1880053 Mon Sep 17 00:00:00 2001 From: Elliot Voris Date: Mon, 12 Aug 2024 10:51:08 -0500 Subject: [PATCH 13/15] add PHP language to code highlighting languages --- docusaurus.config.ts | 1 + src/constants.ts | 52 ++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index e5a442107..be6a256af 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -394,6 +394,7 @@ const config: Config = { "log", "nginx", "powershell", + "php", "python", "rust", "scala", diff --git a/src/constants.ts b/src/constants.ts index c537d4975..d218b4890 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,30 +1,30 @@ export const CODE_LANGS = { - bash: 'bash', - cpp: 'C++', - curl: 'cURL', - dart: 'Flutter', - flutter: 'Flutter', - docker: 'Dockerfile', - go: 'Go', - html: 'html', - kotlin: 'Kotlin', - kt: 'Kotlin', - java: 'Java', - javascript: 'JavaScript', - js: 'JavaScript', - json: 'JSON', - json5: 'JSON5', - python: 'Python', - scss: 'SCSS', - sql: 'SQL', - rust: 'Rust', - php: 'PHP', - toml: 'TOML', - ts: 'TypeScript', - tsx: 'TSX', - typescript: 'TypeScript', - yaml: 'YAML', - }; + bash: 'bash', + cpp: 'C++', + curl: 'cURL', + dart: 'Flutter', + flutter: 'Flutter', + docker: 'Dockerfile', + go: 'Go', + html: 'html', + kotlin: 'Kotlin', + kt: 'Kotlin', + java: 'Java', + javascript: 'JavaScript', + js: 'JavaScript', + json: 'JSON', + json5: 'JSON5', + python: 'Python', + scss: 'SCSS', + sql: 'SQL', + rust: 'Rust', + php: 'PHP', + toml: 'TOML', + ts: 'TypeScript', + tsx: 'TSX', + typescript: 'TypeScript', + yaml: 'YAML', +}; export const FUTURENET_DETAILS = { network: "FUTURENET", From 1b97da61d284ec3fdee5fa2c910d3f6d85e84626 Mon Sep 17 00:00:00 2001 From: Elliot Voris Date: Mon, 12 Aug 2024 10:51:44 -0500 Subject: [PATCH 14/15] fine-tune code examples a bit --- .../working-with-contract-specs.mdx | 168 +++++++++--------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 10f91c865..154a630e9 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -313,9 +313,9 @@ def send_transaction() -> soroban_rpc.SendTransactionResponse: response = send_transaction() print("status", response.status) -print("hash:", response.hash); -print("status:", response.status); -print("errorResultXdr:", response.error_result_xdr); +print("hash:", response.hash) +print("status:", response.status) +print("errorResultXdr:", response.error_result_xdr) ``` ```java @@ -363,7 +363,7 @@ public class SendTransactionExample { ``` ```php -build(); - - $transaction = (new TransactionBuilder($accountA)) - ->addOperation($op)->build(); +$arg = \Soneso\StellarSDK\Xdr\XdrSCVal::forString("John"); +$invokeContractHostFunction = new InvokeContractHostFunction($contractId, "hello", [$arg]); +$builder = new InvokeHostFunctionOperationBuilder($invokeContractHostFunction); +$op = $builder->build(); +$transaction = (new TransactionBuilder($accountA)) + ->addOperation($op)->build(); ``` @@ -597,29 +596,28 @@ import org.stellar.sdk.xdr.SCVal; import org.stellar.sdk.xdr.TransactionMeta; public static void main(String[] args) { - SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org"); - try { - GetTransactionResponse tx = server - .getTransaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121"); - if (tx.getStatus() == GetTransactionResponse.GetTransactionStatus.SUCCESS) { - List output = new ArrayList(); - String base64Xdr = tx.getResultMetaXdr(); - // convert the string to a result - SCVal[] result = TransactionMeta.fromXdrBase64(base64Xdr).getV3() - .getSorobanMeta().getReturnValue().getVec() - .getSCVec(); - for (SCVal x : result) { - output.add(x.getStr().getSCString().toString()); - } - System.out.println("transaction result: " + output.toString()); - - } else { - System.out.println("Transaction failed: " + tx.getStatus()); + SorobanServer server = new SorobanServer("https://soroban-testnet.stellar.org"); + try { + GetTransactionResponse tx = server + .getTransaction("7e47c6ba2ebe53e156bc50c48e34302d49c91c04c465e8cd2b8a25219c2c8121"); + if (tx.getStatus() == GetTransactionResponse.GetTransactionStatus.SUCCESS) { + List output = new ArrayList(); + String base64Xdr = tx.getResultMetaXdr(); + // convert the string to a result + SCVal[] result = TransactionMeta.fromXdrBase64(base64Xdr).getV3() + .getSorobanMeta().getReturnValue().getVec() + .getSCVec(); + for (SCVal x : result) { + output.add(x.getStr().getSCString().toString()); } - } catch (Exception e) { - System.err.println("An error has occurred:"); - e.printStackTrace(); + System.out.println("transaction result: " + output.toString()); + + } else { + System.out.println("Transaction failed: " + tx.getStatus()); } + } catch (Exception e) { + System.err.println("An error has occurred:"); + e.printStackTrace(); } } ``` @@ -804,8 +802,8 @@ use Soneso\StellarSDK\Xdr\XdrSCVal; XdrSCVal::forVec( [ - XdrSCVal::forU32(0), - XdrSCVal::forU32(0), + XdrSCVal::forU32(0), + XdrSCVal::forU32(0), ] ); ``` @@ -886,12 +884,12 @@ use Soneso\StellarSDK\Xdr\XdrSCVal; XdrSCVal::forVec( [ - XdrSCVal::forSymbol("A"), - XdrSCVal::forMap( - [ - XdrSCVal::forSymbol("B") => XdrSCVal::forU32(0), - ] - ), + XdrSCVal::forSymbol("A"), + XdrSCVal::forMap( + [ + XdrSCVal::forSymbol("B") => XdrSCVal::forU32(0), + ] + ), ] ); ``` @@ -963,8 +961,8 @@ use Soneso\StellarSDK\Xdr\XdrSCVal; XdrSCVal::forVec( [ - XdrSCVal::forU32(0), - XdrSCVal::forU32(0), + XdrSCVal::forU32(0), + XdrSCVal::forU32(0), ] ); ``` @@ -1005,6 +1003,7 @@ impl ClaimableBalanceContract { claimants: Vec
, time_bound: TimeBound, ) {} +} ``` ```json @@ -1100,48 +1099,48 @@ impl ClaimableBalanceContract { ```python from stellar_sdk import scval - secret = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - rpc_server_url = "https://soroban-testnet.stellar.org:443" - contract_id = "CAIKIZOT2LXM2WBEPGTZTPHHTGVHGLEOSI4WE6BOHWIBHJOKHPMCOPLO" # - - network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE - - kp = Keypair.from_secret(secret) - soroban_server = SorobanServer(rpc_server_url) - source = soroban_server.load_account(kp.public_key) - - # Let's build a transaction that invokes the `deposit` function. - tx = ( - TransactionBuilder(source, network_passphrase, base_fee=1000) - .set_timeout(300) - .append_invoke_contract_function_op( - contract_id=contract_id, - function_name="deposit", - parameters=[ - scval.to_address(kp.public_key), - scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), - scval.to_int128(1), - scval.to_vec( - [ - scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), - ] - ), - scval.to_map( - { - scval.to_symbol("kind"): scval.to_vec( - [ - scval.to_symbol("Before"), - ] - ), - scval.to_symbol("timestamp"): scval.to_uint64(12346), - } - ), - ], - ) - .build() +secret = "SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +rpc_server_url = "https://soroban-testnet.stellar.org:443" +contract_id = "CAIKIZOT2LXM2WBEPGTZTPHHTGVHGLEOSI4WE6BOHWIBHJOKHPMCOPLO" + +network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE + +kp = Keypair.from_secret(secret) +soroban_server = SorobanServer(rpc_server_url) +source = soroban_server.load_account(kp.public_key) + +# Let's build a transaction that invokes the `deposit` function. +tx = ( + TransactionBuilder(source, network_passphrase, base_fee=1000) + .set_timeout(300) + .append_invoke_contract_function_op( + contract_id=contract_id, + function_name="deposit", + parameters=[ + scval.to_address(kp.public_key), + scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), + scval.to_int128(1), + scval.to_vec( + [ + scval.to_address("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), + ] + ), + scval.to_map( + { + scval.to_symbol("kind"): scval.to_vec( + [ + scval.to_symbol("Before"), + ] + ), + scval.to_symbol("timestamp"): scval.to_uint64(12346), + } + ), + ], ) - # tx.sign(kp) - print(f"XDR: {tx.to_xdr()}") + .build() +) +# tx.sign(kp) +print(f"XDR: {tx.to_xdr()}") ``` @@ -1222,7 +1221,6 @@ def get_events(): ``` ```bash - curl -X POST \ -H 'Content-Type: application/json' \ -d '{ From 0df6e0f3cb42db26ae73d71c9c3e9006518c593b Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Mon, 12 Aug 2024 17:39:16 +0100 Subject: [PATCH 15/15] resolve PR comments --- .../guides/conventions/working-with-contract-specs.mdx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/build/guides/conventions/working-with-contract-specs.mdx b/docs/build/guides/conventions/working-with-contract-specs.mdx index 154a630e9..8f5fcb49d 100644 --- a/docs/build/guides/conventions/working-with-contract-specs.mdx +++ b/docs/build/guides/conventions/working-with-contract-specs.mdx @@ -1,7 +1,6 @@ --- title: Work with contract specs in Java, Python, and PHP description: A guide to understanding and interacting with Soroban smart contracts in different programming languages. -hide_table_of_contents: false --- import { CodeExample } from "@site/src/components/CodeExample"; @@ -26,7 +25,7 @@ Before diving into contract interactions, ensure you have the following: - A Soroban-compatible SDK for your programming language. View the [list of available SDKs](../../../tools/sdks/library.mdx) to find one that suits your needs - Access to a Stellar [RPC server](../../../learn/fundamentals/networks.mdx) (local or on a test network) -For this guide, we will focus on the Java, Python, and PHP SDKs for reference, but the concepts can also be applied to other languages. +For this guide, we will focus on the [Java](/docs/tools/sdks/library#java-sdk), [Python](/docs/tools/sdks/library#python-sdk), and [PHP](/docs/tools/sdks/library#php-sdk) SDKs for reference, but the concepts can also be applied to other languages. ## What are contract specs? @@ -228,7 +227,7 @@ Every other complex type can be derived using these basic types but these types ## Working with native Soroban types -One of the most common tasks when working with Soroban smart contracts is converting between Soroban types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used to invoke contracts with the help of the contract spec. +One of the most common tasks when working with Stellar smart contracts is converting between Stellar smart contract types and native types in your programming language. In this guide, we will go over some common conversions and show how they can be used to invoke contracts with the help of the contract spec. In most SDKs, the `ScVal` class or function is used to convert between Soroban types and native types. @@ -1139,8 +1138,6 @@ tx = ( ) .build() ) -# tx.sign(kp) -print(f"XDR: {tx.to_xdr()}") ```