Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test_mock_and_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
cd ../readEvents && go build
cd ../simpleCall && go build
cd ../simpleDeclare && go build
cd ../paymaster && go build
cd ../typedData && go build
cd ../websocket && go build

Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/test_paymaster.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: paymaster integration tests

permissions:
contents: read

on:
push:
branches:
- main

jobs:
run:
runs-on: ubuntu-22.04

steps:
- name: Checkout branch
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod

- name: Run paymaster tests
run: cd paymaster && go test -timeout 300s -v -env integration .
env:
STARKNET_PRIVATE_KEY: ${{ secrets.TESTNET_ACCOUNT_PRIVATE_KEY }}
STARKNET_PUBLIC_KEY: ${{ secrets.TESTNET_ACCOUNT_PUBLIC_KEY }}
STARKNET_ACCOUNT_ADDRESS: ${{ secrets.TESTNET_ACCOUNT_ADDRESS }}
AVNU_API_KEY: ${{ secrets.AVNU_API_KEY }}
5 changes: 5 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ linters:
- gosec
- lll
- mnd
- funlen # it's ok for the examples to have big main() functions
path: examples/
- linters:
- gocritic #commentedOutCode: "curve.SignFelts..." is there just as an explanation
- unused # it's part of the example to have two unused functions
path: examples/paymaster/
- linters:
- exhaustruct
- noctx
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ operations on the wallets. The package has excellent documentation for a smooth
- [invoke transaction example](./examples/invoke) to add a new invoke transaction on testnet.
- [declare transaction example](./examples/simpleDeclare) to add a new contract on testnet.
- [deploy contract UDC example](./examples/deployContractUDC) to deploy an ERC20 token using [UDC (Universal Deployer Contract)](https://docs.openzeppelin.com/contracts-cairo/1.0.0/udc) on testnet.
- [paymaster example](./examples/paymaster) to learn how to interact with a paymaster and send transactions with it.
- [typed data example](./examples/typedData) to sign and verify a typed data.
- [websocket example](./examples/websocket) to learn how to subscribe to WebSocket methods.

Expand Down
5 changes: 4 additions & 1 deletion examples/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
#ACCOUNT_ADDRESS=0xyour_account_address
#PUBLIC_KEY=0xyour_starknet_public_key
#PRIVATE_KEY=0xyour_private_key
#ACCOUNT_CAIRO_VERSION=2
#ACCOUNT_CAIRO_VERSION=2

# ----- use this variable for specific cases in the paymaster example
#AVNU_API_KEY=your-api-key
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ To run an example:
R: See [typedData](./typedData/main.go).
1. How to use WebSocket methods? How to subscribe, unsubscribe, handle errors, and read values from them?
R: See [websocket](./websocket/main.go).
1. How to interact with a paymaster? How to send transactions with it?
R: See [paymaster](./paymaster/main.go).
5 changes: 5 additions & 0 deletions examples/internal/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func GetAccountCairoVersion() account.CairoVersion {
}
}

// Validates whether the AVNU_API_KEY variable has been set in the '.env' file and returns it; panics otherwise.
func GetAVNUApiKey() string {
return getEnv("AVNU_API_KEY")
}

