feat: USDC trustline setup for smart wallet#195
Conversation
📝 WalkthroughWalkthroughThis PR implements USDC trustline setup for Smart Wallets by adding a Changes
Sequence DiagramsequenceDiagram
participant Client
participant SmartWalletService
participant HorizonServer as Horizon Server
participant StellarSDK as Stellar SDK
Client->>SmartWalletService: setupUSDCTrustline(accountId, network)
SmartWalletService->>HorizonServer: getAccount(accountId)
HorizonServer-->>SmartWalletService: account with sequence number
SmartWalletService->>SmartWalletService: resolve USDC issuer for network
SmartWalletService->>StellarSDK: Asset('USDC', issuer)
StellarSDK-->>SmartWalletService: USDC asset object
SmartWalletService->>StellarSDK: Operation.changeTrust({asset})
StellarSDK-->>SmartWalletService: changeTrust operation
SmartWalletService->>StellarSDK: TransactionBuilder.build()
StellarSDK-->>SmartWalletService: transaction envelope
SmartWalletService->>SmartWalletService: envelope.toXDR() base64
SmartWalletService-->>Client: return trustlineXdr string
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/core/wallet/src/smart-wallet.service.ts (1)
1152-1157: Docstring mentions "fee-less" but the transaction includesBASE_FEE.The documentation states the returned XDR is "unsigned fee-less," but line 1189 sets
fee: BASE_FEE. While this may work correctly in practice (sponsors can adjust fees), consider clarifying the docstring to avoid confusion, or explicitly setfee: '0'if truly fee-less behavior is intended.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/wallet/src/smart-wallet.service.ts` around lines 1152 - 1157, The docstring claims the returned XDR is "unsigned fee-less" but the transaction is created with fee: BASE_FEE; either update the docstring to remove or clarify "fee-less" semantics (e.g., "unsigned; default base fee set but sponsor may replace") or change the transaction creation to use fee: '0' (or 0) if you truly want a zero-fee unsigned ChangeTrust XDR; locate the code that sets fee: BASE_FEE and the surrounding comment block and apply the chosen fix consistently so the docstring and the transaction fee value match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/wallet/src/smart-wallet.service.ts`:
- Around line 1183-1194: The transaction uses this.network (service-wide
passphrase) while the USDC issuer is selected from USDC_ISSUERS[network],
causing a mismatch if the method's network param differs from the service
config; update the code in setupUSDCTrustline (or the function creating the
transaction) to derive a network passphrase from the method parameter (e.g.,
const networkPassphrase = network === 'testnet' ? Networks.TESTNET :
Networks.PUBLIC) and pass that into new TransactionBuilder(..., {
networkPassphrase }) so the TransactionBuilder uses the same network as the
USDC_ISSUERS lookup; also add an import for Networks from the Stellar SDK if not
already present.
---
Nitpick comments:
In `@packages/core/wallet/src/smart-wallet.service.ts`:
- Around line 1152-1157: The docstring claims the returned XDR is "unsigned
fee-less" but the transaction is created with fee: BASE_FEE; either update the
docstring to remove or clarify "fee-less" semantics (e.g., "unsigned; default
base fee set but sponsor may replace") or change the transaction creation to use
fee: '0' (or 0) if you truly want a zero-fee unsigned ChangeTrust XDR; locate
the code that sets fee: BASE_FEE and the surrounding comment block and apply the
chosen fix consistently so the docstring and the transaction fee value match.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 22a10759-ef15-4304-bcb8-d26f414fd581
📒 Files selected for processing (3)
packages/core/wallet/src/smart-wallet.service.tspackages/core/wallet/src/tests/smart-wallet.service.test.tspackages/core/wallet/src/types/smart-wallet.types.ts
| const issuer = USDC_ISSUERS[network]; | ||
| const usdcAsset = new Asset('USDC', issuer); | ||
|
|
||
| const account = await this.server.getAccount(accountId); | ||
|
|
||
| const tx = new TransactionBuilder(account, { | ||
| fee: BASE_FEE, | ||
| networkPassphrase: this.network, | ||
| }) | ||
| .addOperation(Operation.changeTrust({ asset: usdcAsset })) | ||
| .setTimeout(300) | ||
| .build(); |
There was a problem hiding this comment.
Network passphrase may not match the USDC issuer network.
The method resolves the USDC issuer based on the network parameter ('testnet' or 'mainnet'), but the transaction is built using this.network (the service's configured network passphrase). If the service was initialized with Networks.TESTNET but setupUSDCTrustline is called with 'mainnet', the resulting transaction will have the testnet passphrase but reference the mainnet USDC issuer—making it invalid on both networks.
Consider deriving the network passphrase from the network parameter to ensure consistency:
🔧 Proposed fix
+import { Networks } from '@stellar/stellar-sdk';
+
+// Inside setupUSDCTrustline:
const issuer = USDC_ISSUERS[network];
const usdcAsset = new Asset('USDC', issuer);
+const networkPassphrase = network === 'mainnet' ? Networks.PUBLIC : Networks.TESTNET;
const account = await this.server.getAccount(accountId);
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
- networkPassphrase: this.network,
+ networkPassphrase,
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const issuer = USDC_ISSUERS[network]; | |
| const usdcAsset = new Asset('USDC', issuer); | |
| const account = await this.server.getAccount(accountId); | |
| const tx = new TransactionBuilder(account, { | |
| fee: BASE_FEE, | |
| networkPassphrase: this.network, | |
| }) | |
| .addOperation(Operation.changeTrust({ asset: usdcAsset })) | |
| .setTimeout(300) | |
| .build(); | |
| const issuer = USDC_ISSUERS[network]; | |
| const usdcAsset = new Asset('USDC', issuer); | |
| const networkPassphrase = network === 'mainnet' ? Networks.PUBLIC : Networks.TESTNET; | |
| const account = await this.server.getAccount(accountId); | |
| const tx = new TransactionBuilder(account, { | |
| fee: BASE_FEE, | |
| networkPassphrase, | |
| }) | |
| .addOperation(Operation.changeTrust({ asset: usdcAsset })) | |
| .setTimeout(300) | |
| .build(); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/wallet/src/smart-wallet.service.ts` around lines 1183 - 1194,
The transaction uses this.network (service-wide passphrase) while the USDC
issuer is selected from USDC_ISSUERS[network], causing a mismatch if the
method's network param differs from the service config; update the code in
setupUSDCTrustline (or the function creating the transaction) to derive a
network passphrase from the method parameter (e.g., const networkPassphrase =
network === 'testnet' ? Networks.TESTNET : Networks.PUBLIC) and pass that into
new TransactionBuilder(..., { networkPassphrase }) so the TransactionBuilder
uses the same network as the USDC_ISSUERS lookup; also add an import for
Networks from the Stellar SDK if not already present.
Pull Request
📋 Description
Adds USDC trustline support to the Smart Wallet. Introduces
setupUSDCTrustline()as a standalone method and integrates it optionally into thedeploy()flow via anautoTrustlineUSDCflag. The method builds an unsigned fee-lessChangeTrusttransaction for USDC on the user's classic Stellar account, returning XDR for the fee sponsorship workflow.🔗 Related Issues
Closes #177
🧪 Testing
📚 Documentation Updates (Required)
docs/AI.mdwith new patterns/examplesDocumentation Checklist by Component:
If you modified
packages/core/stellar-sdk/:packages/core/stellar-sdk/README.mddocs/examples/stellar-sdk/If you modified
packages/core/invisible-wallet/:packages/core/invisible-wallet/README.mddocs/SECURITY.mddocs/ARCHITECTURE.mdIf you modified
packages/core/automation/:packages/core/automation/README.mddocs/examples/automation/If you modified
packages/core/defi-protocols/:packages/core/defi-protocols/README.mddocs/ARCHITECTURE.mdIf you modified
packages/core/oracles/:packages/core/oracles/README.mdIf you modified
packages/contracts/:If you modified
tools/cli/:🤖 AI-Friendly Documentation
New Files Created
Key Functions/Classes Added
Patterns Used
setupUSDCTrustline()follows the same pattern as all other service methods — returns unsigned base64 XDR for a fee sponsor to complete. The classic account must still sign before submission.deploy()uses two overload signatures so existing callers receivePromise<string>unchanged; callers passingDeployWithTrustlineOptionsreceivePromise<DeployResult>.server.getAccount()for classic transactions: Unlike Soroban simulation flows that usegetLatestLedger(), classic Stellar transactions require the real account sequence number viagetAccount().USDC_ISSUERSis aconstobject keyed by'testnet' | 'mainnet'; extend this object to support new networks rather than adding conditional logic.N/A — no UI changes.
🔄 Deployment Notes
No special deployment steps. USDC issuers are hardcoded constants — no environment variables required.
✅ Final Checklist
By submitting this PR, I confirm that:
Summary by CodeRabbit