-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Create Transaction Builder Wrapper for Account Abstraction
Description:
Build a high-level transaction builder that wraps Stellar SDK's TransactionBuilder and simplifies account abstraction operations. This is NOT a replacement for Stellar's builder - it's a convenience wrapper focused on smart account operations.
Context:
The Stellar SDK already has an excellent TransactionBuilder with fluent API. However, invoking our account abstraction smart contract methods (add_session_key, execute, revoke_session_key) requires verbose Soroban contract invocation code. This wrapper provides a clean API specifically for our account abstraction layer while delegating to Stellar SDK's builder under the hood.
Important: This uses Stellar SDK's TransactionBuilder internally. We're NOT reimplementing transaction building - we're creating convenience methods for our specific contract operations.
Requirements:
- Create
AccountTransactionBuilderwrapper class using Stellar SDK'sTransactionBuilderinternally - Implement
.addSessionKey()method (wraps contract invocation for add_session_key) - Implement
.revokeSessionKey()method (wraps contract invocation for revoke_session_key) - Implement
.execute()method (wraps contract invocation for execute with session key) - Add
.simulate()method (required for Soroban transactions before submission) - Support standard Stellar operations via
.addOperation()passthrough - Automatic fee estimation from simulation results
- Add memo support (delegate to underlying TransactionBuilder)
- Implement error handling with actionable messages
- Unit tests for all wrapper methods (>90% coverage)
- Integration tests building and submitting to testnet
Implementation Guide:
import * as StellarSdk from '@stellar/stellar-sdk';
import { Contract } from '@stellar/stellar-sdk';
export class AccountTransactionBuilder {
private txBuilder: StellarSdk.TransactionBuilder;
private server: StellarSdk.SorobanRpc.Server;
private contractId: string;
constructor(
sourceAccount: StellarSdk.Account,
server: StellarSdk.SorobanRpc.Server,
accountContractId: string,
networkPassphrase: string
) {
// Use Stellar SDK's TransactionBuilder internally
this.txBuilder = new StellarSdk.TransactionBuilder(sourceAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase
});
this.server = server;
this.contractId = accountContractId;
}
// Convenience method for adding session key
addSessionKey(
publicKey: string,
permissions: number[],
expiresAt: number
): this {
const contract = new Contract(this.contractId);
// Build Soroban contract invocation
const operation = contract.call(
'add_session_key',
StellarSdk.xdr.ScVal.scvAddress(
StellarSdk.Address.fromString(publicKey).toScAddress()
),
StellarSdk.xdr.ScVal.scvVec(
permissions.map(p => StellarSdk.xdr.ScVal.scvU32(p))
),
StellarSdk.xdr.ScVal.scvU64(new StellarSdk.xdr.Uint64(expiresAt))
);
this.txBuilder.addOperation(operation);
return this;
}
// Convenience method for revoking session key
revokeSessionKey(publicKey: string): this {
const contract = new Contract(this.contractId);
const operation = contract.call(
'revoke_session_key',
StellarSdk.xdr.ScVal.scvAddress(
StellarSdk.Address.fromString(publicKey).toScAddress()
)
);
this.txBuilder.addOperation(operation);
return this;
}
// Convenience method for executing with session key
execute(
sessionKeyPublicKey: string,
operations: StellarSdk.xdr.Operation[]
): this {
const contract = new Contract(this.contractId);
const operation = contract.call(
'execute',
StellarSdk.xdr.ScVal.scvAddress(
StellarSdk.Address.fromString(sessionKeyPublicKey).toScAddress()
),
StellarSdk.xdr.ScVal.scvVec(
operations.map(op => /* encode operation */)
)
);
this.txBuilder.addOperation(operation);
return this;
}
// Add memo (delegate to underlying builder)
addMemo(memo: StellarSdk.Memo): this {
this.txBuilder.addMemo(memo);
return this;
}
// Passthrough for any standard Stellar operation
addOperation(operation: StellarSdk.xdr.Operation): this {
this.txBuilder.addOperation(operation);
return this;
}
// Simulate transaction (REQUIRED for Soroban)
async simulate(): Promise<StellarSdk.SorobanRpc.Api.SimulateTransactionResponse> {
const tx = this.txBuilder.build();
return await this.server.simulateTransaction(tx);
}
// Build final transaction with simulation data
async build(): Promise<StellarSdk.Transaction> {
// Simulate first to get resource footprint
const simulation = await this.simulate();
if (StellarSdk.SorobanRpc.Api.isSimulationSuccess(simulation)) {
// Apply simulation results to transaction
const tx = this.txBuilder.build();
return StellarSdk.SorobanRpc.assembleTransaction(tx, simulation).build();
} else {
throw new Error(`Simulation failed: ${simulation.error}`);
}
}
}Usage:
import { AccountTransactionBuilder } from '@ancore/core-sdk';
import { StellarClient } from '@ancore/stellar';
// Initialize
const client = new StellarClient('testnet');
const sourceAccount = await client.getAccount(publicKey);
const server = client.server; // Expose SorobanRpc.Server
// Add session key
const builder = new AccountTransactionBuilder(
sourceAccount,
server,
'CABC...', // account contract ID
StellarSdk.Networks.TESTNET
);
const tx = await builder
.addSessionKey(
sessionKeyPair.publicKey(),
[0, 1], // permissions: SEND_PAYMENT, MANAGE_DATA
Date.now() + 3600000 // expires in 1 hour
)
.addMemo(StellarSdk.Memo.text('Add session key'))
.build(); // Automatically simulates
// Sign and submit
tx.sign(masterKeypair);
const result = await client.submitTransaction(tx);
// Revoke session key
const revokeTx = await new AccountTransactionBuilder(...)
.revokeSessionKey(sessionKeyPair.publicKey())
.build();Key Principles:
- Wrapper, not replacement - Uses
StellarSdk.TransactionBuilderinternally - Convenience methods - Simplifies contract invocations
- Automatic simulation - Soroban requires simulation before submission
- Fluent API - Chain methods just like Stellar SDK
- Passthrough support - Can still add any standard Stellar operation
Files to Create:
packages/core-sdk/src/account-transaction-builder.ts(main wrapper class)packages/core-sdk/src/contract-params.ts(helpers for encoding contract params)packages/core-sdk/src/errors.ts(custom error types for simulation failures)packages/core-sdk/src/__tests__/builder.test.ts(unit tests)packages/core-sdk/src/__tests__/integration.test.ts(testnet tests)packages/core-sdk/README.md(explain wrapper vs Stellar SDK builder)
Dependencies:
@stellar/stellar-sdk(^12.0.0) - The underlying TransactionBuilder@ancore/types(workspace) - SmartAccount, SessionKey types@ancore/stellar(workspace) - StellarClient
Success Criteria:
- Wrapper successfully uses Stellar SDK's TransactionBuilder internally
- All contract invocations (add/revoke session key, execute) work
- Simulation runs automatically before build()
- Fee estimation from simulation applied correctly
- Error messages explain simulation failures clearly
- Can still add standard Stellar operations via passthrough
Definition of Done:
- All account contract methods wrapped (add_session_key, revoke_session_key, execute)
- Automatic simulation before build()
- Tests cover successful and failed simulations
- Documentation clearly explains this is a wrapper, not a replacement
- Integration tests pass with testnet contract deployment
- Examples show both convenience methods and passthrough operations
Key Principle:
This is a WRAPPER, not a replacement! We use
StellarSdk.TransactionBuilderinternally. Only add convenience methods for our account abstraction contract operations.
Additional Resources:
Labels: sdk, transaction, developer-experience
Complexity: 200 points (High)
Estimated Effort: 4-5 days
Priority: High
Questions or need help? Join our Telegram community