Skip to content

Commit a295b43

Browse files
authored
Paymaster PR 5: examples + CI workflow (#809)
* feat: add a new paymaster example to demonstrate interaction with paymaster and transaction sending. - Updated README files to include references to the new paymaster example. - Modified linter configurations in .golangci.yaml to include gocritic and unused linters for the paymaster example. - Introduced a new function in setup.go to validate the AVNU_API_KEY environment variable. * ci: add tests for the paymaster in the CI workflow * ci: update GitHub Actions to use latest versions of checkout and setup-go * docs: update README for paymaster example to include SNIP-29 link
1 parent f0e51db commit a295b43

File tree

11 files changed

+668
-1
lines changed

11 files changed

+668
-1
lines changed

.github/workflows/test_mock_and_examples.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
cd ../readEvents && go build
3030
cd ../simpleCall && go build
3131
cd ../simpleDeclare && go build
32+
cd ../paymaster && go build
3233
cd ../typedData && go build
3334
cd ../websocket && go build
3435
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: paymaster integration tests
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
run:
13+
runs-on: ubuntu-22.04
14+
15+
steps:
16+
- name: Checkout branch
17+
uses: actions/checkout@v5
18+
19+
- name: Set up Go
20+
uses: actions/setup-go@v6
21+
with:
22+
go-version-file: go.mod
23+
24+
- name: Run paymaster tests
25+
run: cd paymaster && go test -timeout 300s -v -env integration .
26+
env:
27+
STARKNET_PRIVATE_KEY: ${{ secrets.TESTNET_ACCOUNT_PRIVATE_KEY }}
28+
STARKNET_PUBLIC_KEY: ${{ secrets.TESTNET_ACCOUNT_PUBLIC_KEY }}
29+
STARKNET_ACCOUNT_ADDRESS: ${{ secrets.TESTNET_ACCOUNT_ADDRESS }}
30+
AVNU_API_KEY: ${{ secrets.AVNU_API_KEY }}

.golangci.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,12 @@ linters:
112112
- gosec
113113
- lll
114114
- mnd
115+
- funlen # it's ok for the examples to have big main() functions
115116
path: examples/
117+
- linters:
118+
- gocritic #commentedOutCode: "curve.SignFelts..." is there just as an explanation
119+
- unused # it's part of the example to have two unused functions
120+
path: examples/paymaster/
116121
- linters:
117122
- exhaustruct
118123
- noctx

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ operations on the wallets. The package has excellent documentation for a smooth
6161
- [invoke transaction example](./examples/invoke) to add a new invoke transaction on testnet.
6262
- [declare transaction example](./examples/simpleDeclare) to add a new contract on testnet.
6363
- [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.
64+
- [paymaster example](./examples/paymaster) to learn how to interact with a paymaster and send transactions with it.
6465
- [typed data example](./examples/typedData) to sign and verify a typed data.
6566
- [websocket example](./examples/websocket) to learn how to subscribe to WebSocket methods.
6667

examples/.env.template

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
#ACCOUNT_ADDRESS=0xyour_account_address
77
#PUBLIC_KEY=0xyour_starknet_public_key
88
#PRIVATE_KEY=0xyour_private_key
9-
#ACCOUNT_CAIRO_VERSION=2
9+
#ACCOUNT_CAIRO_VERSION=2
10+
11+
# ----- use this variable for specific cases in the paymaster example
12+
#AVNU_API_KEY=your-api-key

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ To run an example:
4747
R: See [typedData](./typedData/main.go).
4848
1. How to use WebSocket methods? How to subscribe, unsubscribe, handle errors, and read values from them?
4949
R: See [websocket](./websocket/main.go).
50+
1. How to interact with a paymaster? How to send transactions with it?
51+
R: See [paymaster](./paymaster/main.go).

examples/internal/setup.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ func GetAccountCairoVersion() account.CairoVersion {
6262
}
6363
}
6464

65+
// Validates whether the AVNU_API_KEY variable has been set in the '.env' file and returns it; panics otherwise.
66+
func GetAVNUApiKey() string {
67+
return getEnv("AVNU_API_KEY")
68+
}
69+
6570
// Loads an env variable by name and returns it; panics otherwise.
6671
func getEnv(envName string) string {
6772
env := os.Getenv(envName)

examples/paymaster/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
This example demonstrates how to send transactions on Starknet with a paymaster using the Starkent.go [SNIP-29](https://github.com/starknet-io/SNIPs/blob/dfd91b275ea65413f8c8aedb26677a8afff70f37/SNIPS/snip-29.md) implementation, allowing you to pay fees with tokens other than STRK.
2+
It has three files: main.go, deploy.go, and deploy_and_invoke.go.
3+
4+
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.
5+
6+
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.
7+
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.
8+
Both of these `deploy...`examples require a valid paymaster API key.
9+
10+
All examples demonstrate integration with the AVNU paymaster service and require SNIP-9 compatible accounts.
11+
12+
Steps:
13+
1. Rename the ".env.template" file located at the root of the "examples" folder to ".env"
14+
2. Uncomment, and assign your Sepolia testnet endpoint to the `RPC_PROVIDER_URL` variable in the ".env" file
15+
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)
16+
4. Uncomment, and assign your starknet public key to the `PUBLIC_KEY` variable in the ".env" file
17+
5. Uncomment, and assign your private key to the `PRIVATE_KEY` variable in the ".env" file
18+
6. Make sure you are in the "paymaster" directory
19+
7. Execute `go run .` to run the basic paymaster invoke example
20+
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
21+
22+
The transaction hashes, tracking IDs, and execution status will be returned at the end of each example.

examples/paymaster/deploy.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/NethermindEth/juno/core/felt"
8+
"github.com/NethermindEth/starknet.go/account"
9+
"github.com/NethermindEth/starknet.go/client"
10+
setup "github.com/NethermindEth/starknet.go/examples/internal"
11+
"github.com/NethermindEth/starknet.go/internal/utils"
12+
pm "github.com/NethermindEth/starknet.go/paymaster"
13+
)
14+
15+
// OpenZeppelin account class hash that supports outside executions
16+
const OZAccountClassHash = "0x05b4b537eaa2399e3aa99c4e2e0208ebd6c71bc1467938cd52c798c601e43564"
17+
18+
// An example of how to deploy a contract with a paymaster.
19+
func deployWithPaymaster() {
20+
fmt.Println("Starting paymaster example - deploying an account")
21+
22+
// Load variables from '.env' file
23+
AVNUApiKey := setup.GetAVNUApiKey()
24+
25+
// Since all accounts in Starknet are smart contracts, we need to deploy them first before we can use them.
26+
// And to do so, we need to calculate the address of the new account and fund it with
27+
// enough STRK tokens before deploying it. This tokens will be used to pay the fees for the `deploy` txn.
28+
//
29+
// Deploy an account with a paymaster using the `default` fee mode doesn't make much sense, as we will
30+
// need to send some tokens for the account anyway. So, we will use the `sponsored` fee mode now,
31+
// which will allow the paymaster to fully cover the fees for the `deploy` txn. This mode requires
32+
// an API key from an entity. You can only run this example with it.
33+
34+
// Let's initialise the paymaster client, but now, we will also pass our API key to the client.
35+
// In the AVNU paymaster, the API key is a http header called `x-paymaster-api-key`.
36+
// In the current Starknet.go client, you can set a custom http header using the `client.WithHeader` option.
37+
paymaster, err := pm.New(
38+
context.Background(),
39+
AVNUPaymasterURL,
40+
client.WithHeader("x-paymaster-api-key", AVNUApiKey),
41+
)
42+
if err != nil {
43+
panic(fmt.Errorf("error connecting to the paymaster provider with the API key: %w", err))
44+
}
45+
46+
fmt.Println("Established connection with the paymaster provider")
47+
fmt.Print("Step 1: Build the deploy transaction\n\n")
48+
49+
// First, let's get all the data we need for deploy an account.
50+
_, pubKey, privK := account.GetRandomKeys() // Get random keys for the account
51+
fmt.Println("Public key:", pubKey)
52+
fmt.Println("Private key:", privK)
53+
classHash, _ := utils.HexToFelt(
54+
OZAccountClassHash,
55+
) // It needs to be an SNIP-9 compatible account
56+
constructorCalldata := []*felt.Felt{
57+
pubKey,
58+
} // The OZ account constructor requires the public key
59+
salt, _ := utils.HexToFelt("0xdeadbeef") // Just a random salt
60+
// Precompute the address of the new account based on the salt, class hash and constructor calldata
61+
precAddress := account.PrecomputeAccountAddress(salt, classHash, constructorCalldata)
62+
63+
fmt.Println("Precomputed address:", precAddress)
64+
65+
// Now we can create the deploy data for the transaction.
66+
deployData := &pm.AccountDeploymentData{
67+
Address: precAddress, // The precomputed address of the new account
68+
ClassHash: classHash,
69+
Salt: salt,
70+
Calldata: constructorCalldata,
71+
SignatureData: []*felt.Felt{}, // Optional. For the OZ account, we don't need to add anything in the signature data.
72+
Version: pm.Cairo1,
73+
}
74+
75+
// With the deploy data, we can build the transaction by calling the `paymaster_buildTransaction` method.
76+
// REMEMBER: this will only work if you have a valid API key configured.
77+
//
78+
// A full explanation about the paymaster_buildTransaction method can be found in the `main.go` file of this same example.
79+
builtTxn, err := paymaster.BuildTransaction(context.Background(), &pm.BuildTransactionRequest{
80+
Transaction: pm.UserTransaction{
81+
Type: pm.UserTxnDeploy, // we are building an `deploy` transaction
82+
Deployment: deployData,
83+
},
84+
Parameters: pm.UserParameters{
85+
Version: pm.UserParamV1,
86+
FeeMode: pm.FeeMode{
87+
Mode: pm.FeeModeSponsored, // We then set the fee mode to `sponsored`
88+
Tip: &pm.TipPriority{
89+
Priority: pm.TipPriorityNormal,
90+
},
91+
},
92+
},
93+
})
94+
if err != nil {
95+
panic(fmt.Errorf("error building the deploy transaction: %w", err))
96+
}
97+
fmt.Println("Transaction successfully built by the paymaster")
98+
PrettyPrint(builtTxn)
99+
100+
// Since we are deploying an account, we don't need to sign the transaction, just execute it.
101+
102+
fmt.Println("Step 2: Send the signed transaction")
103+
104+
// With our built deploy transaction, we can send it to the paymaster by calling the `paymaster_executeTransaction` method.
105+
response, err := paymaster.ExecuteTransaction(
106+
context.Background(),
107+
&pm.ExecuteTransactionRequest{
108+
Transaction: pm.ExecutableUserTransaction{
109+
Type: pm.UserTxnDeploy,
110+
Deployment: builtTxn.Deployment, // The deployment data is the same. We can use our `deployData` variable, or
111+
// the `builtTxn.Deployment` value.
112+
},
113+
Parameters: pm.UserParameters{
114+
Version: pm.UserParamV1,
115+
116+
// Using the same fee options as in the `paymaster_buildTransaction` method.
117+
FeeMode: pm.FeeMode{
118+
Mode: pm.FeeModeSponsored,
119+
Tip: &pm.TipPriority{
120+
Priority: pm.TipPriorityNormal,
121+
},
122+
},
123+
},
124+
},
125+
)
126+
if err != nil {
127+
panic(fmt.Errorf("error executing the deploy transaction with the paymaster: %w", err))
128+
}
129+
130+
fmt.Println("Deploy transaction successfully executed by the paymaster")
131+
fmt.Println("Tracking ID:", response.TrackingID)
132+
fmt.Println("Transaction Hash:", response.TransactionHash)
133+
}

0 commit comments

Comments
 (0)