stellar-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/package.json +37 -0
- package/src/core-skills/module-help.csv +5 -0
- package/src/core-skills/module.yaml +33 -0
- package/src/core-skills/stellar-brainstorming/SKILL.md +6 -0
- package/src/core-skills/stellar-brainstorming/steps/step-01-session-setup.md +67 -0
- package/src/core-skills/stellar-brainstorming/steps/step-02a-user-selected.md +20 -0
- package/src/core-skills/stellar-brainstorming/steps/step-02b-ai-recommended.md +29 -0
- package/src/core-skills/stellar-brainstorming/steps/step-03-technique-execution.md +69 -0
- package/src/core-skills/stellar-brainstorming/steps/step-04-idea-organization.md +64 -0
- package/src/core-skills/stellar-brainstorming/workflow.md +50 -0
- package/src/core-skills/stellar-help/SKILL.md +71 -0
- package/src/core-skills/stellar-party-mode/SKILL.md +109 -0
- package/src/scripts/resolve_config.py +170 -0
- package/src/scripts/resolve_customization.py +209 -0
- package/src/stellar-skills/1-analysis/stellar-agent-analyst/SKILL.md +71 -0
- package/src/stellar-skills/1-analysis/stellar-agent-analyst/customize.toml +41 -0
- package/src/stellar-skills/1-analysis/stellar-analytics/SKILL.md +239 -0
- package/src/stellar-skills/1-analysis/stellar-domain-research/SKILL.md +82 -0
- package/src/stellar-skills/1-analysis/stellar-market-research/SKILL.md +90 -0
- package/src/stellar-skills/2-planning/stellar-agent-pm/SKILL.md +57 -0
- package/src/stellar-skills/2-planning/stellar-agent-pm/customize.toml +36 -0
- package/src/stellar-skills/2-planning/stellar-epics-stories/SKILL.md +106 -0
- package/src/stellar-skills/2-planning/stellar-prd/SKILL.md +115 -0
- package/src/stellar-skills/2-planning/stellar-project-brief/SKILL.md +83 -0
- package/src/stellar-skills/3-architecture/stellar-agent-architect/SKILL.md +53 -0
- package/src/stellar-skills/3-architecture/stellar-agent-architect/customize.toml +31 -0
- package/src/stellar-skills/3-architecture/stellar-architecture-doc/SKILL.md +162 -0
- package/src/stellar-skills/4-implementation/stellar-agent-developer/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-developer/customize.toml +56 -0
- package/src/stellar-skills/4-implementation/stellar-agent-devops/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-devops/customize.toml +36 -0
- package/src/stellar-skills/4-implementation/stellar-agent-frontend/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-frontend/customize.toml +52 -0
- package/src/stellar-skills/4-implementation/stellar-agent-qa/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-qa/customize.toml +31 -0
- package/src/stellar-skills/4-implementation/stellar-create-asset/SKILL.md +145 -0
- package/src/stellar-skills/4-implementation/stellar-create-transaction/SKILL.md +134 -0
- package/src/stellar-skills/4-implementation/stellar-deploy-contract/SKILL.md +124 -0
- package/src/stellar-skills/4-implementation/stellar-freighter-integration/SKILL.md +193 -0
- package/src/stellar-skills/4-implementation/stellar-horizon-integration/SKILL.md +198 -0
- package/src/stellar-skills/4-implementation/stellar-init-contract/SKILL.md +102 -0
- package/src/stellar-skills/4-implementation/stellar-liquidity-pool/SKILL.md +156 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-setup/SKILL.md +198 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-soroban/SKILL.md +228 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-wallet/SKILL.md +276 -0
- package/src/stellar-skills/4-implementation/stellar-sep10-auth/SKILL.md +252 -0
- package/src/stellar-skills/4-implementation/stellar-setup-environment/SKILL.md +163 -0
- package/src/stellar-skills/4-implementation/stellar-setup-trustline/SKILL.md +107 -0
- package/src/stellar-skills/4-implementation/stellar-test-contract/SKILL.md +146 -0
- package/src/stellar-skills/4-implementation/stellar-write-contract/SKILL.md +140 -0
- package/src/stellar-skills/module-help.csv +24 -0
- package/src/stellar-skills/module.yaml +103 -0
- package/tools/installer/cli-utils.js +39 -0
- package/tools/installer/commands/init.js +335 -0
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/prompts.js +852 -0
- package/tools/installer/stellar-cli.js +80 -0
- package/tools/installer/yaml-format.js +245 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-sep10-auth
|
|
3
|
+
description: 'Implement SEP-10 Web Authentication for Stellar dApps — challenge/response flow, JWT issuance, and backend verification. Use when the user needs to authenticate users by their Stellar account without sharing private keys.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SEP-10 Web Authentication
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Implement the Stellar SEP-10 standard to authenticate users by proving ownership of a Stellar account. Issues a JWT for subsequent API calls.
|
|
11
|
+
|
|
12
|
+
## On Activation
|
|
13
|
+
|
|
14
|
+
Load `{network_preference}` from `{project-root}/_stellar/stellar/config.yaml`.
|
|
15
|
+
|
|
16
|
+
## How SEP-10 Works
|
|
17
|
+
|
|
18
|
+
1. **Client** requests a challenge from the server, providing their public key
|
|
19
|
+
2. **Server** builds a Stellar transaction encoding a random nonce — signed by the server's keypair — and returns it as XDR
|
|
20
|
+
3. **Client** signs the challenge transaction with their wallet (Freighter)
|
|
21
|
+
4. **Server** verifies both signatures, checks nonce/expiry, issues a JWT
|
|
22
|
+
5. **Client** sends the JWT in `Authorization: Bearer {token}` on subsequent API requests
|
|
23
|
+
|
|
24
|
+
## Backend Setup (Node.js / Next.js API Routes)
|
|
25
|
+
|
|
26
|
+
### Step 1: Install Dependencies
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yarn add express jsonwebtoken stellar-sdk
|
|
30
|
+
# or for Next.js only:
|
|
31
|
+
npm install jsonwebtoken stellar-sdk
|
|
32
|
+
npm install --save-dev @types/jsonwebtoken
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 2: Environment Variables
|
|
36
|
+
|
|
37
|
+
```env
|
|
38
|
+
# Server-side only (no NEXT_PUBLIC_ prefix)
|
|
39
|
+
SEP10_SERVER_SECRET=S... # Stellar keypair secret key for signing challenges
|
|
40
|
+
SEP10_HOME_DOMAIN=yourdomain.com # must match stellar.toml WEB_AUTH_ENDPOINT host
|
|
41
|
+
JWT_SECRET=your-jwt-secret-here # random 32+ char string
|
|
42
|
+
JWT_EXPIRES_IN=24h
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 3: Challenge Endpoint
|
|
46
|
+
|
|
47
|
+
`src/app/api/auth/challenge/route.ts`:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
51
|
+
import {
|
|
52
|
+
Keypair, TransactionBuilder, Networks, Operation,
|
|
53
|
+
Account, StrKey, BASE_FEE,
|
|
54
|
+
} from 'stellar-sdk';
|
|
55
|
+
|
|
56
|
+
const SERVER_KEYPAIR = Keypair.fromSecret(process.env.SEP10_SERVER_SECRET!);
|
|
57
|
+
const HOME_DOMAIN = process.env.SEP10_HOME_DOMAIN!;
|
|
58
|
+
const NETWORK_PASSPHRASE = process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE!;
|
|
59
|
+
|
|
60
|
+
export async function GET(request: NextRequest) {
|
|
61
|
+
const { searchParams } = new URL(request.url);
|
|
62
|
+
const clientPublicKey = searchParams.get('account');
|
|
63
|
+
|
|
64
|
+
if (!clientPublicKey || !StrKey.isValidEd25519PublicKey(clientPublicKey)) {
|
|
65
|
+
return NextResponse.json({ error: 'Invalid account' }, { status: 400 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// SEP-10 requires a fake account with sequence 0
|
|
69
|
+
const serverAccount = new Account(SERVER_KEYPAIR.publicKey(), '-1');
|
|
70
|
+
|
|
71
|
+
const now = Math.floor(Date.now() / 1000);
|
|
72
|
+
const tx = new TransactionBuilder(serverAccount, {
|
|
73
|
+
fee: BASE_FEE,
|
|
74
|
+
networkPassphrase: NETWORK_PASSPHRASE,
|
|
75
|
+
timebounds: {
|
|
76
|
+
minTime: now,
|
|
77
|
+
maxTime: now + 300, // 5 minute window
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.addOperation(
|
|
81
|
+
Operation.manageData({
|
|
82
|
+
name: `${HOME_DOMAIN} auth`,
|
|
83
|
+
value: Buffer.from(crypto.getRandomValues(new Uint8Array(48))).toString('base64'),
|
|
84
|
+
source: clientPublicKey, // operation source = client account
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
.addOperation(
|
|
88
|
+
Operation.manageData({
|
|
89
|
+
name: 'web_auth_domain',
|
|
90
|
+
value: HOME_DOMAIN,
|
|
91
|
+
source: SERVER_KEYPAIR.publicKey(),
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
.build();
|
|
95
|
+
|
|
96
|
+
tx.sign(SERVER_KEYPAIR);
|
|
97
|
+
|
|
98
|
+
return NextResponse.json({
|
|
99
|
+
transaction: tx.toXDR(),
|
|
100
|
+
network_passphrase: NETWORK_PASSPHRASE,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Step 4: Token Endpoint
|
|
106
|
+
|
|
107
|
+
`src/app/api/auth/token/route.ts`:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
111
|
+
import { TransactionBuilder, Keypair, StrKey } from 'stellar-sdk';
|
|
112
|
+
import jwt from 'jsonwebtoken';
|
|
113
|
+
|
|
114
|
+
const SERVER_KEYPAIR = Keypair.fromSecret(process.env.SEP10_SERVER_SECRET!);
|
|
115
|
+
const HOME_DOMAIN = process.env.SEP10_HOME_DOMAIN!;
|
|
116
|
+
const NETWORK_PASSPHRASE = process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE!;
|
|
117
|
+
const JWT_SECRET = process.env.JWT_SECRET!;
|
|
118
|
+
|
|
119
|
+
export async function POST(request: NextRequest) {
|
|
120
|
+
const { transaction } = await request.json();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const tx = TransactionBuilder.fromXDR(transaction, NETWORK_PASSPHRASE);
|
|
124
|
+
|
|
125
|
+
// 1. Verify server signature
|
|
126
|
+
const serverSigned = tx.signatures.some(sig =>
|
|
127
|
+
SERVER_KEYPAIR.verify(tx.hash(), sig.signature())
|
|
128
|
+
);
|
|
129
|
+
if (!serverSigned) {
|
|
130
|
+
return NextResponse.json({ error: 'Invalid server signature' }, { status: 400 });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 2. Extract client public key from first operation's source
|
|
134
|
+
const clientKey = (tx.operations[0] as any).source;
|
|
135
|
+
if (!clientKey || !StrKey.isValidEd25519PublicKey(clientKey)) {
|
|
136
|
+
return NextResponse.json({ error: 'Missing client source account' }, { status: 400 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 3. Verify client signature
|
|
140
|
+
const clientKeypair = Keypair.fromPublicKey(clientKey);
|
|
141
|
+
const clientSigned = tx.signatures.some(sig => {
|
|
142
|
+
try {
|
|
143
|
+
return clientKeypair.verify(tx.hash(), sig.signature());
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
if (!clientSigned) {
|
|
149
|
+
return NextResponse.json({ error: 'Client signature missing or invalid' }, { status: 400 });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 4. Verify timebounds not expired
|
|
153
|
+
const now = Math.floor(Date.now() / 1000);
|
|
154
|
+
const { minTime, maxTime } = tx.timeBounds!;
|
|
155
|
+
if (now < Number(minTime) || now > Number(maxTime)) {
|
|
156
|
+
return NextResponse.json({ error: 'Challenge expired' }, { status: 400 });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 5. Issue JWT
|
|
160
|
+
const token = jwt.sign(
|
|
161
|
+
{ sub: clientKey, iss: HOME_DOMAIN },
|
|
162
|
+
JWT_SECRET,
|
|
163
|
+
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return NextResponse.json({ token });
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return NextResponse.json({ error: String(err) }, { status: 400 });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Step 5: Client-Side SEP-10 Flow
|
|
174
|
+
|
|
175
|
+
`src/lib/stellar/sep10.ts`:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { TransactionBuilder } from 'stellar-sdk';
|
|
179
|
+
import { NETWORK_PASSPHRASE } from './config';
|
|
180
|
+
|
|
181
|
+
export async function authenticate(
|
|
182
|
+
publicKey: string,
|
|
183
|
+
signFn: (xdr: string) => Promise<string>
|
|
184
|
+
): Promise<string> {
|
|
185
|
+
// 1. Get challenge
|
|
186
|
+
const chalRes = await fetch(`/api/auth/challenge?account=${publicKey}`);
|
|
187
|
+
if (!chalRes.ok) throw new Error('Failed to get challenge');
|
|
188
|
+
const { transaction } = await chalRes.json();
|
|
189
|
+
|
|
190
|
+
// 2. Sign with wallet (Freighter)
|
|
191
|
+
const signedXdr = await signFn(transaction);
|
|
192
|
+
|
|
193
|
+
// 3. Submit signed challenge
|
|
194
|
+
const tokenRes = await fetch('/api/auth/token', {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({ transaction: signedXdr }),
|
|
198
|
+
});
|
|
199
|
+
if (!tokenRes.ok) throw new Error('Authentication failed');
|
|
200
|
+
const { token } = await tokenRes.json();
|
|
201
|
+
return token;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Step 6: Protect API Routes
|
|
206
|
+
|
|
207
|
+
Middleware helper for authenticated routes:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import jwt from 'jsonwebtoken';
|
|
211
|
+
import { NextRequest } from 'next/server';
|
|
212
|
+
|
|
213
|
+
export function verifyJwt(request: NextRequest): string | null {
|
|
214
|
+
const auth = request.headers.get('Authorization');
|
|
215
|
+
if (!auth?.startsWith('Bearer ')) return null;
|
|
216
|
+
try {
|
|
217
|
+
const payload = jwt.verify(auth.slice(7), process.env.JWT_SECRET!) as jwt.JwtPayload;
|
|
218
|
+
return payload.sub ?? null; // returns the Stellar public key
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Usage in protected routes:
|
|
226
|
+
```typescript
|
|
227
|
+
export async function GET(request: NextRequest) {
|
|
228
|
+
const publicKey = verifyJwt(request);
|
|
229
|
+
if (!publicKey) {
|
|
230
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
231
|
+
}
|
|
232
|
+
// publicKey is the authenticated Stellar account
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Step 7: stellar.toml Entry
|
|
237
|
+
|
|
238
|
+
Add to your `/.well-known/stellar.toml`:
|
|
239
|
+
```toml
|
|
240
|
+
WEB_AUTH_ENDPOINT="https://yourdomain.com/api/auth/challenge"
|
|
241
|
+
SIGNING_KEY="G..." # SERVER_KEYPAIR public key
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Reference
|
|
245
|
+
|
|
246
|
+
- Full spec: [SEP-10](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md)
|
|
247
|
+
- Related: SEP-24 (hosted deposit/withdraw), SEP-31 (cross-border payments)
|
|
248
|
+
|
|
249
|
+
### Next Steps
|
|
250
|
+
|
|
251
|
+
- Use the JWT from `authenticate()` in fetch headers for all protected API calls
|
|
252
|
+
- `stellar-nextjs-wallet` — hook the auth flow into the wallet connect button
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-setup-environment
|
|
3
|
+
description: 'Set up a complete local Stellar development environment including the Docker quickstart bundle, Stellar CLI, Rust toolchain, and SDK installation. Use when the user wants to set up their local Stellar development environment.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Set Up Stellar Development Environment
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Configure a fully working local Stellar development environment for Soroban and Classic Stellar development.
|
|
11
|
+
|
|
12
|
+
## On Activation
|
|
13
|
+
|
|
14
|
+
Load `{network_preference}` and `{primary_language}` from `{project-root}/_stellar/stellar/config.yaml`.
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Detect Current State
|
|
19
|
+
|
|
20
|
+
Run `stellar doctor` if already installed:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
stellar doctor
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Report what's installed and what's missing. If everything is green, skip to the verification step.
|
|
27
|
+
|
|
28
|
+
### Step 2: Install Rust (for Soroban)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Install Rust via rustup
|
|
32
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
33
|
+
|
|
34
|
+
# Add the WASM compile target
|
|
35
|
+
rustup target add wasm32-unknown-unknown
|
|
36
|
+
|
|
37
|
+
# Verify
|
|
38
|
+
rustc --version
|
|
39
|
+
cargo --version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 3: Install Stellar CLI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Install via cargo (recommended — always gets latest stable)
|
|
46
|
+
cargo install --locked stellar-cli --features opt
|
|
47
|
+
|
|
48
|
+
# Verify installation
|
|
49
|
+
stellar --version
|
|
50
|
+
|
|
51
|
+
# Diagnose environment
|
|
52
|
+
stellar doctor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 4: Install Docker (for Local Network)
|
|
56
|
+
|
|
57
|
+
**macOS:** Install [Docker Desktop](https://www.docker.com/products/docker-desktop/)
|
|
58
|
+
|
|
59
|
+
**Linux (Ubuntu/Debian):**
|
|
60
|
+
```bash
|
|
61
|
+
sudo apt-get update
|
|
62
|
+
sudo apt-get install docker-ce docker-ce-cli containerd.io
|
|
63
|
+
sudo usermod -aG docker $USER # run without sudo
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Verify: `docker --version`
|
|
67
|
+
|
|
68
|
+
### Step 5: Start Local Stellar Network
|
|
69
|
+
|
|
70
|
+
**Option A: Stellar CLI (recommended)**
|
|
71
|
+
```bash
|
|
72
|
+
stellar network container start local
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Option B: Docker directly**
|
|
76
|
+
```bash
|
|
77
|
+
docker run --rm -it \
|
|
78
|
+
-p 8000:8000 \
|
|
79
|
+
--name stellar \
|
|
80
|
+
stellar/quickstart:testing \
|
|
81
|
+
--local \
|
|
82
|
+
--enable-stellar-rpc
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The quickstart bundle includes:
|
|
86
|
+
- **Stellar Core** — validates and applies transactions
|
|
87
|
+
- **Horizon** — REST API at `http://localhost:8000`
|
|
88
|
+
- **Stellar RPC** — contract invocation at `http://localhost:8000/soroban/rpc`
|
|
89
|
+
- **Friendbot** — testnet faucet at `http://localhost:8000/friendbot`
|
|
90
|
+
|
|
91
|
+
Verify: `curl http://localhost:8000/`
|
|
92
|
+
|
|
93
|
+
### Step 6: Configure Stellar CLI Networks
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Add local network
|
|
97
|
+
stellar network add local \
|
|
98
|
+
--rpc-url http://localhost:8000/soroban/rpc \
|
|
99
|
+
--network-passphrase "Standalone Network ; February 2017"
|
|
100
|
+
|
|
101
|
+
# Testnet is pre-configured, but you can add it explicitly:
|
|
102
|
+
stellar network add testnet \
|
|
103
|
+
--rpc-url https://soroban-testnet.stellar.org \
|
|
104
|
+
--network-passphrase "Test SDF Network ; September 2015"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 7: Create Development Keypairs
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Generate a new keypair and fund it
|
|
111
|
+
stellar keys generate dev-account --network local
|
|
112
|
+
stellar keys fund dev-account --network local
|
|
113
|
+
|
|
114
|
+
# View the address
|
|
115
|
+
stellar keys address dev-account
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Step 8: Install JS/TS SDK (if applicable)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install @stellar/stellar-sdk
|
|
122
|
+
# or
|
|
123
|
+
yarn add @stellar/stellar-sdk
|
|
124
|
+
# or
|
|
125
|
+
bun add @stellar/stellar-sdk
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Step 9: Install Python SDK (if applicable)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install stellar-sdk
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Step 8b: Install TypeScript Wallet SDK (optional)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
yarn add @stellar/typescript-wallet-sdk
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Step 10: End-to-End Verification
|
|
141
|
+
|
|
142
|
+
Deploy the hello-world contract to local network:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
stellar contract init hello-world
|
|
146
|
+
cd hello-world
|
|
147
|
+
stellar contract build
|
|
148
|
+
stellar contract deploy \
|
|
149
|
+
--wasm target/wasm32-unknown-unknown/release/hello_world.wasm \
|
|
150
|
+
--source dev-account \
|
|
151
|
+
--network local
|
|
152
|
+
stellar contract invoke \
|
|
153
|
+
--id {contract_id} \
|
|
154
|
+
--source dev-account \
|
|
155
|
+
--network local \
|
|
156
|
+
-- hello --to World
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Expected output: `["Hello, World!"]`
|
|
160
|
+
|
|
161
|
+
### Step 11: Document Setup
|
|
162
|
+
|
|
163
|
+
Save environment details to `{project-root}/_stellar-output/project-knowledge/environment-guide.md`.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-setup-trustline
|
|
3
|
+
description: 'Set up trustlines for custom Stellar assets, handle authorization for regulated assets, and manage trustline limits. Use when the user needs to configure an account to hold a custom Stellar asset.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Set Up Trustline
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Configure trustlines so accounts can hold and receive custom Stellar assets.
|
|
11
|
+
|
|
12
|
+
## On Activation
|
|
13
|
+
|
|
14
|
+
Load `{network_preference}` from `{project-root}/_stellar/stellar/config.yaml`.
|
|
15
|
+
|
|
16
|
+
## Concepts
|
|
17
|
+
|
|
18
|
+
- A **trustline** is an explicit opt-in by an account to hold a specific custom asset.
|
|
19
|
+
- The account must maintain enough XLM to cover the **base reserve** (0.5 XLM per trustline).
|
|
20
|
+
- If the issuing account has `AUTH_REQUIRED` set, the issuer must separately authorize each trustline.
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
### Step 1: Gather Information
|
|
25
|
+
|
|
26
|
+
Collect from user:
|
|
27
|
+
- Asset code and issuer public key (e.g., `USDC:GABCDE...`)
|
|
28
|
+
- Account that needs the trustline
|
|
29
|
+
- Trust limit (or leave blank for maximum: `922337203685.4775807`)
|
|
30
|
+
- Does the asset have `AUTH_REQUIRED`? (check via Horizon: `GET /accounts/{issuer}`)
|
|
31
|
+
|
|
32
|
+
### Step 2: Check Account Balance
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const account = await server.loadAccount(accountPublicKey);
|
|
36
|
+
const xlmBalance = account.balances.find(b => b.asset_type === 'native');
|
|
37
|
+
console.log('XLM balance:', xlmBalance?.balance);
|
|
38
|
+
// Ensure at least 0.5 XLM above minimum reserve
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Step 3: Create Trustline
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const asset = new StellarSDK.Asset(assetCode, issuerPublicKey);
|
|
45
|
+
|
|
46
|
+
const tx = new StellarSDK.TransactionBuilder(account, {
|
|
47
|
+
fee: StellarSDK.BASE_FEE,
|
|
48
|
+
networkPassphrase: StellarSDK.Networks.TESTNET,
|
|
49
|
+
})
|
|
50
|
+
.addOperation(
|
|
51
|
+
StellarSDK.Operation.changeTrust({
|
|
52
|
+
asset,
|
|
53
|
+
limit: '1000000', // omit for maximum limit
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
.setTimeout(30)
|
|
57
|
+
.build();
|
|
58
|
+
|
|
59
|
+
tx.sign(accountKeypair);
|
|
60
|
+
await server.submitTransaction(tx);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Step 4: Authorize Trustline (AUTH_REQUIRED Assets)
|
|
64
|
+
|
|
65
|
+
If the asset has `AUTH_REQUIRED`, the issuer must authorize each new trustline:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const issuerAccount = await server.loadAccount(issuerPublicKey);
|
|
69
|
+
|
|
70
|
+
const authTx = new StellarSDK.TransactionBuilder(issuerAccount, {
|
|
71
|
+
fee: StellarSDK.BASE_FEE,
|
|
72
|
+
networkPassphrase: StellarSDK.Networks.TESTNET,
|
|
73
|
+
})
|
|
74
|
+
.addOperation(
|
|
75
|
+
StellarSDK.Operation.setTrustLineFlags({
|
|
76
|
+
trustor: accountPublicKey,
|
|
77
|
+
asset,
|
|
78
|
+
flags: {
|
|
79
|
+
authorized: true,
|
|
80
|
+
authorizedToMaintainLiabilities: false,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
)
|
|
84
|
+
.setTimeout(30)
|
|
85
|
+
.build();
|
|
86
|
+
|
|
87
|
+
authTx.sign(issuerKeypair);
|
|
88
|
+
await server.submitTransaction(authTx);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 5: Verify
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const updatedAccount = await server.loadAccount(accountPublicKey);
|
|
95
|
+
const trustline = updatedAccount.balances.find(
|
|
96
|
+
b => b.asset_code === assetCode && b.asset_issuer === issuerPublicKey
|
|
97
|
+
);
|
|
98
|
+
console.log('Trustline:', trustline);
|
|
99
|
+
// { balance: '0.0000000', limit: '1000000.0000000', is_authorized: true }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Step 6: Remove Trustline (if needed)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Set limit to '0' to remove — only works if balance is zero
|
|
106
|
+
StellarSDK.Operation.changeTrust({ asset, limit: '0' })
|
|
107
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-test-contract
|
|
3
|
+
description: 'Write and run comprehensive Soroban contract tests using the Rust test harness and soroban_sdk testutils. Use when the user wants to test a Soroban smart contract.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test Soroban Contract
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Achieve comprehensive contract coverage using the Soroban Rust test framework.
|
|
11
|
+
|
|
12
|
+
## On Activation
|
|
13
|
+
|
|
14
|
+
Load config from `{project-root}/_stellar/stellar/config.yaml`.
|
|
15
|
+
|
|
16
|
+
## Core Testing Patterns
|
|
17
|
+
|
|
18
|
+
### Test Environment Setup
|
|
19
|
+
|
|
20
|
+
```rust
|
|
21
|
+
#[cfg(test)]
|
|
22
|
+
mod tests {
|
|
23
|
+
use super::*;
|
|
24
|
+
use soroban_sdk::{testutils::Address as _, Address, Env};
|
|
25
|
+
|
|
26
|
+
fn setup() -> (Env, Address, MyContractClient<'static>) {
|
|
27
|
+
let env = Env::default();
|
|
28
|
+
env.mock_all_auths();
|
|
29
|
+
let contract_id = env.register(MyContract, ());
|
|
30
|
+
let client = MyContractClient::new(&env, &contract_id);
|
|
31
|
+
let admin = Address::generate(&env);
|
|
32
|
+
(env, admin, client)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[test]
|
|
36
|
+
fn test_initialize_sets_admin() {
|
|
37
|
+
let (env, admin, client) = setup();
|
|
38
|
+
client.initialize(&admin);
|
|
39
|
+
assert_eq!(client.get_admin(), admin);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Authorization Testing
|
|
45
|
+
|
|
46
|
+
```rust
|
|
47
|
+
#[test]
|
|
48
|
+
fn test_transfer_requires_from_auth() {
|
|
49
|
+
let env = Env::default();
|
|
50
|
+
env.mock_all_auths();
|
|
51
|
+
let contract_id = env.register(MyContract, ());
|
|
52
|
+
let client = MyContractClient::new(&env, &contract_id);
|
|
53
|
+
let from = Address::generate(&env);
|
|
54
|
+
let to = Address::generate(&env);
|
|
55
|
+
|
|
56
|
+
client.transfer(&from, &to, &100i128);
|
|
57
|
+
|
|
58
|
+
// Verify the authorization was required
|
|
59
|
+
assert_auth!(env, from, contract_id, "transfer");
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Error Path Testing
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
#[test]
|
|
67
|
+
fn test_withdraw_fails_when_insufficient_balance() {
|
|
68
|
+
let (env, admin, client) = setup();
|
|
69
|
+
client.initialize(&admin);
|
|
70
|
+
let user = Address::generate(&env);
|
|
71
|
+
|
|
72
|
+
let result = client.try_withdraw(&user, &1000i128);
|
|
73
|
+
assert_eq!(result, Err(Ok(ContractError::InsufficientBalance)));
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Event Testing
|
|
78
|
+
|
|
79
|
+
```rust
|
|
80
|
+
#[test]
|
|
81
|
+
fn test_deposit_emits_event() {
|
|
82
|
+
let (env, admin, client) = setup();
|
|
83
|
+
client.initialize(&admin);
|
|
84
|
+
let user = Address::generate(&env);
|
|
85
|
+
|
|
86
|
+
client.deposit(&user, &500i128);
|
|
87
|
+
|
|
88
|
+
let events = env.events().all();
|
|
89
|
+
assert_eq!(events.len(), 1);
|
|
90
|
+
// Assert event data matches expected values
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Storage Expiration Testing
|
|
95
|
+
|
|
96
|
+
```rust
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_persistent_storage_survives_archival() {
|
|
99
|
+
let env = Env::default();
|
|
100
|
+
env.mock_all_auths();
|
|
101
|
+
let contract_id = env.register(MyContract, ());
|
|
102
|
+
let client = MyContractClient::new(&env, &contract_id);
|
|
103
|
+
|
|
104
|
+
// Simulate ledger advancement
|
|
105
|
+
env.ledger().with_mut(|l| {
|
|
106
|
+
l.sequence_number += 100_000;
|
|
107
|
+
l.timestamp += 100_000;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Persistent data should still be accessible
|
|
111
|
+
let balance = client.get_balance(&Address::generate(&env));
|
|
112
|
+
assert_eq!(balance, 0i128);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Workflow
|
|
117
|
+
|
|
118
|
+
### Step 1: Identify Test Cases
|
|
119
|
+
|
|
120
|
+
For each contract function, enumerate:
|
|
121
|
+
- Happy path (valid inputs, expected output)
|
|
122
|
+
- Authorization failures (wrong signer, missing auth)
|
|
123
|
+
- Input validation failures (negative amount, zero, overflow)
|
|
124
|
+
- State-dependent failures (already initialized, not initialized, paused)
|
|
125
|
+
- Edge cases (max values, empty collections)
|
|
126
|
+
|
|
127
|
+
### Step 2: Implement Tests
|
|
128
|
+
|
|
129
|
+
Write one test per scenario. Test names must describe expected behavior:
|
|
130
|
+
`test_deposit_fails_when_paused` — not `test_deposit_2`.
|
|
131
|
+
|
|
132
|
+
### Step 3: Run Tests
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cargo test
|
|
136
|
+
cargo test -- --nocapture # for event/log debugging
|
|
137
|
+
cargo test test_deposit # run specific test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Step 4: Coverage Review
|
|
141
|
+
|
|
142
|
+
Walk through each storage key and each event emission — confirm a test exercises each path.
|
|
143
|
+
|
|
144
|
+
### Step 5: Next Steps
|
|
145
|
+
|
|
146
|
+
After tests pass, proceed to `stellar-deploy-contract` for testnet deployment.
|