Skip to content

Commit 3a9b71c

Browse files
feat: Stellar soroban gas abstraction (#642)
* feat: Add initial stellar soroban gas abstraction support * fix: Add factory contract address * fix: Fixing /quote endpoint * feat: Introducing soroban gas abstraction * feat: Tool for signing user auth entry * test: Fixing test cases * fix: Improvements * test: Adding test cases * test: Adding test cases * fix: Improvements * fix: Improvements * fix: Update openapi file * feat: Enhance Stellar DEX service integration * feat: Adding soroban token swaps * test: Adding test cases * fix: Improvements to env vars and default addresses * fix: Improvements * fix: Adding coverage * fix: Fixing test * fix: Fixing clippy issue * docs: Adding documentation * docs: Improvements --------- Co-authored-by: Zeljko <zeljko89markovic@gmail.com>
1 parent 260c2b6 commit 3a9b71c

40 files changed

Lines changed: 11738 additions & 1274 deletions

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,9 @@ path = "helpers/generate_openapi.rs"
148148
name = "verify_eip712"
149149
path = "helpers/verify_eip712.rs"
150150

151+
[[example]]
152+
name = "sign_soroban_auth_entry"
153+
path = "helpers/sign_soroban_auth_entry.rs"
154+
151155
[lib]
152156
path = "src/lib.rs"

docs/guides/stellar-sponsored-transactions-guide.mdx

Lines changed: 289 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ First, configure a Stellar relayer in your `config.json`:
7070
### 2. Policy Configuration Explained
7171

7272
#### `fee_payment_strategy`
73+
7374
- **`relayer`**: Relayer pays all network fees. Users pay fees in tokens.
7475
- **`user`**: User must include fee payment in the transaction.
7576

7677
#### `allowed_tokens`
78+
7779
List of tokens that can be used for fee payment. Each token can have:
7880

7981
- **`asset`**: Asset identifier in format `"native"` or `"CODE:ISSUER"` (e.g., `"USDC:GA5Z..."`)
@@ -85,16 +87,19 @@ List of tokens that can be used for fee payment. Each token can have:
8587
- `retain_min_amount`: Minimum amount to retain after swap
8688

8789
#### `swap_config` (Global)
90+
8891
Configuration for converting collected tokens back to XLM:
8992

9093
- **`strategies`**: DEX strategies to use (currently supports `order-book`)
9194
- **`cron_schedule`**: Schedule for automatic token swaps (e.g., `"0 */6 * * *"` = every 6 hours)
9295
- **`min_balance_threshold`**: Minimum XLM balance (in stroops) before triggering swaps
9396

9497
#### `slippage_percentage`
98+
9599
Default slippage tolerance for token conversions (default: 1.0%)
96100

97101
#### `fee_margin_percentage`
102+
98103
Additional fee margin added to estimated fees to account for price fluctuations (default: 10.0%)
99104

100105
### 3. Enabling Trustlines
@@ -103,7 +108,6 @@ Before users can pay fees in tokens, the relayer account must establish trustlin
103108

104109
For a complete example of how to create trustlines, see the [Relayer SDK example](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/tree/main/examples/relayer/stellar/sponsored/create-trustline.ts).
105110

106-
107111
## Using the API
108112

109113
The sponsored transaction flow consists of four steps: **Quote**, **Build**, **Sign**, and **Send**.
@@ -226,7 +230,7 @@ If the user's address is connected with another relayer configured in **relayer
226230

227231
**Endpoint:** `POST /api/v1/relayers/{relayer_id}/sign-transaction`
228232

229-
```
233+
````
230234
231235
#### Option 3: Custom Signing Solution
232236
@@ -251,7 +255,7 @@ Submit the signed transaction to the relayer. The relayer will:
251255
"network": "testnet",
252256
"fee_bump": true
253257
}
254-
```
258+
````
255259

256260
**Response:**
257261

@@ -304,6 +308,7 @@ When building transactions from operations, use the following format:
304308
```
305309

306310
Supported operation types:
311+
307312
- `payment`: Standard payment operation
308313
- `createAccount`: Account creation
309314
- `changeTrust`: Trustline management
@@ -331,7 +336,6 @@ Supported operation types:
331336
- `extendFootprintTtl`: Extend footprint TTL
332337
- `restoreFootprint`: Restore footprint
333338

334-
335339
## Best Practices
336340

337341
1. **Always Quote First**: Get a fee quote before building to show users the expected cost
@@ -342,6 +346,287 @@ Supported operation types:
342346
6. **Use Fee Margins**: The `fee_margin_percentage` helps account for price fluctuations
343347
7. **Monitor Trustlines**: Ensure trustlines are established before users attempt transactions
344348

349+
## Soroban Gas Abstraction
350+
351+
### Overview
352+
353+
Soroban Gas Abstraction extends the sponsored transaction concept to Soroban smart contracts. Instead of requiring users to hold XLM for contract invocation fees, users can pay in any Soroban token (e.g., a USDC Soroban token contract). This is powered by a **FeeForwarder smart contract** that wraps the user's contract call with fee collection logic.
354+
355+
**How it differs from Classic Stellar Sponsored Transactions:**
356+
357+
| Aspect | Classic Stellar | Soroban Gas Abstraction |
358+
| -------------------- | ----------------------------------------------- | -------------------------------------------------------- |
359+
| **Transaction type** | Classic operations (payments, trustlines, etc.) | Soroban `InvokeHostFunction` operations |
360+
| **Fee mechanism** | Fee-bump transaction wrapper | FeeForwarder smart contract |
361+
| **Fee token format** | Credit assets (`CODE:ISSUER`) | Soroban token contracts (`C...` address) |
362+
| **Authorization** | Standard transaction signing | User signs a `SorobanAuthorizationEntry` |
363+
| **Submission** | Relayer wraps in fee-bump envelope | Relayer injects signed auth entries and submits directly |
364+
365+
### Prerequisites
366+
367+
- A running OpenZeppelin Relayer instance
368+
- A Stellar relayer configured with `"fee_payment_strategy": "user"`
369+
- A deployed **FeeForwarder contract** on the target network. [Check the OpenZeppelin contract here](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/fee-abstraction)
370+
- The `STELLAR_TESTNET_FEE_FORWARDER_ADDRESS` or `STELLAR_MAINNET_FEE_FORWARDER_ADDRESS` environment variable set to the FeeForwarder contract address
371+
- Allowed tokens configured with Soroban token contract addresses (`C...` format)
372+
- Sufficient XLM balance in the relayer account for network fees
373+
374+
### Configuration
375+
376+
Configure the relayer with Soroban token contracts in the `allowed_tokens` list. Note that Soroban tokens use contract addresses (`C...` format) rather than the `CODE:ISSUER` format used for classic Stellar assets.
377+
378+
```json
379+
{
380+
"relayers": [
381+
{
382+
"id": "stellar-soroban-gas",
383+
"name": "Stellar Soroban Gas Abstraction",
384+
"network": "testnet",
385+
"paused": false,
386+
"signer_id": "local-signer",
387+
"network_type": "stellar",
388+
"policies": {
389+
"fee_payment_strategy": "user",
390+
"fee_margin_percentage": 5,
391+
"allowed_tokens": [
392+
{
393+
"asset": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
394+
"max_allowed_fee": 1000000000,
395+
"swap_config": {
396+
"slippage_percentage": 0.5,
397+
"min_amount": 10000000,
398+
"max_amount": 1000000000,
399+
"retain_min_amount": 10000000
400+
}
401+
}
402+
],
403+
"swap_config": {
404+
"strategies": ["soroswap"],
405+
"cron_schedule": "0 */6 * * *",
406+
"min_balance_threshold": 50000000
407+
}
408+
}
409+
}
410+
]
411+
}
412+
```
413+
414+
Set the required environment variables:
415+
416+
<Callout>
417+
For testnet, you must provide all environment variables. For mainnet, the code
418+
already contains default addresses, but you can override them using
419+
environment variables.
420+
</Callout>
421+
422+
```bash
423+
# Stellar FeeForwarder Contract Addresses
424+
# STELLAR_MAINNET_FEE_FORWARDER_ADDRESS=
425+
STELLAR_TESTNET_FEE_FORWARDER_ADDRESS=
426+
427+
# Stellar Soroswap Router Contract Addresses
428+
STELLAR_MAINNET_SOROSWAP_ROUTER_ADDRESS=
429+
STELLAR_TESTNET_SOROSWAP_ROUTER_ADDRESS=
430+
431+
# Stellar Soroswap Factory Contract Addresses
432+
STELLAR_MAINNET_SOROSWAP_FACTORY_ADDRESS=
433+
STELLAR_TESTNET_SOROSWAP_FACTORY_ADDRESS=
434+
435+
# Stellar Soroswap Native Wrapper (XLM) Contract Addresses
436+
STELLAR_MAINNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS=
437+
STELLAR_TESTNET_SOROSWAP_NATIVE_WRAPPER_ADDRESS=
438+
```
439+
440+
### Using the API
441+
442+
The Soroban gas abstraction flow uses the same endpoints as classic sponsored transactions but with key differences in the request/response payloads and an additional authorization signing step.
443+
444+
#### Step 1: Get Fee Quote
445+
446+
Estimate the fee for a Soroban contract invocation in your preferred token.
447+
448+
**Endpoint:** `POST /api/v1/relayers/{relayer_id}/transactions/sponsored/quote`
449+
450+
**Request Body:**
451+
452+
The `transaction_xdr` should contain the Soroban `InvokeHostFunction` operation, and the `fee_token` should be a Soroban token contract address (`C...` format).
453+
454+
```json
455+
{
456+
"transaction_xdr": "AAAAAgAAAAD...",
457+
"fee_token": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
458+
}
459+
```
460+
461+
**Response:**
462+
463+
The response includes `max_fee_in_token` and `max_fee_in_token_ui` fields that account for slippage buffer. These represent the maximum amount the user authorizes.
464+
465+
```json
466+
{
467+
"success": true,
468+
"data": {
469+
"fee_in_token_ui": "1.5",
470+
"fee_in_token": "15000000",
471+
"conversion_rate": "0.15",
472+
"max_fee_in_token": "15750000",
473+
"max_fee_in_token_ui": "1.575"
474+
}
475+
}
476+
```
477+
478+
#### Step 2: Build Sponsored Transaction
479+
480+
Prepare a Soroban transaction that wraps your contract call with the FeeForwarder contract.
481+
482+
**Endpoint:** `POST /api/v1/relayers/{relayer_id}/transactions/sponsored/build`
483+
484+
**Request Body:**
485+
486+
```json
487+
{
488+
"transaction_xdr": "AAAAAgAAAAD...",
489+
"fee_token": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
490+
}
491+
```
492+
493+
**Response:**
494+
495+
The response includes a `user_auth_entry` field containing the Soroban authorization entry that the user must sign. This authorization entry authorizes the FeeForwarder contract to collect fees in the specified token on the user's behalf.
496+
497+
```json
498+
{
499+
"success": true,
500+
"data": {
501+
"transaction": "AAAAAgAAAAD...",
502+
"fee_in_token_ui": "1.5",
503+
"fee_in_token": "15000000",
504+
"fee_in_stroops": "100000",
505+
"fee_token": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
506+
"valid_until": "2024-01-01T00:01:00Z",
507+
"user_auth_entry": "AAAABgAAAAA...",
508+
"max_fee_in_token": "15750000",
509+
"max_fee_in_token_ui": "1.575"
510+
}
511+
}
512+
```
513+
514+
#### Step 3: Sign the Authorization Entry
515+
516+
Unlike classic sponsored transactions where the user signs the full transaction XDR, for Soroban gas abstraction the user must sign the **`user_auth_entry`** returned from the Build response. This is a `SorobanAuthorizationEntry` XDR that authorizes:
517+
518+
1. The FeeForwarder contract to transfer fee tokens from the user to the relayer
519+
2. The user's original contract invocation (wrapped inside the FeeForwarder call)
520+
521+
The signing method depends on your setup:
522+
523+
- **Programmatic signing**: Use the Stellar SDK to sign the authorization entry with the user's private key
524+
- **Wallet signing**: Use a Soroban-compatible wallet that supports signing authorization entries
525+
- **CLI helper tool**: A ready-to-use signing tool is provided in the repository at `helpers/sign_soroban_auth_entry.rs`
526+
527+
##### Using the CLI Helper Tool
528+
529+
The repository includes a helper tool that signs Soroban authorization entries directly from the command line. This is useful for testing and development:
530+
531+
```bash
532+
cargo run --example sign_soroban_auth_entry -- \
533+
--secret-key "S..." \
534+
--auth-entry "<base64 XDR from build response>" \
535+
--network testnet
536+
```
537+
538+
To generate the full JSON payload ready for the `/transactions` endpoint, include the `--transaction-xdr` flag:
539+
540+
```bash
541+
cargo run --example sign_soroban_auth_entry -- \
542+
--secret-key "S..." \
543+
--auth-entry "<base64 XDR from build response>" \
544+
--network testnet \
545+
--transaction-xdr "<transaction XDR from build response>"
546+
```
547+
548+
This outputs a JSON object with `network`, `transaction_xdr`, and `signed_auth_entry` fields that can be sent directly to the Submit endpoint.
549+
550+
For complete code examples of signing authorization entries and testing the full gas abstraction flow (quote, build, sign, and send), see the [Relayer SDK examples](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/tree/main/examples/relayers/stellar/src/soroban). The example demonstrates the end-to-end flow including fee estimation, transaction building, authorization signing, and submission.
551+
552+
#### Step 4: Submit to Relayer
553+
554+
Submit the transaction XDR from the Build response along with the user's **signed authorization entry**. The relayer will:
555+
556+
1. Inject the user's signed authorization entry into the transaction
557+
2. Add the relayer's own authorization entry (as the transaction source account)
558+
3. Re-simulate the transaction with the signed auth entries for accurate resource calculation
559+
4. Apply a resource buffer (15% safety margin) to prevent execution failures
560+
5. Sign the transaction with the relayer's key
561+
6. Submit it to the Stellar network
562+
563+
**Endpoint:** `POST /api/v1/relayers/{relayer_id}/transactions`
564+
565+
**Request Body:**
566+
567+
```json
568+
{
569+
"transaction_xdr": "AAAAAgAAAAD...",
570+
"network": "testnet",
571+
"signed_auth_entry": "AAAABgAAAAA..."
572+
}
573+
```
574+
575+
> **Note:** The `signed_auth_entry` field is used instead of `fee_bump` for Soroban gas abstraction. These two fields are mutually exclusive.
576+
577+
**Response:**
578+
579+
```json
580+
{
581+
"success": true,
582+
"data": {
583+
"id": "tx-123456",
584+
"status": "pending",
585+
"network_data": {
586+
"signature": "abc123...",
587+
"transaction": "AAAAAgAAAAD..."
588+
}
589+
}
590+
}
591+
```
592+
593+
### Fee Token Format
594+
595+
For Soroban gas abstraction, fee tokens are specified using Soroban contract addresses:
596+
597+
- **Soroban token**: `"C..."` (e.g., `"CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"`)
598+
599+
This differs from classic sponsored transactions which use the `"CODE:ISSUER"` format. The relayer automatically detects whether to use the classic or Soroban flow based on the `fee_token` format and the presence of `InvokeHostFunction` operations in the transaction XDR.
600+
601+
### How the FeeForwarder Contract Works
602+
603+
The FeeForwarder contract acts as a proxy that:
604+
605+
1. **Collects the fee**: Transfers the fee token amount from the user to the relayer
606+
2. **Executes the user's call**: Invokes the target contract function on behalf of the user
607+
3. **Manages authorization**: Bundles fee approval and contract invocation into a single authorization entry
608+
609+
The contract's `forward()` function takes these parameters:
610+
611+
- `fee_token` - Soroban token contract address for fee payment
612+
- `fee_amount` - Actual fee to charge
613+
- `max_fee_amount` - Maximum fee authorized by the user (includes slippage buffer)
614+
- `expiration_ledger` - Ledger number when the authorization expires
615+
- `target_contract` - The contract the user wants to invoke
616+
- `target_fn` - The function name to call
617+
- `target_args` - The function arguments
618+
- `user` - The user's account address
619+
- `relayer` - The relayer's account address (fee recipient)
620+
621+
### Best Practices for Soroban Gas Abstraction
622+
623+
1. **Use the Quote endpoint first**: Always get a fee estimate before building to show users the expected cost, including the max fee with slippage
624+
2. **Handle authorization expiration**: Authorization entries expire at a specific ledger. Rebuild if the authorization has expired
625+
3. **Validate token contract addresses**: Ensure fee tokens use valid Soroban contract addresses (`C...` format, 56 characters)
626+
4. **Monitor resource fees**: Soroban transactions have dynamic resource fees based on CPU instructions, storage reads/writes, and ledger entry access. The relayer applies a 15% buffer, but complex contract calls may need higher limits
627+
5. **Set appropriate max fees**: Configure `max_allowed_fee` per token to prevent excessive fees during network congestion
628+
6. **Configure the FeeForwarder address**: Ensure the correct FeeForwarder contract address is set via environment variables for each network
629+
345630
## Additional Resources
346631

347632
- [Stellar Documentation](https://developers.stellar.org/)

0 commit comments

Comments
 (0)