// Loads an env variable by name and returns it; panics otherwise.
func getEnv(envName string) string {
env := os.Getenv(envName)
Expand Down
22 changes: 22 additions & 0 deletions examples/paymaster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
This example demonstrates how to send transactions on Starknet with a paymaster using the Starkent.go SNIP-29 implementation, allowing you to pay fees with tokens other than STRK.
It has three files: main.go, deploy.go, and deploy_and_invoke.go.

The main.go file shows how to send an invoke transaction using a paymaster with the "default" fee mode, where you pay fees using supported tokens (like STRK). It demonstrates the complete 3-step process: building the transaction via the paymaster, signing it with your account, and executing the transaction.

The deploy.go file demonstrates how to deploy a new account using a paymaster with the "sponsored" fee mode, where an entity covers the transaction fees.
The deploy_and_invoke.go file shows how to deploy an account and invoke a function in the same transaction using a paymaster, combining both deployment and execution in a single request.
Both of these `deploy...`examples require a valid paymaster API key.

All examples demonstrate integration with the AVNU paymaster service and require SNIP-9 compatible accounts.

Steps:
1. Rename the ".env.template" file located at the root of the "examples" folder to ".env"
2. Uncomment, and assign your Sepolia testnet endpoint to the `RPC_PROVIDER_URL` variable in the ".env" file
3. Uncomment, and assign your SNIP-9 compatible account address to the `ACCOUNT_ADDRESS` variable in the ".env" file (make sure to have some STRK tokens in it)
4. Uncomment, and assign your starknet public key to the `PUBLIC_KEY` variable in the ".env" file
5. Uncomment, and assign your private key to the `PRIVATE_KEY` variable in the ".env" file
6. Make sure you are in the "paymaster" directory
7. Execute `go run .` to run the basic paymaster invoke example
8. To run the deploy examples (requires API key), uncomment the function calls at the end of main.go and execute again. Also, uncomment, and assign your paymaster API key to the `AVNU_API_KEY` variable in the ".env" file

The transaction hashes, tracking IDs, and execution status will be returned at the end of each example.
133 changes: 133 additions & 0 deletions examples/paymaster/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package main

import (
"context"
"fmt"

"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/account"
"github.com/NethermindEth/starknet.go/client"
setup "github.com/NethermindEth/starknet.go/examples/internal"
"github.com/NethermindEth/starknet.go/internal/utils"
pm "github.com/NethermindEth/starknet.go/paymaster"
)

// OpenZeppelin account class hash that supports outside executions
const OZAccountClassHash = "0x05b4b537eaa2399e3aa99c4e2e0208ebd6c71bc1467938cd52c798c601e43564"

// An example of how to deploy a contract with a paymaster.
func deployWithPaymaster() {
fmt.Println("Starting paymaster example - deploying an account")

// Load variables from '.env' file
AVNUApiKey := setup.GetAVNUApiKey()

// Since all accounts in Starknet are smart contracts, we need to deploy them first before we can use them.
// And to do so, we need to calculate the address of the new account and fund it with
// enough STRK tokens before deploying it. This tokens will be used to pay the fees for the `deploy` txn.
//
// Deploy an account with a paymaster using the `default` fee mode doesn't make much sense, as we will
// need to send some tokens for the account anyway. So, we will use the `sponsored` fee mode now,
// which will allow the paymaster to fully cover the fees for the `deploy` txn. This mode requires
// an API key from an entity. You can only run this example with it.

// Let's initialise the paymaster client, but now, we will also pass our API key to the client.
// In the AVNU paymaster, the API key is a http header called `x-paymaster-api-key`.
// In the current Starknet.go client, you can set a custom http header using the `client.WithHeader` option.
paymaster, err := pm.New(
context.Background(),
AVNUPaymasterURL,
client.WithHeader("x-paymaster-api-key", AVNUApiKey),
)
if err != nil {
panic(fmt.Errorf("error connecting to the paymaster provider with the API key: %w", err))
}

fmt.Println("Established connection with the paymaster provider")
fmt.Print("Step 1: Build the deploy transaction\n\n")

// First, let's get all the data we need for deploy an account.
_, pubKey, privK := account.GetRandomKeys() // Get random keys for the account
fmt.Println("Public key:", pubKey)
fmt.Println("Private key:", privK)
classHash, _ := utils.HexToFelt(
OZAccountClassHash,
) // It needs to be an SNIP-9 compatible account
constructorCalldata := []*felt.Felt{
pubKey,
} // The OZ account constructor requires the public key
salt, _ := utils.HexToFelt("0xdeadbeef") // Just a random salt
// Precompute the address of the new account based on the salt, class hash and constructor calldata
precAddress := account.PrecomputeAccountAddress(salt, classHash, constructorCalldata)

fmt.Println("Precomputed address:", precAddress)

// Now we can create the deploy data for the transaction.
deployData := &pm.AccountDeploymentData{
Address: precAddress, // The precomputed address of the new account
ClassHash: classHash,
Salt: salt,
Calldata: constructorCalldata,
SignatureData: []*felt.Felt{}, // Optional. For the OZ account, we don't need to add anything in the signature data.
Version: pm.Cairo1,
}

// With the deploy data, we can build the transaction by calling the `paymaster_buildTransaction` method.
// REMEMBER: this will only work if you have a valid API key configured.
//
// A full explanation about the paymaster_buildTransaction method can be found in the `main.go` file of this same example.
builtTxn, err := paymaster.BuildTransaction(context.Background(), &pm.BuildTransactionRequest{
Transaction: pm.UserTransaction{
Type: pm.UserTxnDeploy, // we are building an `deploy` transaction
Deployment: deployData,
},
Parameters: pm.UserParameters{
Version: pm.UserParamV1,
FeeMode: pm.FeeMode{
Mode: pm.FeeModeSponsored, // We then set the fee mode to `sponsored`
Tip: &pm.TipPriority{
Priority: pm.TipPriorityNormal,
},
},
},
})
if err != nil {
panic(fmt.Errorf("error building the deploy transaction: %w", err))
}
fmt.Println("Transaction successfully built by the paymaster")
PrettyPrint(builtTxn)

// Since we are deploying an account, we don't need to sign the transaction, just execute it.

fmt.Println("Step 2: Send the signed transaction")

// With our built deploy transaction, we can send it to the paymaster by calling the `paymaster_executeTransaction` method.
response, err := paymaster.ExecuteTransaction(
context.Background(),
&pm.ExecuteTransactionRequest{
Transaction: pm.ExecutableUserTransaction{
Type: pm.UserTxnDeploy,
Deployment: builtTxn.Deployment, // The deployment data is the same. We can use our `deployData` variable, or
// the `builtTxn.Deployment` value.
},
Parameters: pm.UserParameters{
Version: pm.UserParamV1,

// Using the same fee options as in the `paymaster_buildTransaction` method.
FeeMode: pm.FeeMode{
Mode: pm.FeeModeSponsored,
Tip: &pm.TipPriority{
Priority: pm.TipPriorityNormal,
},
},
},
},
)
if err != nil {
panic(fmt.Errorf("error executing the deploy transaction with the paymaster: %w", err))
}

fmt.Println("Deploy transaction successfully executed by the paymaster")
fmt.Println("Tracking ID:", response.TrackingID)
fmt.Println("Transaction Hash:", response.TransactionHash)
}
Loading
Loading