@continuumdao/ctm-mpc-defi 0.2.55 → 0.2.57
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/dist/agent/catalog.cjs +692 -310
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +279 -99
- package/dist/agent/catalog.js +687 -312
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/aave-v4/SKILL.md +26 -3
- package/dist/agent/skills/aerodrome/SKILL.md +3 -1
- package/dist/agent/skills/circle-cctp/SKILL.md +5 -4
- package/dist/agent/skills/continuum-dao-marketplace/SKILL.md +4 -4
- package/dist/agent/skills/curve-dao/SKILL.md +2 -2
- package/dist/agent/skills/uniswap-v4/SKILL.md +2 -2
- package/dist/index.cjs +165 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +166 -20
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/aave-v4/index.cjs +198 -15
- package/dist/protocols/evm/aave-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/aave-v4/index.d.ts +62 -7
- package/dist/protocols/evm/aave-v4/index.js +189 -16
- package/dist/protocols/evm/aave-v4/index.js.map +1 -1
- package/dist/protocols/evm/aerodrome/index.cjs +152 -63
- package/dist/protocols/evm/aerodrome/index.cjs.map +1 -1
- package/dist/protocols/evm/aerodrome/index.d.ts +30 -58
- package/dist/protocols/evm/aerodrome/index.js +151 -64
- package/dist/protocols/evm/aerodrome/index.js.map +1 -1
- package/dist/protocols/evm/circle-cctp/index.cjs +77 -19
- package/dist/protocols/evm/circle-cctp/index.cjs.map +1 -1
- package/dist/protocols/evm/circle-cctp/index.d.ts +53 -3
- package/dist/protocols/evm/circle-cctp/index.js +76 -20
- package/dist/protocols/evm/circle-cctp/index.js.map +1 -1
- package/dist/protocols/evm/compound-v3/index.cjs +115 -2
- package/dist/protocols/evm/compound-v3/index.cjs.map +1 -1
- package/dist/protocols/evm/compound-v3/index.js +116 -3
- package/dist/protocols/evm/compound-v3/index.js.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.cjs +288 -32
- package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.d.ts +14 -1
- package/dist/protocols/evm/continuum-dao/index.js +287 -33
- package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
- package/dist/protocols/evm/curve-dao/index.cjs +120 -5
- package/dist/protocols/evm/curve-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/curve-dao/index.js +120 -5
- package/dist/protocols/evm/curve-dao/index.js.map +1 -1
- package/dist/protocols/evm/euler-v2/index.cjs +121 -6
- package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
- package/dist/protocols/evm/euler-v2/index.js +121 -6
- package/dist/protocols/evm/euler-v2/index.js.map +1 -1
- package/dist/protocols/evm/morpho/index.cjs +129 -57
- package/dist/protocols/evm/morpho/index.cjs.map +1 -1
- package/dist/protocols/evm/morpho/index.d.ts +2 -1
- package/dist/protocols/evm/morpho/index.js +129 -58
- package/dist/protocols/evm/morpho/index.js.map +1 -1
- package/dist/protocols/evm/pendle/index.cjs +129 -10
- package/dist/protocols/evm/pendle/index.cjs.map +1 -1
- package/dist/protocols/evm/pendle/index.js +129 -10
- package/dist/protocols/evm/pendle/index.js.map +1 -1
- package/dist/protocols/evm/uniswap-v4/index.cjs +164 -17
- package/dist/protocols/evm/uniswap-v4/index.cjs.map +1 -1
- package/dist/protocols/evm/uniswap-v4/index.js +165 -18
- package/dist/protocols/evm/uniswap-v4/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ Aave v4 organizes liquidity into **hubs** (market segments) and **spokes** (on-c
|
|
|
11
11
|
|
|
12
12
|
| Concept | What it is | Agent needs to know |
|
|
13
13
|
|---------|------------|---------------------|
|
|
14
|
-
| **Hub** | Off-chain market segment (Core, Plus, Prime) | Maps to UI `marketId`: `main` → Plus, `core` → Core, `bluechip` → Prime |
|
|
14
|
+
| **Hub** | Off-chain market segment (Core, Plus, Prime, Coinbase Stocks) | Maps to UI `marketId`: `main` → Plus, `core` → Core, `bluechip` → Prime, `bstocks` → Coinbase Stocks (Base tokenized equities) |
|
|
15
15
|
| **Spoke** | On-chain contract (`supply`, `withdraw`, `borrow`, `repay`) | **Do not guess.** The MCP server resolves spoke from the Aave v4 API using `chainId` + `underlying` + `marketId` |
|
|
16
16
|
| **Reserve** | One asset in one hub (underlying + APY + spoke) | From `ctm_aave_v4_fetch_markets` — do not web-search rates |
|
|
17
17
|
| **Position** | User’s supplies/debt on a spoke | Withdraw/borrow/repay risk depends on health factor on that spoke |
|
|
@@ -33,6 +33,7 @@ Do **not** web-search Aave APRs, APYs, or reserves. Load the protocol and use th
|
|
|
33
33
|
| User intent | MCP tool |
|
|
34
34
|
|-------------|----------|
|
|
35
35
|
| List reserves / lending APRs / compare rates | `ctm_aave_v4_fetch_markets` |
|
|
36
|
+
| Base tokenized stocks (bStocks) | `ctm_aave_v4_fetch_stock_markets` |
|
|
36
37
|
| One asset’s APY (optionally one hub) | `ctm_aave_v4_fetch_market` |
|
|
37
38
|
| Supply / deposit (lend) | `ctm_aave_v4_build_deposit_multisign` |
|
|
38
39
|
| Withdraw supplied balance | `ctm_aave_v4_build_withdraw_multisign` |
|
|
@@ -50,7 +51,7 @@ Do **not** web-search Aave APRs, APYs, or reserves. Load the protocol and use th
|
|
|
50
51
|
| `purposeText` | yes | Human-readable reason for the sign request |
|
|
51
52
|
| `underlying` | yes | Token address for the action (see per-action below) |
|
|
52
53
|
| `amountHuman` | yes | Human amount (e.g. `"0.5"`, `"100"`) |
|
|
53
|
-
| `marketId` | no | `main` (default), `core`, or `
|
|
54
|
+
| `marketId` | no | `main` (default), `core`, `bluechip`, or `bstocks` (aliases: `stocks`, `equities`) |
|
|
54
55
|
| `useCustomGas` | no | `true` to use chain registry gas config |
|
|
55
56
|
| `spoke` | no | **Omit** — server resolves from Aave API |
|
|
56
57
|
|
|
@@ -87,7 +88,7 @@ Do **not** web-search Aave APRs, APYs, or reserves. Load the protocol and use th
|
|
|
87
88
|
Example: “How do Aave lending APRs compare to Compound on Ethereum?”
|
|
88
89
|
|
|
89
90
|
1. `load_defi_protocol({ protocolId: "aave-v4" })` then `get_defi_protocol_skill`.
|
|
90
|
-
2. `ctm_aave_v4_fetch_markets` with `chainId` 1. Optional `marketId` (`main` / `core` / `bluechip`) or `underlying` to filter.
|
|
91
|
+
2. `ctm_aave_v4_fetch_markets` with `chainId` 1. Optional `marketId` (`main` / `core` / `bluechip` / `bstocks`) or `underlying` to filter. On Base, `ctm_aave_v4_fetch_stock_markets` lists the Coinbase Stocks hub.
|
|
91
92
|
3. Each row is one live spoke on a hub (unused zero-liquidity listings omitted): `supplyApyPercent`, `borrowApyPercent`, `utilizationPercent`, `availableLiquidity`. The same asset can appear on more than one spoke.
|
|
92
93
|
4. Aave reports **APY** (compounded). Compound v3 reports **APR** — say so when comparing.
|
|
93
94
|
5. Copy `underlying` + `marketId` for a later deposit/borrow. Do not invent rates from the web.
|
|
@@ -158,6 +159,28 @@ After stETH is supplied:
|
|
|
158
159
|
}
|
|
159
160
|
```
|
|
160
161
|
|
|
162
|
+
## bStocks (Base tokenized equities)
|
|
163
|
+
|
|
164
|
+
Aave v4 on Base (chainId **8453**) has a **Coinbase Stocks** hub. UI and MCP `marketId` is **`bstocks`**. The same hub resolver is used by the node app and by `ctm_aave_v4_fetch_stock_markets` / `build_*_multisign`.
|
|
165
|
+
|
|
166
|
+
- Collateral is whatever the Coinbase Stocks hub currently lists (`ctm_aave_v4_fetch_stock_markets`). Pass a ticker such as `AAPLc`; it resolves from the live Coinbase catalog. Equity reserves are **not borrowable**.
|
|
167
|
+
- Borrow asset: **USDC** only. Supply the stock with `marketId: "bstocks"`, then borrow USDC. `collateralUnderlying` may be the stock ticker.
|
|
168
|
+
- Chainlink equity feeds publish 24/5 (Sunday 20:00 ET through Friday 20:00 ET) and hold the last price over weekends and US market holidays. Health factor can still fall from interest while the feed is stale.
|
|
169
|
+
- Coinbase Tokenized Stocks are not for U.S. persons.
|
|
170
|
+
- Do not treat `main` as this hub. Base has no Plus hub; `main` does not select Coinbase Stocks.
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"keyGenId": "<keygen-id>",
|
|
175
|
+
"chainId": 8453,
|
|
176
|
+
"purposeText": "Supply 1 AAPLc to Aave v4 bStocks",
|
|
177
|
+
"underlying": "AAPLc",
|
|
178
|
+
"marketId": "bstocks",
|
|
179
|
+
"amountHuman": "1",
|
|
180
|
+
"enableAsCollateralAfterSupply": true
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
161
184
|
## Notes
|
|
162
185
|
|
|
163
186
|
- Native supply uses wrapped native from `get_defi_protocol_supported_tokens` (`nativeWrapped`).
|
|
@@ -86,7 +86,9 @@ Skip emissions when `claimEmissions` is `null` (no gauge). Slipstream unstaked f
|
|
|
86
86
|
|
|
87
87
|
Loaded from official GitHub (`velodrome-finance/sugar` `deployments/base.env` and `aerodrome-finance/docs` security.mdx) with baked fallbacks. Do not invent addresses.
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Aero (the Aerodrome rename) is scheduled for **2026-10-22 00:00 UTC** on Base, Ethereum, OP Mainnet, Arc, Ink, Robinhood Chain, and Arbitrum. Those contract addresses are not published yet, so quotes and writes stay on the live Base deployment (`chainId` 8453). Tokenized stocks stay on Base even after that launch.
|
|
90
|
+
|
|
91
|
+
Coinbase B20 stocks (AAPLc, NVDAc, PYPLc, AMDc, ORCLc, BRK.Bc, …) trade on **Slipstream 3** (`clFactory3`) vs USDC. Tickers resolve from the live Coinbase tokenized-stocks API. There is no baked-in address list. Pass the ticker — do not ask for a contract address. Stock-backed **loans** (lend/borrow USDC against B20 collateral) are Morpho — `load_defi_protocol morpho` and `ctm_morpho_fetch_stock_markets`, not Aerodrome LP. Sugar tokens whose address uses the B20 ASSET prefix `0xb200…` are also treated as stocks. `issued` is live `totalSupply() > 0`. Quotes use factory `getPool` discovery + MixedRouteQuoterV3 (path bitmasks). Sugar `forSwaps` alone misses these pools. Native ETH → stock is typically WETH → USDC → stock.
|
|
90
92
|
|
|
91
93
|
## Out of scope
|
|
92
94
|
|
|
@@ -32,10 +32,11 @@ After `{ requestId }`: `sign_request_agree` → `trigger_sign_result` → `broad
|
|
|
32
32
|
|-------|----------|-------|
|
|
33
33
|
| `sourceChainId` | yes | EIP-155 id where burn executes |
|
|
34
34
|
| `destChainId` | yes | EIP-155 destination (mint chain) |
|
|
35
|
-
| `transferAmountHuman` |
|
|
35
|
+
| `transferAmountHuman` | yes for a usable maxFee | USDC the recipient receives (6 decimals). Protocol fee is this amount times Iris `minimumFee` bps. Omit only to inspect raw forwarding tiers. |
|
|
36
36
|
| `feeTier` | no | `low` \| `med` (default) \| `high` |
|
|
37
|
+
| `minFinalityThreshold` | no | `1000` fast (default, has protocol fee) or `2000` standard |
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
`maxFee = forwardFee[tier] + protocolFee`. `forwardFee*` is the Forwarding Service component only. `maxFeeLow` / `Med` / `High` are the combined caps. Returns `fees` plus the same fields at the top level, including `protocolFeeHuman`, `minimumFeeBps`, and `totalBurnHuman` when an amount was passed. Pass that output as `feeSnapshot` to build and balance tools. Do not add `minimumFee` again on top of `maxFee`.
|
|
39
40
|
|
|
40
41
|
## `ctm_cctp_fetch_usdc_balance`
|
|
41
42
|
|
|
@@ -51,10 +52,10 @@ Optional `transferAmountHuman` + `feeSnapshot` (from fetch_burn_fees) returns `r
|
|
|
51
52
|
| `purposeText` | yes | Human-readable purpose |
|
|
52
53
|
| `transferAmountHuman` | yes | Same as fee quote |
|
|
53
54
|
| `mintRecipient` | no | Destination address; default executor |
|
|
54
|
-
| `feeSnapshot` | no | Pass `ctm_cctp_fetch_burn_fees` output |
|
|
55
|
+
| `feeSnapshot` | no | Pass `ctm_cctp_fetch_burn_fees` output. Build re-quotes Iris. A snapshot `maxFee` below the live forward+protocol total is raised; a higher snapshot is kept. |
|
|
55
56
|
| `useCustomGas` | no | Chain registry custom gas |
|
|
56
57
|
|
|
57
|
-
Returns `{ requestId, fees }` — `fees`
|
|
58
|
+
Returns `{ requestId, fees }` — `fees.maxFee` is the forwarding tier plus the protocol fee, and `totalBurn` is the recipient amount plus that max fee.
|
|
58
59
|
|
|
59
60
|
## Important
|
|
60
61
|
|
|
@@ -32,14 +32,14 @@ If the user already said how to order them, pass that on the first search:
|
|
|
32
32
|
|
|
33
33
|
If they only said they want to buy a veCTM, ask once which of those they want, then search. Do not fetch the raw book first.
|
|
34
34
|
|
|
35
|
-
`kind` defaults to listings, which is an immediate buy. Use `auction` or `both` only when they want to bid. Pass `executor` (the KeyGen)
|
|
35
|
+
`kind` defaults to listings, which is an immediate buy. Use `auction` or `both` only when they want to bid. Pass `executor` (the KeyGen). A listing that address already sells comes back with `action: "delist"` and `fillable: false`. Call `ctm_continuum_dao_marketplace_build_delist_multisign` for that row. Do not buy it. An attached KeyGen comes back with `fillable: false`; do not offer to buy it. After they pick someone else's token id, build the fulfill-listing or auction-bid multisign.
|
|
36
36
|
|
|
37
37
|
## Catalog
|
|
38
38
|
|
|
39
39
|
Prefer `https://app-api.continuumdao.org/marketplace`. If that API fails, the same tools replay marketplace logs from block 32100001 and say `source: "logs"`.
|
|
40
40
|
|
|
41
41
|
- `ctm_continuum_dao_marketplace_search` — the ranked short list for a buy. It walks catalog pages. `fetch_listings` does not.
|
|
42
|
-
- `ctm_continuum_dao_marketplace_fetch_auctions` — live auctions
|
|
42
|
+
- `ctm_continuum_dao_marketplace_fetch_auctions` — live auctions. `derivedStatus` is `Active` while the deadline is still ahead, including when no bid has met the reserve. `storedStatus` stays `Pending` until a qualifying bid, then `Active`. `Complete` still needs settle. Set `includeInactive` for sold, canceled, and expired. Every auction includes `bids` (bidder, price, tx, time). Cancel is still allowed while `storedStatus` is `Pending` and no bid has reached the reserve.
|
|
43
43
|
- `ctm_continuum_dao_marketplace_fetch_listings` / `fetch_offerings` — unfiltered pages in created order, with `nextCursor`. `includeInactive` is the past-sales list. A fulfilled listing is a completed sale.
|
|
44
44
|
- `ctm_continuum_dao_marketplace_fetch_token` — one veCTM, including `history` of closed listings, offerings, and auctions.
|
|
45
45
|
- `ctm_continuum_dao_marketplace_fetch_activity` — one address as seller, buyer, or bidder.
|
|
@@ -49,10 +49,10 @@ Prefer `https://app-api.continuumdao.org/marketplace`. If that API fails, the sa
|
|
|
49
49
|
|
|
50
50
|
Builders are the same functions the node app calls. They preflight on-chain. Do not prepend billing legs.
|
|
51
51
|
|
|
52
|
-
Refuse a buy, an offering, or an auction bid when this KeyGen already owns the veCTM (`ownerOf`) or is the listing or auction seller. A live auction escrows the NFT in the marketplace, so the seller check is what blocks bidding on your own auction.
|
|
52
|
+
Refuse a buy, an offering, or an auction bid when this KeyGen already owns the veCTM (`ownerOf`) or is the listing or auction seller. When the KeyGen is the listing seller, delist that listing. A live auction escrows the NFT in the marketplace, so the seller check is what blocks bidding on your own auction.
|
|
53
53
|
|
|
54
54
|
WETH orders send `msg.value` equal to the price or bid. Other payment tokens approve the marketplace for the amount. `createOffering` with WETH still approves WETH, because the contract wraps ETH back to the buyer and the seller pulls WETH later.
|
|
55
55
|
|
|
56
|
-
Creating a listing or auction approves the marketplace for that token id when it is not already approved. Auctions escrow the NFT. An attached KeyGen cannot be
|
|
56
|
+
Creating a listing or auction approves the marketplace for that token id when it is not already approved. Auctions escrow the NFT. An attached KeyGen cannot be listed, offered on, or auctioned, and cannot be used to accept an offering.
|
|
57
57
|
|
|
58
58
|
After `fulfillListing`, or `settleAuction` when this KeyGen is the highest bidder, add the veCTM to the node assets if it is not already there.
|
|
@@ -55,8 +55,8 @@ Always quote first unless the user already has a verified route. Explain quote o
|
|
|
55
55
|
| Field | Required | Notes |
|
|
56
56
|
|-------|----------|-------|
|
|
57
57
|
| `chainId` | yes | Must be Curve-supported (see supported chains below) |
|
|
58
|
-
| `tokenIn` | yes | ERC-20 address, or native: `0xeeee…`, `0x0`, `eth` |
|
|
59
|
-
| `tokenOut` | yes | ERC-20 address or `0xeeee…` for native out |
|
|
58
|
+
| `tokenIn` | yes | ERC-20 address, a Coinbase stock ticker (`AAPLc`) from the live catalog, or native: `0xeeee…`, `0x0`, `eth` |
|
|
59
|
+
| `tokenOut` | yes | ERC-20 address, the same Coinbase stock ticker, or `0xeeee…` for native out |
|
|
60
60
|
| `amountHuman` | yes | Human decimal string of **tokenIn** (e.g. `"100"`, `"0.5"`) |
|
|
61
61
|
| `tokenInDecimals` | no | Omit to read decimals on-chain (native = 18) |
|
|
62
62
|
| `rpcUrl` | no | **Do not pass** — server injects from chain registry |
|
|
@@ -41,8 +41,8 @@ Do not skip steps. Pass the **full quote JSON** into `create_swap`, then pass **
|
|
|
41
41
|
|-------|----------|-------|
|
|
42
42
|
| `type` | yes | `EXACT_INPUT` (sell exact tokenIn) or `EXACT_OUTPUT` (receive exact tokenOut) |
|
|
43
43
|
| `amount` | yes | **Base units** string (wei for 18-decimal tokens). For human amounts, convert with token decimals first |
|
|
44
|
-
| `tokenIn` | yes | Input token
|
|
45
|
-
| `tokenOut` | yes | Output token address
|
|
44
|
+
| `tokenIn` | yes | Input token address, or a Coinbase stock ticker (`AAPLc`) from the live catalog. Native ETH: `0x0000000000000000000000000000000000000000` |
|
|
45
|
+
| `tokenOut` | yes | Output token address, or the same Coinbase stock ticker |
|
|
46
46
|
| `chainId` | yes | Must be Uniswap-supported |
|
|
47
47
|
| `keyGenId` | recommended | Server resolves `swapper` (MPC executor) |
|
|
48
48
|
| `swapper` | optional | MPC wallet address if `keyGenId` omitted |
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,124 @@ var init_expiry = __esm({
|
|
|
51
51
|
]);
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
+
function isCoinbaseB20AssetToken(address) {
|
|
55
|
+
try {
|
|
56
|
+
return viem.getAddress(address).toLowerCase().startsWith(COINBASE_B20_ASSET_PREFIX);
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function looksLikeCoinbaseStockSymbol(symbol) {
|
|
62
|
+
return /^[A-Za-z][A-Za-z0-9]{0,8}(?:\.[A-Za-z][A-Za-z0-9]{0,4})?c$/.test((symbol ?? "").trim());
|
|
63
|
+
}
|
|
64
|
+
function indexCatalog(rows) {
|
|
65
|
+
catalogRows = rows;
|
|
66
|
+
catalogAt = Date.now();
|
|
67
|
+
catalogBySymbol.clear();
|
|
68
|
+
catalogByAddress.clear();
|
|
69
|
+
for (const row of rows) {
|
|
70
|
+
catalogBySymbol.set(row.symbol.toLowerCase(), row);
|
|
71
|
+
catalogByAddress.set(row.address.toLowerCase(), row);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function parseCoinbaseTokenizedStocksPayload(payload) {
|
|
75
|
+
const tokens = Array.isArray(payload) ? payload : payload && typeof payload === "object" && Array.isArray(payload.tokens) ? payload.tokens : [];
|
|
76
|
+
const out = [];
|
|
77
|
+
const seen = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const row of tokens) {
|
|
79
|
+
if (!row || typeof row !== "object") continue;
|
|
80
|
+
const rec = row;
|
|
81
|
+
const symbol = typeof rec.symbol === "string" ? rec.symbol.trim() : "";
|
|
82
|
+
const addressRaw = typeof rec.contract_address === "string" ? rec.contract_address.trim() : "";
|
|
83
|
+
if (!looksLikeCoinbaseStockSymbol(symbol) || !isCoinbaseB20AssetToken(addressRaw)) continue;
|
|
84
|
+
const address = viem.getAddress(addressRaw);
|
|
85
|
+
const key = address.toLowerCase();
|
|
86
|
+
if (seen.has(key)) continue;
|
|
87
|
+
seen.add(key);
|
|
88
|
+
const name = typeof rec.name === "string" && rec.name.trim() ? rec.name.trim() : symbol;
|
|
89
|
+
const iconUrl = typeof rec.icon_url === "string" && rec.icon_url.trim() ? rec.icon_url.trim() : void 0;
|
|
90
|
+
const decimals = typeof rec.decimals === "number" && Number.isInteger(rec.decimals) && rec.decimals >= 0 && rec.decimals <= 18 ? rec.decimals : COINBASE_B20_STOCK_DECIMALS;
|
|
91
|
+
out.push({ address, symbol, name, decimals, iconUrl });
|
|
92
|
+
if (out.length >= CATALOG_MAX) break;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
async function fetchCoinbaseB20StockCatalog() {
|
|
97
|
+
const merged = [];
|
|
98
|
+
const seen = /* @__PURE__ */ new Set();
|
|
99
|
+
let pageToken = "";
|
|
100
|
+
for (let page = 0; page < 4; page++) {
|
|
101
|
+
const url = new URL(COINBASE_TOKENIZED_STOCKS_API_URL);
|
|
102
|
+
url.searchParams.set("page_size", "500");
|
|
103
|
+
if (pageToken) url.searchParams.set("page_token", pageToken);
|
|
104
|
+
const res = await fetch(url, { cache: "no-store", signal: AbortSignal.timeout(8e3) });
|
|
105
|
+
if (!res.ok) break;
|
|
106
|
+
const json = await res.json();
|
|
107
|
+
for (const row of parseCoinbaseTokenizedStocksPayload(json)) {
|
|
108
|
+
const key = row.address.toLowerCase();
|
|
109
|
+
if (seen.has(key)) continue;
|
|
110
|
+
seen.add(key);
|
|
111
|
+
merged.push(row);
|
|
112
|
+
if (merged.length >= CATALOG_MAX) break;
|
|
113
|
+
}
|
|
114
|
+
pageToken = typeof json.next_page_token === "string" ? json.next_page_token.trim() : "";
|
|
115
|
+
if (!pageToken || merged.length >= CATALOG_MAX) break;
|
|
116
|
+
}
|
|
117
|
+
if (merged.length > 0) indexCatalog(merged);
|
|
118
|
+
return catalogRows ?? [];
|
|
119
|
+
}
|
|
120
|
+
async function ensureCoinbaseB20StockCatalog() {
|
|
121
|
+
if (catalogRows && catalogRows.length > 0 && Date.now() - catalogAt < CATALOG_TTL_MS) return catalogRows;
|
|
122
|
+
if (!catalogInflight) {
|
|
123
|
+
catalogInflight = fetchCoinbaseB20StockCatalog().finally(() => {
|
|
124
|
+
catalogInflight = null;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
return await catalogInflight;
|
|
129
|
+
} catch {
|
|
130
|
+
return catalogRows ?? [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function lookupCoinbaseB20StockToken(token) {
|
|
134
|
+
const key = (token ?? "").trim();
|
|
135
|
+
if (!key) return null;
|
|
136
|
+
const fromCatalog = catalogBySymbol.get(key.toLowerCase());
|
|
137
|
+
if (fromCatalog) return fromCatalog;
|
|
138
|
+
try {
|
|
139
|
+
return catalogByAddress.get(viem.getAddress(key).toLowerCase()) ?? null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function canonicalizeEvmTokenInput(token) {
|
|
145
|
+
const raw = (token ?? "").trim();
|
|
146
|
+
if (!raw || viem.isAddress(raw)) return raw;
|
|
147
|
+
if (!looksLikeCoinbaseStockSymbol(raw)) return raw;
|
|
148
|
+
await ensureCoinbaseB20StockCatalog().catch(() => []);
|
|
149
|
+
const hit = lookupCoinbaseB20StockToken(raw);
|
|
150
|
+
if (!hit) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Unknown Coinbase stock ticker "${raw}". It is not in the live tokenized-stocks catalog.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return hit.address;
|
|
156
|
+
}
|
|
157
|
+
var COINBASE_B20_STOCK_DECIMALS, COINBASE_B20_ASSET_PREFIX, COINBASE_TOKENIZED_STOCKS_API_URL, CATALOG_TTL_MS, CATALOG_MAX, catalogRows, catalogAt, catalogInflight, catalogBySymbol, catalogByAddress;
|
|
158
|
+
var init_coinbaseB20 = __esm({
|
|
159
|
+
"src/protocols/evm/shared/coinbaseB20.ts"() {
|
|
160
|
+
COINBASE_B20_STOCK_DECIMALS = 8;
|
|
161
|
+
COINBASE_B20_ASSET_PREFIX = "0xb200000000000000000000";
|
|
162
|
+
COINBASE_TOKENIZED_STOCKS_API_URL = "https://api.coinbase.com/v1/tokenized-stocks";
|
|
163
|
+
CATALOG_TTL_MS = 15 * 60 * 1e3;
|
|
164
|
+
CATALOG_MAX = 500;
|
|
165
|
+
catalogRows = null;
|
|
166
|
+
catalogAt = 0;
|
|
167
|
+
catalogInflight = null;
|
|
168
|
+
catalogBySymbol = /* @__PURE__ */ new Map();
|
|
169
|
+
catalogByAddress = /* @__PURE__ */ new Map();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
54
172
|
function isAerodromeChainSupported(chainId) {
|
|
55
173
|
const n = typeof chainId === "number" ? chainId : Number.parseInt(String(chainId), 10);
|
|
56
174
|
return n === AERODROME_CHAIN_ID;
|
|
@@ -946,6 +1064,9 @@ var UNISWAP_V4_LP_COLLECT_DEFAULT_GAS_UNITS = 900000n;
|
|
|
946
1064
|
var UNISWAP_V4_LP_ERC20_APPROVE_FALLBACK = 100000n;
|
|
947
1065
|
var UNISWAP_V4_LP_WETH_DEPOSIT_FALLBACK = 120000n;
|
|
948
1066
|
var UNISWAP_V4_LP_PERMIT2_APPROVE_FALLBACK = 100000n;
|
|
1067
|
+
|
|
1068
|
+
// src/protocols/evm/uniswap-v4/quote.ts
|
|
1069
|
+
init_coinbaseB20();
|
|
949
1070
|
viem.parseAbi([
|
|
950
1071
|
"function isAllowed(address account, uint8 permission) view returns (bool)",
|
|
951
1072
|
"function PERMISSIONED_TOKEN() view returns (address)"
|
|
@@ -1312,6 +1433,11 @@ function messageFromUniswapHttpResponseBody(text, status, statusText = "") {
|
|
|
1312
1433
|
return short;
|
|
1313
1434
|
}
|
|
1314
1435
|
async function uniswapTradeQuote(args) {
|
|
1436
|
+
args = {
|
|
1437
|
+
...args,
|
|
1438
|
+
tokenIn: await canonicalizeEvmTokenInput(args.tokenIn),
|
|
1439
|
+
tokenOut: await canonicalizeEvmTokenInput(args.tokenOut)
|
|
1440
|
+
};
|
|
1315
1441
|
const apiKey = (args.uniswapApiKey || "").trim();
|
|
1316
1442
|
if (!apiKey) {
|
|
1317
1443
|
throw new Error("uniswapApiKey (x-api-key) is required");
|
|
@@ -1431,6 +1557,7 @@ function parseUniswapQuoteClassicInOut(res) {
|
|
|
1431
1557
|
}
|
|
1432
1558
|
|
|
1433
1559
|
// src/protocols/evm/uniswap-v4/swapMultisign.ts
|
|
1560
|
+
init_coinbaseB20();
|
|
1434
1561
|
init_defiEvmClient();
|
|
1435
1562
|
function parseOptionalGasLimitString(raw) {
|
|
1436
1563
|
if (raw == null) return null;
|
|
@@ -1763,7 +1890,7 @@ async function buildEvmMultisignBodyUniswapV4NativeInOnly(args, quoteInputWei) {
|
|
|
1763
1890
|
});
|
|
1764
1891
|
}
|
|
1765
1892
|
async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
|
|
1766
|
-
const tokenIn = viem.getAddress(args.tokenIn);
|
|
1893
|
+
const tokenIn = viem.getAddress(await canonicalizeEvmTokenInput(args.tokenIn));
|
|
1767
1894
|
const parsedInOut = parseUniswapQuoteClassicInOut(args.fullQuoteSnapshot);
|
|
1768
1895
|
const quoteInputWei = parsedInOut?.inputWei;
|
|
1769
1896
|
if (quoteInputWei == null || quoteInputWei <= 0n) {
|
|
@@ -2038,6 +2165,7 @@ async function buildEvmMultisignBodyUniswapV4SkipPermit2Batch(args) {
|
|
|
2038
2165
|
var UNISWAP_V4_LP_POOL_LOOKUP_HINT = "Pool may not be initialized on this chain. Call ctm_uniswap_v4_list_lp_pools for other presets, pass a custom existingPool.poolReference, or use newPool to initialize a pool.";
|
|
2039
2166
|
|
|
2040
2167
|
// src/protocols/evm/uniswap-v4/liquidityApi.ts
|
|
2168
|
+
init_coinbaseB20();
|
|
2041
2169
|
var LP_HEADERS_BASE = {
|
|
2042
2170
|
"Content-Type": "application/json",
|
|
2043
2171
|
Accept: "application/json",
|
|
@@ -2142,36 +2270,47 @@ function extractUniswapLpTokenAmounts(response) {
|
|
|
2142
2270
|
return { token0: read("token0"), token1: read("token1") };
|
|
2143
2271
|
}
|
|
2144
2272
|
async function uniswapLpCreatePosition(args) {
|
|
2273
|
+
const independentAddress = await canonicalizeEvmTokenInput(args.independentToken.tokenAddress);
|
|
2274
|
+
const existingPool = args.existingPool ? {
|
|
2275
|
+
...args.existingPool,
|
|
2276
|
+
token0Address: await canonicalizeEvmTokenInput(args.existingPool.token0Address),
|
|
2277
|
+
token1Address: await canonicalizeEvmTokenInput(args.existingPool.token1Address)
|
|
2278
|
+
} : void 0;
|
|
2279
|
+
const newPool = args.newPool ? {
|
|
2280
|
+
...args.newPool,
|
|
2281
|
+
token0Address: await canonicalizeEvmTokenInput(args.newPool.token0Address),
|
|
2282
|
+
token1Address: await canonicalizeEvmTokenInput(args.newPool.token1Address)
|
|
2283
|
+
} : void 0;
|
|
2145
2284
|
const body = {
|
|
2146
2285
|
protocol: "V4",
|
|
2147
2286
|
walletAddress: trimAddr2(args.walletAddress),
|
|
2148
2287
|
chainId: args.chainId,
|
|
2149
2288
|
independentToken: {
|
|
2150
|
-
tokenAddress: trimAddr2(
|
|
2289
|
+
tokenAddress: trimAddr2(independentAddress),
|
|
2151
2290
|
amount: String(args.independentToken.amount).trim()
|
|
2152
2291
|
},
|
|
2153
2292
|
slippageTolerance: args.slippageTolerance ?? UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT,
|
|
2154
2293
|
deadline: args.deadline ?? lpDeadlineUnix(),
|
|
2155
2294
|
simulateTransaction: args.simulateTransaction ?? false
|
|
2156
2295
|
};
|
|
2157
|
-
if (
|
|
2296
|
+
if (existingPool) {
|
|
2158
2297
|
body.existingPool = {
|
|
2159
|
-
token0Address: trimAddr2(
|
|
2160
|
-
token1Address: trimAddr2(
|
|
2161
|
-
poolReference: trimAddr2(
|
|
2298
|
+
token0Address: trimAddr2(existingPool.token0Address),
|
|
2299
|
+
token1Address: trimAddr2(existingPool.token1Address),
|
|
2300
|
+
poolReference: trimAddr2(existingPool.poolReference)
|
|
2162
2301
|
};
|
|
2163
2302
|
}
|
|
2164
|
-
if (
|
|
2303
|
+
if (newPool) {
|
|
2165
2304
|
body.newPool = {
|
|
2166
|
-
token0Address: trimAddr2(
|
|
2167
|
-
token1Address: trimAddr2(
|
|
2168
|
-
fee:
|
|
2169
|
-
tickSpacing:
|
|
2170
|
-
initialPrice: String(
|
|
2171
|
-
...
|
|
2305
|
+
token0Address: trimAddr2(newPool.token0Address),
|
|
2306
|
+
token1Address: trimAddr2(newPool.token1Address),
|
|
2307
|
+
fee: newPool.fee,
|
|
2308
|
+
tickSpacing: newPool.tickSpacing,
|
|
2309
|
+
initialPrice: String(newPool.initialPrice).trim(),
|
|
2310
|
+
...newPool.hooks ? { hooks: trimAddr2(newPool.hooks) } : {}
|
|
2172
2311
|
};
|
|
2173
2312
|
}
|
|
2174
|
-
if (!
|
|
2313
|
+
if (!existingPool && !newPool) {
|
|
2175
2314
|
throw new Error("Provide existingPool or newPool for LP create.");
|
|
2176
2315
|
}
|
|
2177
2316
|
if (args.priceBounds) {
|
|
@@ -2206,11 +2345,11 @@ async function uniswapLpIncreasePosition(args) {
|
|
|
2206
2345
|
protocol: "V4",
|
|
2207
2346
|
walletAddress: trimAddr2(args.walletAddress),
|
|
2208
2347
|
chainId: args.chainId,
|
|
2209
|
-
token0Address: trimAddr2(args.token0Address),
|
|
2210
|
-
token1Address: trimAddr2(args.token1Address),
|
|
2348
|
+
token0Address: trimAddr2(await canonicalizeEvmTokenInput(args.token0Address)),
|
|
2349
|
+
token1Address: trimAddr2(await canonicalizeEvmTokenInput(args.token1Address)),
|
|
2211
2350
|
nftTokenId: String(args.nftTokenId),
|
|
2212
2351
|
independentToken: {
|
|
2213
|
-
tokenAddress: trimAddr2(args.independentToken.tokenAddress),
|
|
2352
|
+
tokenAddress: trimAddr2(await canonicalizeEvmTokenInput(args.independentToken.tokenAddress)),
|
|
2214
2353
|
amount: String(args.independentToken.amount).trim()
|
|
2215
2354
|
},
|
|
2216
2355
|
slippageTolerance: args.slippageTolerance ?? UNISWAP_LP_DEFAULT_SLIPPAGE_PERCENT,
|
|
@@ -2820,6 +2959,7 @@ async function swapExactInput(args) {
|
|
|
2820
2959
|
}
|
|
2821
2960
|
|
|
2822
2961
|
// src/protocols/evm/uniswap-v4/limitOrder.ts
|
|
2962
|
+
init_coinbaseB20();
|
|
2823
2963
|
function trimAddr3(a) {
|
|
2824
2964
|
return a.trim();
|
|
2825
2965
|
}
|
|
@@ -2920,6 +3060,11 @@ async function uniswapLimitOrderQuote(args) {
|
|
|
2920
3060
|
args.nodeReadAuth ?? { bearerOnGet: true, jwt: null }
|
|
2921
3061
|
);
|
|
2922
3062
|
}
|
|
3063
|
+
args = {
|
|
3064
|
+
...args,
|
|
3065
|
+
tokenIn: await canonicalizeEvmTokenInput(args.tokenIn),
|
|
3066
|
+
tokenOut: await canonicalizeEvmTokenInput(args.tokenOut)
|
|
3067
|
+
};
|
|
2923
3068
|
const chainIdForGuard = parseUniswapChainId(resolveInputChainId(args));
|
|
2924
3069
|
assertUniswapLimitOrderChain(chainIdForGuard);
|
|
2925
3070
|
const base = args.baseUrl && args.baseUrl.trim() ? args.baseUrl.trim().replace(/\/$/, "") : "https://trade-api.gateway.uniswap.org/v1";
|
|
@@ -3372,6 +3517,7 @@ async function loadFullCurveSessionForRpc(rpcUrl) {
|
|
|
3372
3517
|
var CURVE_ROUTER_EXCHANGE_DEFAULT_GAS_UNITS = 1200000n;
|
|
3373
3518
|
|
|
3374
3519
|
// src/protocols/evm/curve-dao/multisign.ts
|
|
3520
|
+
init_coinbaseB20();
|
|
3375
3521
|
init_defiEvmClient();
|
|
3376
3522
|
var ETH_PLACEHOLDER2 = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
3377
3523
|
function txToViemStep(tx) {
|
|
@@ -3387,7 +3533,7 @@ function txToViemStep(tx) {
|
|
|
3387
3533
|
return { to, data, value };
|
|
3388
3534
|
}
|
|
3389
3535
|
async function buildEvmMultisignBodyCurveDaoBatch(args) {
|
|
3390
|
-
const tokenIn = viem.getAddress(args.tokenIn);
|
|
3536
|
+
const tokenIn = viem.getAddress(await canonicalizeEvmTokenInput(args.tokenIn));
|
|
3391
3537
|
const executor = viem.getAddress(args.executorAddress);
|
|
3392
3538
|
const session = await loadFullCurveSessionForRpc(args.rpcUrl);
|
|
3393
3539
|
if (!session?.curve?.router?.populateSwap) {
|
|
@@ -3396,7 +3542,7 @@ async function buildEvmMultisignBodyCurveDaoBatch(args) {
|
|
|
3396
3542
|
const { curve, wrappedNative } = session;
|
|
3397
3543
|
const wn = wrappedNative;
|
|
3398
3544
|
const tokenInId = toCurveRouterTokenId(tokenIn, wn);
|
|
3399
|
-
const outTrim = (args.tokenOut ?? "").trim();
|
|
3545
|
+
const outTrim = (await canonicalizeEvmTokenInput(args.tokenOut ?? "")).trim();
|
|
3400
3546
|
const tokenOutId = toCurveRouterTokenId(
|
|
3401
3547
|
outTrim.toLowerCase() === ETH_PLACEHOLDER2 ? ETH_PLACEHOLDER2 : viem.getAddress(outTrim),
|
|
3402
3548
|
wn
|