@goplausible/openclaw-algorand-plugin 1.1.0 → 1.2.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/openclaw.plugin.json +4 -2
- package/package.json +1 -1
- package/skills/algorand-interaction/SKILL.md +4 -2
- package/skills/algorand-interaction/references/algorand-mcp.md +49 -2
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +49 -0
- package/skills/haystack-router-development/SKILL.md +85 -0
- package/skills/haystack-router-development/references/api-reference.md +381 -0
- package/skills/haystack-router-development/references/configuration.md +184 -0
- package/skills/haystack-router-development/references/fees-and-referrals.md +91 -0
- package/skills/haystack-router-development/references/getting-started.md +93 -0
- package/skills/haystack-router-development/references/migration.md +53 -0
- package/skills/haystack-router-development/references/node-automation.md +113 -0
- package/skills/haystack-router-development/references/quotes.md +155 -0
- package/skills/haystack-router-development/references/react-integration.md +260 -0
- package/skills/haystack-router-development/references/swaps.md +161 -0
- package/skills/haystack-router-interaction/SKILL.md +146 -0
- package/skills/haystack-router-interaction/references/configuration.md +53 -0
- package/skills/haystack-router-interaction/references/getting-started.md +48 -0
- package/skills/haystack-router-interaction/references/node-automation.md +51 -0
- package/skills/haystack-router-interaction/references/quotes.md +80 -0
- package/skills/haystack-router-interaction/references/swaps.md +84 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Configuration (SDK)
|
|
2
|
+
|
|
3
|
+
## API Key Tiers
|
|
4
|
+
|
|
5
|
+
| Tier | Key | Rate Limit | Use Case |
|
|
6
|
+
| -------------- | -------------------------------------- | --------------- | --------------------------------------------- |
|
|
7
|
+
| **Free** | `1b72df7e-1131-4449-8ce1-29b79dd3f51e` | 60 requests/min | Development, testing, low-volume integrations |
|
|
8
|
+
| **Production** | Request from support@txnlab.dev | Higher limits | Production applications |
|
|
9
|
+
|
|
10
|
+
The free tier key requires no registration and works immediately. The rate limit applies to all API calls (both `fetchQuote` and `fetchExecuteSwapTxns`), not just quotes.
|
|
11
|
+
|
|
12
|
+
For production integrations with higher rate limits, contact support@txnlab.dev for a dedicated key.
|
|
13
|
+
|
|
14
|
+
## RouterClient Options
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
18
|
+
|
|
19
|
+
const router = new RouterClient({
|
|
20
|
+
// Required
|
|
21
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
22
|
+
|
|
23
|
+
// Optional
|
|
24
|
+
apiBaseUrl: undefined, // Override API endpoint (SDK manages defaults)
|
|
25
|
+
algodUri: undefined, // Algod node URI (default: MainNet via Nodely)
|
|
26
|
+
algodToken: undefined, // Algod node token
|
|
27
|
+
algodPort: undefined, // Algod node port (default: 443)
|
|
28
|
+
referrerAddress: undefined, // Earn 25% of swap fees
|
|
29
|
+
feeBps: undefined, // Fee in basis points (default: 10, max: 300)
|
|
30
|
+
autoOptIn: false, // Auto-detect asset opt-in needs
|
|
31
|
+
debugLevel: 'none', // Logging: 'none' | 'info' | 'debug' | 'trace'
|
|
32
|
+
middleware: [], // SwapMiddleware plugins
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Network Configuration
|
|
37
|
+
|
|
38
|
+
### MainNet (Default)
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const router = new RouterClient({
|
|
42
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e',
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Uses default Nodely MainNet algod endpoint.
|
|
47
|
+
|
|
48
|
+
### TestNet
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const router = new RouterClient({
|
|
52
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
53
|
+
algodUri: 'https://testnet-api.4160.nodely.dev/',
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Custom Algod Node
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const router = new RouterClient({
|
|
61
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
62
|
+
algodUri: 'http://localhost:4001',
|
|
63
|
+
algodToken:
|
|
64
|
+
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
65
|
+
algodPort: 4001,
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Slippage
|
|
70
|
+
|
|
71
|
+
Slippage is a percentage tolerance on the output amount. Set per-swap, not on the client.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const swap = await router.newSwap({
|
|
75
|
+
quote,
|
|
76
|
+
address,
|
|
77
|
+
signer,
|
|
78
|
+
slippage: 1, // 1% — receive at least 99% of quoted output
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Recommendations:**
|
|
83
|
+
|
|
84
|
+
- **Stable pairs** (ALGO/USDC): 0.5–1%
|
|
85
|
+
- **Volatile pairs**: 1–3%
|
|
86
|
+
- **Low liquidity**: 3–5%
|
|
87
|
+
|
|
88
|
+
Slippage is verified on the **final output** of the swap, not on individual hops. This means intermediate steps can have higher variance as long as the final result is within tolerance.
|
|
89
|
+
|
|
90
|
+
## Fee Configuration
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const router = new RouterClient({
|
|
94
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
95
|
+
feeBps: 15, // 0.15% output fee
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- **Default**: 10 bps (0.10%)
|
|
100
|
+
- **Range**: 10–300 bps (0.10%–3.00%)
|
|
101
|
+
- Fee is applied to the **output amount**
|
|
102
|
+
- Additional network transaction fees apply (paid by swapper)
|
|
103
|
+
|
|
104
|
+
## Referrer Address
|
|
105
|
+
|
|
106
|
+
Earn 25% of swap fees by setting a referrer address:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const router = new RouterClient({
|
|
110
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
111
|
+
referrerAddress: 'YOUR_ALGORAND_ADDRESS',
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
See [fees-and-referrals.md](fees-and-referrals.md) for details on the referral program.
|
|
116
|
+
|
|
117
|
+
## Auto Opt-In
|
|
118
|
+
|
|
119
|
+
When `autoOptIn: true`, the SDK automatically checks if the user needs to opt into the output asset and includes the opt-in transaction in the swap group.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const router = new RouterClient({
|
|
123
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
124
|
+
autoOptIn: true,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Address is required for auto opt-in detection
|
|
128
|
+
const quote = await router.newQuote({
|
|
129
|
+
fromASAID: 0,
|
|
130
|
+
toASAID: 31566704,
|
|
131
|
+
amount: 1_000_000,
|
|
132
|
+
address: activeAddress,
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Middleware
|
|
137
|
+
|
|
138
|
+
Plugins that hook into the quote and swap lifecycle:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { RouterClient, AutoOptOutMiddleware } from '@txnlab/haystack-router'
|
|
142
|
+
|
|
143
|
+
// Built-in: auto opt-out when swapping full balance
|
|
144
|
+
const autoOptOut = new AutoOptOutMiddleware({
|
|
145
|
+
excludedAssets: [31566704], // Never auto-opt-out of USDC
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const router = new RouterClient({
|
|
149
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
150
|
+
middleware: [autoOptOut],
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
See [api-reference.md](api-reference.md) for the `SwapMiddleware` interface.
|
|
155
|
+
|
|
156
|
+
## Debug Logging
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const router = new RouterClient({
|
|
160
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
161
|
+
debugLevel: 'info',
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
| Level | Output |
|
|
166
|
+
| ------- | ------------------------------------------------------------------ |
|
|
167
|
+
| `none` | No logging (default) |
|
|
168
|
+
| `info` | High-level operations (quote fetched, swap submitted) |
|
|
169
|
+
| `debug` | Detailed flow (middleware applied, validation, status transitions) |
|
|
170
|
+
| `trace` | Everything including request/response payloads |
|
|
171
|
+
|
|
172
|
+
## Finding ASA IDs
|
|
173
|
+
|
|
174
|
+
Common Algorand Standard Asset IDs:
|
|
175
|
+
|
|
176
|
+
| Asset | ASA ID |
|
|
177
|
+
| ----- | --------- |
|
|
178
|
+
| ALGO | 0 |
|
|
179
|
+
| USDC | 31566704 |
|
|
180
|
+
| USDt | 312769 |
|
|
181
|
+
| goBTC | 386192725 |
|
|
182
|
+
| goETH | 386195940 |
|
|
183
|
+
|
|
184
|
+
Look up ASA IDs on [Allo.info](https://allo.info) or [Pera Explorer](https://explorer.perawallet.app/).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Fees and Referrals
|
|
2
|
+
|
|
3
|
+
## Fee Structure
|
|
4
|
+
|
|
5
|
+
### Swap Fees
|
|
6
|
+
|
|
7
|
+
- **Default fee**: 10 basis points (0.10%) on the **output** amount
|
|
8
|
+
- **Configurable**: 10–300 bps via the `feeBps` parameter
|
|
9
|
+
- **Positive slippage**: When a swap executes better than quoted, the excess is captured as additional revenue
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const router = new RouterClient({
|
|
13
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
14
|
+
feeBps: 15, // 0.15%
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Network Fees
|
|
19
|
+
|
|
20
|
+
Swappers pay all Algorand network transaction fees (min fee per transaction in the group). These are separate from the protocol output fee.
|
|
21
|
+
|
|
22
|
+
### Fee Distribution
|
|
23
|
+
|
|
24
|
+
| Recipient | Share |
|
|
25
|
+
| --------- | ----- |
|
|
26
|
+
| Protocol | 75% |
|
|
27
|
+
| Referrer | 25% |
|
|
28
|
+
|
|
29
|
+
## Referral Program
|
|
30
|
+
|
|
31
|
+
Integrators earn **25% of swap fees** generated by their users.
|
|
32
|
+
|
|
33
|
+
### Setup
|
|
34
|
+
|
|
35
|
+
Pass your Algorand address as the `referrerAddress` when creating the RouterClient:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const router = new RouterClient({
|
|
39
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
40
|
+
referrerAddress: 'YOUR_ALGORAND_ADDRESS',
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Every swap executed through this client generates referral commissions.
|
|
45
|
+
|
|
46
|
+
### How It Works
|
|
47
|
+
|
|
48
|
+
1. Fees from each swap are deposited into an **escrow account** linked to your referrer address
|
|
49
|
+
2. The escrow is an on-chain account **rekeyed to the protocol app** — only your address can claim from it
|
|
50
|
+
3. When new assets are swapped, the escrow automatically opts into them
|
|
51
|
+
4. Commissions accumulate across all assets traded
|
|
52
|
+
|
|
53
|
+
### Claiming Commissions
|
|
54
|
+
|
|
55
|
+
Claim accumulated commissions through the referrer dashboard:
|
|
56
|
+
|
|
57
|
+
- **Dashboard**: Register and claim at the referrer portal
|
|
58
|
+
- **Bulk claim**: Withdraw multiple asset commissions simultaneously
|
|
59
|
+
- **Single claim**: Withdraw a specific asset, optionally closing out the position
|
|
60
|
+
|
|
61
|
+
### Auto-Conversion (Optional)
|
|
62
|
+
|
|
63
|
+
Optionally convert accumulated commissions to a target asset (ALGO, USDC, etc.):
|
|
64
|
+
|
|
65
|
+
- **Disabled by default** — requires explicit opt-in
|
|
66
|
+
- **1% convenience fee** on auto-conversion swaps
|
|
67
|
+
- Runs daily when enabled
|
|
68
|
+
- Select any target asset for conversion
|
|
69
|
+
- Can be enabled/disabled at any time
|
|
70
|
+
|
|
71
|
+
### Referral Escrow Details
|
|
72
|
+
|
|
73
|
+
- Each referrer gets a unique escrow account
|
|
74
|
+
- Escrow is **permanently linked** once created (cannot be unlinked)
|
|
75
|
+
- The escrow is controlled by the protocol smart contract
|
|
76
|
+
- Only the linked referrer address can claim funds
|
|
77
|
+
- Minimum 0.257 ALGO required for escrow Minimum Balance Requirement (MBR)
|
|
78
|
+
|
|
79
|
+
## Protocol Contracts
|
|
80
|
+
|
|
81
|
+
### MainNet (Latest: V2.2)
|
|
82
|
+
|
|
83
|
+
- **Application ID**: 3172554435
|
|
84
|
+
- **Contract Address**: `ESPO2SMA5BPAAAFAN66EFNHGR3HJ63U2OYM6RJXSEP6YOBU4HJTWZG7D5A`
|
|
85
|
+
|
|
86
|
+
### Security
|
|
87
|
+
|
|
88
|
+
The protocol has been audited by:
|
|
89
|
+
|
|
90
|
+
- **Ulam Labs** (April 2023)
|
|
91
|
+
- **Vantage Point** (November 2022)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- **Node.js** >= 20
|
|
6
|
+
- **algosdk** 3.x (peer dependency)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @txnlab/haystack-router algosdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## RouterClient Initialization
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
18
|
+
|
|
19
|
+
const router = new RouterClient({
|
|
20
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### With Options
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const router = new RouterClient({
|
|
28
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
29
|
+
autoOptIn: true, // Auto-detect asset opt-in needs
|
|
30
|
+
referrerAddress: 'ABC...', // Earn 25% of swap fees
|
|
31
|
+
feeBps: 15, // Fee in basis points (default: 10)
|
|
32
|
+
debugLevel: 'info', // 'none' | 'info' | 'debug' | 'trace'
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### TestNet
|
|
37
|
+
|
|
38
|
+
Override the algod connection and API base URL for TestNet:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const router = new RouterClient({
|
|
42
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
43
|
+
algodUri: 'https://testnet-api.4160.nodely.dev/',
|
|
44
|
+
// Set apiBaseUrl if using a TestNet-specific API endpoint
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
52
|
+
|
|
53
|
+
const router = new RouterClient({
|
|
54
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Get a quote: swap 1 ALGO → USDC
|
|
58
|
+
const quote = await router.newQuote({
|
|
59
|
+
fromASAID: 0, // ALGO
|
|
60
|
+
toASAID: 31566704, // USDC
|
|
61
|
+
amount: 1_000_000, // 1 ALGO in microAlgos
|
|
62
|
+
address: activeAddress,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
console.log(`Expected output: ${quote.quote} microUSDC`)
|
|
66
|
+
console.log(`USD value: $${quote.usdOut}`)
|
|
67
|
+
|
|
68
|
+
// Execute the swap (use-wallet signer for browser, custom signer for Node.js)
|
|
69
|
+
const swap = await router.newSwap({
|
|
70
|
+
quote,
|
|
71
|
+
address: activeAddress,
|
|
72
|
+
signer: transactionSigner,
|
|
73
|
+
slippage: 1, // 1% slippage tolerance
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const result = await swap.execute()
|
|
77
|
+
console.log(`Confirmed in round ${result.confirmedRound}`)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Amounts and Units
|
|
81
|
+
|
|
82
|
+
All amounts are in **base units** (smallest denomination):
|
|
83
|
+
|
|
84
|
+
| Asset | Decimals | 1 unit in base | Example |
|
|
85
|
+
| ------------------- | -------- | -------------- | -------------------- |
|
|
86
|
+
| ALGO (ASA 0) | 6 | 1,000,000 | `1_000_000` = 1 ALGO |
|
|
87
|
+
| USDC (ASA 31566704) | 6 | 1,000,000 | `5_000_000` = 5 USDC |
|
|
88
|
+
|
|
89
|
+
Convert human-readable amounts:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const amount = BigInt(Math.floor(parseFloat(userInput) * 10 ** decimals))
|
|
93
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Migration from @txnlab/deflex
|
|
2
|
+
|
|
3
|
+
## Package Rename
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm uninstall @txnlab/deflex
|
|
7
|
+
npm install @txnlab/haystack-router
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Import Changes
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// Before
|
|
14
|
+
import { DeflexClient } from '@txnlab/deflex'
|
|
15
|
+
|
|
16
|
+
// After
|
|
17
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Class and Type Renames
|
|
21
|
+
|
|
22
|
+
| Before (@txnlab/deflex) | After (@txnlab/haystack-router) |
|
|
23
|
+
| ----------------------- | ------------------------------- |
|
|
24
|
+
| `DeflexClient` | `RouterClient` |
|
|
25
|
+
| `DeflexQuote` | `SwapQuote` |
|
|
26
|
+
| `DeflexTransaction` | `SwapTransaction` |
|
|
27
|
+
| `DeflexConfig` | `Config` |
|
|
28
|
+
| `DeflexConfigParams` | `ConfigParams` |
|
|
29
|
+
| `DeflexSignature` | `Signature` |
|
|
30
|
+
|
|
31
|
+
## API Endpoint
|
|
32
|
+
|
|
33
|
+
The SDK automatically uses the updated API endpoint. No manual URL changes needed.
|
|
34
|
+
|
|
35
|
+
## Functionality
|
|
36
|
+
|
|
37
|
+
All functionality is identical — only naming changed. A find-and-replace of the class and type names is sufficient.
|
|
38
|
+
|
|
39
|
+
### Quick Migration
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Find all files that import from @txnlab/deflex
|
|
43
|
+
grep -r "@txnlab/deflex" --include="*.ts" --include="*.tsx" -l
|
|
44
|
+
|
|
45
|
+
# Replace imports
|
|
46
|
+
# @txnlab/deflex → @txnlab/haystack-router
|
|
47
|
+
# DeflexClient → RouterClient
|
|
48
|
+
# DeflexQuote → SwapQuote
|
|
49
|
+
# DeflexTransaction → SwapTransaction
|
|
50
|
+
# DeflexConfig → Config
|
|
51
|
+
# DeflexConfigParams → ConfigParams
|
|
52
|
+
# DeflexSignature → Signature
|
|
53
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Node.js Automation (SDK)
|
|
2
|
+
|
|
3
|
+
When building Node.js automation scripts that integrate Haystack Router directly in application code:
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @txnlab/haystack-router algosdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment Variables
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# .env
|
|
15
|
+
HAYSTACK_API_KEY=1b72df7e-1131-4449-8ce1-29b79dd3f51e # Free tier (60 requests/min)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Complete Example
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import algosdk from 'algosdk'
|
|
22
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const apiKey = process.env.HAYSTACK_API_KEY!
|
|
26
|
+
|
|
27
|
+
// For application code, use use-wallet or AlgorandClient for signing
|
|
28
|
+
// For Node.js scripts, create a custom signer:
|
|
29
|
+
const account = algosdk.mnemonicToSecretKey(process.env.MNEMONIC!)
|
|
30
|
+
const address = account.addr.toString()
|
|
31
|
+
|
|
32
|
+
const signer = async (
|
|
33
|
+
txnGroup: algosdk.Transaction[],
|
|
34
|
+
indexesToSign: number[],
|
|
35
|
+
): Promise<Uint8Array[]> => {
|
|
36
|
+
return indexesToSign.map(
|
|
37
|
+
(index) => algosdk.signTransaction(txnGroup[index], account.sk).blob,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Initialize router
|
|
42
|
+
const router = new RouterClient({
|
|
43
|
+
apiKey,
|
|
44
|
+
autoOptIn: true,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Get quote: 1 ALGO → USDC
|
|
48
|
+
const quote = await router.newQuote({
|
|
49
|
+
fromASAID: 0,
|
|
50
|
+
toASAID: 31566704,
|
|
51
|
+
amount: 1_000_000,
|
|
52
|
+
address,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
console.log(`Expected output: ${Number(quote.quote) / 1e6} USDC`)
|
|
56
|
+
console.log(`USD value: $${quote.usdOut.toFixed(2)}`)
|
|
57
|
+
|
|
58
|
+
// Execute swap
|
|
59
|
+
const swap = await router.newSwap({
|
|
60
|
+
quote,
|
|
61
|
+
address,
|
|
62
|
+
signer,
|
|
63
|
+
slippage: 1,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const result = await swap.execute()
|
|
67
|
+
console.log(`Confirmed in round ${result.confirmedRound}`)
|
|
68
|
+
console.log(`Transaction IDs: ${result.txIds.join(', ')}`)
|
|
69
|
+
|
|
70
|
+
const summary = swap.getSummary()
|
|
71
|
+
if (summary) {
|
|
72
|
+
console.log(`Input: ${summary.inputAmount} microunits`)
|
|
73
|
+
console.log(`Output: ${summary.outputAmount} microunits`)
|
|
74
|
+
console.log(`Fees: ${summary.totalFees} microAlgos`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main().catch(console.error)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tracking with Notes
|
|
82
|
+
|
|
83
|
+
Attach identifiers to transactions for backend tracking:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const swap = await router.newSwap({
|
|
87
|
+
quote,
|
|
88
|
+
address,
|
|
89
|
+
signer,
|
|
90
|
+
slippage: 1,
|
|
91
|
+
note: new TextEncoder().encode(
|
|
92
|
+
JSON.stringify({
|
|
93
|
+
orderId: 'order-123',
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await swap.execute()
|
|
100
|
+
const txId = swap.getInputTransactionId()
|
|
101
|
+
console.log(`Tracked: order-123 → ${txId}`)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Debug Logging
|
|
105
|
+
|
|
106
|
+
Enable verbose logging for troubleshooting:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const router = new RouterClient({
|
|
110
|
+
apiKey,
|
|
111
|
+
debugLevel: 'debug', // 'none' | 'info' | 'debug' | 'trace'
|
|
112
|
+
})
|
|
113
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Quotes
|
|
2
|
+
|
|
3
|
+
## Getting a Quote
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { RouterClient } from '@txnlab/haystack-router'
|
|
7
|
+
|
|
8
|
+
const router = new RouterClient({
|
|
9
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const quote = await router.newQuote({
|
|
13
|
+
fromASAID: 0, // ALGO
|
|
14
|
+
toASAID: 31566704, // USDC
|
|
15
|
+
amount: 1_000_000, // 1 ALGO in base units
|
|
16
|
+
address: activeAddress,
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Parameters
|
|
21
|
+
|
|
22
|
+
| Parameter | Type | Required | Description |
|
|
23
|
+
| ------------------- | ------------------ | -------- | ----------------------------------------------- |
|
|
24
|
+
| `fromASAID` | `number \| bigint` | Yes | Input asset ID (0 = ALGO) |
|
|
25
|
+
| `toASAID` | `number \| bigint` | Yes | Output asset ID |
|
|
26
|
+
| `amount` | `number \| bigint` | Yes | Amount in base units |
|
|
27
|
+
| `type` | `string` | No | `'fixed-input'` (default) or `'fixed-output'` |
|
|
28
|
+
| `address` | `string` | No | User address (needed for auto opt-in detection) |
|
|
29
|
+
| `maxGroupSize` | `number` | No | Max transactions in group (default: 16) |
|
|
30
|
+
| `maxDepth` | `number` | No | Max routing hops (default: 4) |
|
|
31
|
+
| `optIn` | `boolean` | No | Include opt-in transaction for output asset |
|
|
32
|
+
| `disabledProtocols` | `Protocol[]` | No | Protocols to exclude from routing |
|
|
33
|
+
|
|
34
|
+
## Quote Response (SwapQuote)
|
|
35
|
+
|
|
36
|
+
`newQuote()` returns a `SwapQuote` (extends `FetchQuoteResponse`):
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
quote.quote // bigint — expected output amount in base units
|
|
40
|
+
quote.amount // bigint — original input amount
|
|
41
|
+
quote.usdIn // number — USD value of input
|
|
42
|
+
quote.usdOut // number — USD value of output
|
|
43
|
+
quote.userPriceImpact // number | undefined — price impact %
|
|
44
|
+
quote.marketPriceImpact // number | undefined — market price impact %
|
|
45
|
+
quote.route // Route[] — routing path details
|
|
46
|
+
quote.flattenedRoute // Record<string, number> — protocol split percentages
|
|
47
|
+
quote.quotes // DexQuote[] — individual DEX quotes
|
|
48
|
+
quote.requiredAppOptIns // number[] — app IDs needing opt-in
|
|
49
|
+
quote.createdAt // number — timestamp (ms)
|
|
50
|
+
quote.address // string | undefined — user address if provided
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quote Types
|
|
54
|
+
|
|
55
|
+
**Fixed-input** (default): Specify exact input amount, receive variable output.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const quote = await router.newQuote({
|
|
59
|
+
fromASAID: 0,
|
|
60
|
+
toASAID: 31566704,
|
|
61
|
+
amount: 1_000_000, // Exact: send 1 ALGO
|
|
62
|
+
type: 'fixed-input',
|
|
63
|
+
})
|
|
64
|
+
// quote.quote = expected USDC received
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Fixed-output**: Specify desired output, send variable input.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const quote = await router.newQuote({
|
|
71
|
+
fromASAID: 0,
|
|
72
|
+
toASAID: 31566704,
|
|
73
|
+
amount: 1_000_000, // Exact: receive 1 USDC
|
|
74
|
+
type: 'fixed-output',
|
|
75
|
+
})
|
|
76
|
+
// quote.quote = ALGO required to send
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Displaying Quote Data
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const fromDecimals = 6 // ALGO
|
|
83
|
+
const toDecimals = 6 // USDC
|
|
84
|
+
|
|
85
|
+
const outputHuman = Number(quote.quote) / 10 ** toDecimals
|
|
86
|
+
const inputHuman = Number(quote.amount) / 10 ** fromDecimals
|
|
87
|
+
const rate = outputHuman / inputHuman
|
|
88
|
+
|
|
89
|
+
console.log(`${inputHuman} ALGO → ${outputHuman} USDC`)
|
|
90
|
+
console.log(`Rate: 1 ALGO = ${rate.toFixed(4)} USDC`)
|
|
91
|
+
console.log(`USD in: $${quote.usdIn.toFixed(2)}`)
|
|
92
|
+
console.log(`USD out: $${quote.usdOut.toFixed(2)}`)
|
|
93
|
+
|
|
94
|
+
if (quote.userPriceImpact !== undefined) {
|
|
95
|
+
console.log(`Price impact: ${quote.userPriceImpact.toFixed(2)}%`)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Route Details
|
|
100
|
+
|
|
101
|
+
Each quote includes routing information showing how the swap is split:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Flattened view: protocol → percentage
|
|
105
|
+
for (const [protocol, pct] of Object.entries(quote.flattenedRoute)) {
|
|
106
|
+
console.log(`${protocol}: ${pct}%`)
|
|
107
|
+
}
|
|
108
|
+
// e.g., "TinymanV2: 60%", "Pact: 40%"
|
|
109
|
+
|
|
110
|
+
// Detailed route with hops
|
|
111
|
+
for (const route of quote.route) {
|
|
112
|
+
console.log(`${route.percentage}% of swap:`)
|
|
113
|
+
for (const hop of route.path) {
|
|
114
|
+
console.log(` ${hop.in.unit_name} → ${hop.out.unit_name} via ${hop.name}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Asset Opt-In Detection
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Option 1: Set autoOptIn on the client
|
|
123
|
+
const router = new RouterClient({
|
|
124
|
+
apiKey: '1b72df7e-1131-4449-8ce1-29b79dd3f51e', // Free tier (60 requests/min)
|
|
125
|
+
autoOptIn: true,
|
|
126
|
+
})
|
|
127
|
+
const quote = await router.newQuote({
|
|
128
|
+
fromASAID: 0,
|
|
129
|
+
toASAID: 31566704,
|
|
130
|
+
amount: 1_000_000,
|
|
131
|
+
address: activeAddress, // Required for auto opt-in
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// Option 2: Check manually and pass optIn flag
|
|
135
|
+
const needsOptIn = await router.needsAssetOptIn(activeAddress, 31566704)
|
|
136
|
+
const quote = await router.newQuote({
|
|
137
|
+
fromASAID: 0,
|
|
138
|
+
toASAID: 31566704,
|
|
139
|
+
amount: 1_000_000,
|
|
140
|
+
optIn: needsOptIn,
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Lower-Level: fetchQuote()
|
|
145
|
+
|
|
146
|
+
`fetchQuote()` returns the raw `FetchQuoteResponse` without `SwapQuote` enhancements (no bigint coercion, no `createdAt`). Use `newQuote()` unless you need the raw response.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const raw = await router.fetchQuote({
|
|
150
|
+
fromASAID: 0,
|
|
151
|
+
toASAID: 31566704,
|
|
152
|
+
amount: 1_000_000,
|
|
153
|
+
})
|
|
154
|
+
// raw.quote is string | number (not bigint)
|
|
155
|
+
```
|