@oracle-agent/oracle 0.3.4 → 0.3.6

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.
Files changed (37) hide show
  1. package/README.md +33 -13
  2. package/SETUP.md +59 -1
  3. package/artifacts/specialist-packs/oracle-full-crypto.json +31 -9
  4. package/bin/oracle-data-mcp.mjs +317 -4
  5. package/bin/oracle-init.mjs +100 -27
  6. package/bin/oracle-upgrade.mjs +42 -0
  7. package/docs/profiles.md +29 -7
  8. package/package.json +4 -1
  9. package/plugins/oracle-owner-gate/__init__.py +213 -0
  10. package/plugins/oracle-owner-gate/plugin.yaml +9 -0
  11. package/profiles/_template/SOUL.md +8 -1
  12. package/profiles/oracle/SOUL.md +17 -2
  13. package/profiles/oracle/profile.json +6 -2
  14. package/profiles/protocol-builder/SOUL.md +13 -6
  15. package/profiles/protocol-builder/profile.json +3 -1
  16. package/profiles/robinhood-agent/SOUL.md +9 -3
  17. package/profiles/robinhood-agent/profile.json +1 -0
  18. package/skills/balance/SKILL.md +176 -0
  19. package/skills/oracle-action-semantics/SKILL.md +40 -0
  20. package/skills/oracle-multichain-nft-launch/SKILL.md +338 -0
  21. package/skills/oracle-multichain-token-launch/SKILL.md +300 -0
  22. package/src/action-semantics.mjs +62 -0
  23. package/src/data/catalog.mjs +27 -3
  24. package/src/data/desk-data.mjs +34 -4
  25. package/src/data/providers/magiceden-sol.mjs +21 -2
  26. package/src/data/providers/nft-gallery.mjs +163 -0
  27. package/src/data/providers/nft-portfolio.mjs +494 -0
  28. package/src/data/providers/opensea-nft.mjs +272 -0
  29. package/src/data/providers/portfolio-history.mjs +394 -0
  30. package/src/data/providers/portfolio.mjs +594 -0
  31. package/src/data/providers/satflow.mjs +1 -0
  32. package/src/exec-policy.mjs +5 -0
  33. package/src/gmx-attestation.mjs +1 -0
  34. package/src/index.mjs +9 -0
  35. package/src/profile-upgrade.mjs +277 -0
  36. package/src/scanner/chains.config.mjs +50 -1
  37. package/src/vault-attestation.mjs +1 -0
@@ -0,0 +1,300 @@
1
+ ---
2
+ name: oracle-multichain-token-launch
3
+ description: Use when planning, building, reviewing, or preparing a fungible token launch on EVM, Solana, Bitcoin Runes, Cosmos, Move, Hyperliquid, or another chain family. Fail closed on unsupported adapters; prepare and simulate only; the user signs each side effect.
4
+ ---
5
+
6
+ # Multichain token launch
7
+
8
+ ## Contract
9
+
10
+ Use this skill for fungible asset creation, initial distribution, public sale setup,
11
+ and liquidity bootstrap preparation.
12
+
13
+ "Every chain" means route the request to a verified chain-family path. It does not
14
+ mean one bytecode artifact or one transaction encoder works everywhere. Never call
15
+ a chain launch-ready because its RPC responds.
16
+
17
+ Oracle researches, scaffolds, validates, simulates, and prepares unsigned actions.
18
+ The user signs deployment, mint, metadata, authority, liquidity, and verification
19
+ actions separately. Never broadcast from this skill.
20
+
21
+ When loaded by the `oracle` router, use this skill to classify the request, then
22
+ route chain research to the relevant specialist and build/prepare work to
23
+ `protocol-builder`. The router remains read and simulate only.
24
+
25
+ ## Support status language
26
+
27
+ Use exactly one status before building:
28
+
29
+ | Status | Meaning |
30
+ |---|---|
31
+ | `TEMPLATE_READY` | Oracle ships a gated template and unsigned prepare path for this exact standard. |
32
+ | `ADAPTER_READY` | A chain-specific encoder exists and passed live simulation or testnet verification. |
33
+ | `GUIDED_BUILD` | Oracle can scaffold and test it, but no generic transaction adapter is shipped. |
34
+ | `RESEARCH_ONLY` | Oracle can verify chain mechanics and produce a launch plan, not a deployable bundle. |
35
+ | `UNSUPPORTED` | Required chain primitives or trustworthy tooling cannot be verified. Stop. |
36
+
37
+ Do not upgrade a status without evidence. A compiler pass alone is not
38
+ `ADAPTER_READY`. A mainnet-ready claim needs exact chain identity, final artifacts,
39
+ simulation, fee estimate, and source/program verification path.
40
+
41
+ ## Current family matrix
42
+
43
+ | Family | Common standards | Default status | Required path |
44
+ |---|---|---|---|
45
+ | EVM | ERC-20 | `TEMPLATE_READY` only for Oracle `safe-erc20`; custom tax, mint, proxy, or hook designs are `GUIDED_BUILD` | Foundry gate, chain-id proof, unsigned deploy, source verification |
46
+ | Solana | SPL Token, Token-2022 | `GUIDED_BUILD` | Select extensions explicitly, construct unsigned transactions, simulate, user wallet signs |
47
+ | Bitcoin | Runes | `RESEARCH_ONLY` until an etch adapter is present | Commit/reveal plan, UTXO and fee model, exact terms review |
48
+ | Cosmos | tokenfactory, CW20 | `RESEARCH_ONLY` by default | Resolve the chain's module or CosmWasm messages; EVM-enabled Cosmos chains use the EVM path only when verified |
49
+ | Sui | Coin, regulated coin primitives | `RESEARCH_ONLY` by default | Move package, treasury capability model, devnet/testnet publish first |
50
+ | Aptos | Coin, Fungible Asset | `RESEARCH_ONLY` by default | Move module/object model, upgrade policy, testnet publish first |
51
+ | Hyperliquid L1 | spot asset deployment | `RESEARCH_ONLY` | Verify current native deployment/auction process; HIP-3 is a market deployment, not a generic token contract |
52
+ | HyperEVM | ERC-20 | EVM status | Use the exact HyperEVM chain id and verified gas/DEX addresses |
53
+ | Other | chain-native | `UNSUPPORTED` until classified | Add and verify a dedicated adapter before preparing transactions |
54
+
55
+ This matrix is the minimum truth bar, not a marketing ceiling. If a verified adapter
56
+ lands later, update its status with tests and evidence.
57
+
58
+ ## 1. Build the launch manifest
59
+
60
+ Do not write code until all required fields are known:
61
+
62
+ ```yaml
63
+ asset:
64
+ name: ""
65
+ symbol: ""
66
+ chain_family: ""
67
+ chain_name: ""
68
+ chain_id_or_genesis: ""
69
+ network: mainnet|testnet|devnet|local
70
+ standard: ""
71
+ decimals: null
72
+ max_supply: ""
73
+ initial_supply: ""
74
+ allocation:
75
+ treasury: ""
76
+ public: ""
77
+ liquidity: ""
78
+ team: ""
79
+ community: ""
80
+ authorities:
81
+ mint: none|wallet|multisig|timelock|program
82
+ freeze: none|wallet|multisig|timelock|program
83
+ pause: none|wallet|multisig|timelock
84
+ upgrade: none|wallet|multisig|timelock
85
+ metadata: immutable|wallet|multisig|timelock
86
+ mechanics:
87
+ transfer_tax_bps: 0
88
+ blacklist: false
89
+ transfer_hook: none
90
+ sale: none
91
+ liquidity: none
92
+ verification:
93
+ source: required
94
+ simulation: required
95
+ firm_audit: false
96
+ ```
97
+
98
+ Reject ambiguous supply units. Record raw base units and human-readable units.
99
+ Percent allocations must total 100 percent before deploy preparation.
100
+
101
+ ## 2. Verify the chain, standard, and tooling
102
+
103
+ 1. Resolve the exact chain identity from a live RPC or official client.
104
+ 2. Verify native gas asset, address format, finality model, fee fields, explorer,
105
+ compiler/toolchain version, and source/program verification mechanism.
106
+ 3. Verify the intended standard from official chain or standards documentation.
107
+ 4. Prefer a boring official or widely reviewed implementation.
108
+ 5. Record every external factory, router, program, module, and metadata endpoint.
109
+ 6. Mark unverified addresses and deprecated tooling `UNSUPPORTED`, not best effort.
110
+
111
+ Chain names are not identities. `mainnet`, `testnet`, and forks must carry distinct
112
+ IDs and RPC evidence.
113
+
114
+ ## 3. Force an authority decision
115
+
116
+ Before generating transactions, print a table for:
117
+
118
+ - mint or treasury capability
119
+ - freeze or deny-list capability
120
+ - pause capability
121
+ - upgrade authority
122
+ - metadata authority
123
+ - fee/tax setter
124
+ - transfer-hook owner
125
+ - sale contract owner
126
+ - liquidity position owner
127
+ - treasury and royalty recipients
128
+
129
+ For each authority, state who holds it at creation, whether it can change, the
130
+ transfer/revoke transaction, and what breaks if revoked.
131
+
132
+ Defaults:
133
+
134
+ - fixed supply when future minting is not a product requirement
135
+ - no transfer tax
136
+ - no blacklist
137
+ - no proxy upgradeability
138
+ - no hidden owner balance changes
139
+ - multisig or timelock for powers that must remain
140
+ - revoke only after verification and launch operations that need the authority
141
+
142
+ Never claim "renounced" until independent on-chain read-back proves it.
143
+
144
+ ## 4. Family-specific build paths
145
+
146
+ ### EVM
147
+
148
+ For a plain fixed-supply token, use Oracle's gated template:
149
+
150
+ ```js
151
+ data.call("protocol-templates", "gate", { templateId: "safe-erc20" })
152
+ data.call("protocol-templates", "prepareDeploy", {
153
+ templateId: "safe-erc20",
154
+ chainId,
155
+ args: [name, symbol, supply, initialHolder, initialOwner],
156
+ })
157
+ ```
158
+
159
+ Required gates:
160
+
161
+ 1. Verify chain id and RPC reality.
162
+ 2. `forge test` passes.
163
+ 3. Static analysis runs when available.
164
+ 4. Constructor units and addresses decode back exactly.
165
+ 5. Dry-run deployment succeeds on the target chain fork when archive state exists.
166
+ 6. Unsigned deploy is stamped; source verification input is prepared separately.
167
+ 7. Liquidity and ownership changes remain separate unsigned actions.
168
+
169
+ Any custom minting, tax, blacklist, transfer hook, proxy, permit, votes, vesting, or
170
+ cross-chain bridge logic leaves `safe-erc20` and becomes a custom `GUIDED_BUILD`.
171
+ Do not silently bolt features onto the reviewed template.
172
+
173
+ ### Solana
174
+
175
+ Choose standard SPL Token unless a specific Token-2022 extension is required.
176
+ For Token-2022, enumerate every selected extension and its authority, including
177
+ transfer fees, permanent delegate, transfer hook, default account state, metadata
178
+ pointer, interest bearing, confidential transfer, and non-transferable behavior.
179
+
180
+ Prepare distinct unsigned transactions for:
181
+
182
+ 1. create mint account and initialize mint
183
+ 2. create metadata when used
184
+ 3. create distribution token accounts
185
+ 4. mint initial supply
186
+ 5. transfer or revoke mint authority
187
+ 6. transfer or revoke freeze authority
188
+ 7. create sale or liquidity positions, only if separately approved
189
+
190
+ Simulate each transaction against the intended cluster. A blockhash-expired result
191
+ must be rebuilt, never manually edited.
192
+
193
+ ### Bitcoin Runes
194
+
195
+ A Rune launch is an etching, not a smart-contract deployment. Collect and print:
196
+
197
+ - rune name and spacers
198
+ - divisibility and symbol
199
+ - premine
200
+ - mint amount and cap
201
+ - start and end heights or offsets
202
+ - turbo flag
203
+ - commit UTXO, reveal destination, fee rate, and expected total fees
204
+
205
+ Prepare commit/reveal artifacts only through a verified adapter. Protect the commit
206
+ UTXO from accidental spend, account for the reveal window and reorg risk, and never
207
+ promise an indexer ticker reservation before the etching confirms. Without an
208
+ adapter, status stays `RESEARCH_ONLY`.
209
+
210
+ ### Cosmos
211
+
212
+ First determine whether the target uses a native tokenfactory module, CosmWasm
213
+ CW20, or an EVM runtime. These are different launch paths.
214
+
215
+ - tokenfactory: verify denom creation, mint, burn, admin-change, and metadata message
216
+ types from that chain's running version
217
+ - CW20: compile and test the exact contract artifact, instantiate message, minter
218
+ model, marketing info, and migration admin
219
+ - EVM runtime: use the EVM path only after proving chain id and JSON-RPC behavior
220
+
221
+ Never transplant Osmosis, Injective, Sei, or another chain's message type by name.
222
+
223
+ ### Sui and Aptos
224
+
225
+ Sui and Aptos need separate Move packages and authority models. Do not share source
226
+ because both languages are called Move.
227
+
228
+ - Sui: identify one-time witness, treasury capability, metadata/display objects,
229
+ deny-list or regulated-coin powers, package upgrade policy, and object ownership
230
+ - Aptos: choose Coin or Fungible Asset, define mint/burn/freeze capability objects,
231
+ metadata object, store model, and module upgrade policy
232
+
233
+ Publish and exercise the full lifecycle on devnet/testnet first. Mainnet remains
234
+ `RESEARCH_ONLY` until the exact package and publish transaction pass review.
235
+
236
+ ### Hyperliquid
237
+
238
+ Do not conflate HIP-3 builder-deployed perp markets, HIP-1 or native spot assets,
239
+ HyperEVM ERC-20s, and HIP-4 outcomes. Route HyperEVM assets to EVM. For Hyperliquid
240
+ L1 native assets, verify current official deployment, auction, genesis allocation,
241
+ and deployer requirements before naming a prepare path. Without a dedicated native
242
+ adapter, status is `RESEARCH_ONLY`.
243
+
244
+ ## 5. Distribution, sale, and liquidity are separate actions
245
+
246
+ Token creation does not authorize a sale or pool. For each follow-on action, show:
247
+
248
+ - destination contract/program/module
249
+ - token and quote amounts
250
+ - opening price and implied fully diluted value
251
+ - slippage and deadline
252
+ - LP ownership, lock, burn, or withdrawal rights
253
+ - treasury recipient
254
+ - vesting terms and clawback/admin powers
255
+ - simulation and fee estimate
256
+
257
+ Verify DEX factories and routers on the exact chain. A canonical address on Ethereum
258
+ is not canonical on a sibling EVM chain.
259
+
260
+ Never auto-create liquidity after deployment. Never auto-revoke authority before
261
+ metadata, distribution, or verification actions that need it are complete.
262
+
263
+ ## 6. Required artifacts
264
+
265
+ A completed prepare run returns:
266
+
267
+ 1. `launch-manifest.yaml` with exact units and chain identity
268
+ 2. authority and mutability table
269
+ 3. source/package hash and dependency lock
270
+ 4. test and static-analysis results
271
+ 5. target-chain simulation or an explicit reason it is unavailable
272
+ 6. fee and balance estimate
273
+ 7. ordered unsigned transaction bundle with hashes
274
+ 8. source/program verification payload
275
+ 9. separate liquidity/distribution bundle when requested
276
+ 10. a final user approval checklist, one side effect per line
277
+
278
+ Do not emit a signed payload, private key request, seed phrase request, or broadcast
279
+ command with signing enabled.
280
+
281
+ ## Refusals
282
+
283
+ Refuse hidden minting, honeypots, selective sell blocks, fake renouncement, covert
284
+ taxes, wash liquidity, fake volume, stolen upgrade keys, undisclosed permanent
285
+ delegates, and misleading supply claims.
286
+
287
+ ## Completion gate
288
+
289
+ A launch is only `PREPARED` when:
290
+
291
+ - chain and standard are exact
292
+ - support status is honest
293
+ - supply and allocation reconcile
294
+ - all authorities are disclosed
295
+ - tests pass
296
+ - simulation passes or the missing capability is explicit
297
+ - every destination and payload hash is recorded
298
+ - no signing or broadcast occurred
299
+
300
+ Anything less is `DRAFT`, `RESEARCH_ONLY`, or `UNSUPPORTED`.
@@ -0,0 +1,62 @@
1
+ export const ACTION_MODES = Object.freeze({
2
+ ALERT_ONLY: "alert_only",
3
+ EXECUTE: "execute",
4
+ });
5
+
6
+ const ALERT_VERBS = new Set(["watch", "watch this", "ping", "ping me", "alert", "notify"]);
7
+ const ACTIVE_LEGACY_WATCH_STATUSES = new Set(["watching", "armed"]);
8
+
9
+ export function actionModeForVerb(verb) {
10
+ if (typeof verb !== "string" || !verb.trim()) {
11
+ throw new TypeError("explicit action verb required");
12
+ }
13
+ const normalized = verb.trim().toLowerCase().replace(/\s+/g, " ");
14
+ if (ALERT_VERBS.has(normalized)) return ACTION_MODES.ALERT_ONLY;
15
+ if (normalized === "arm") return ACTION_MODES.EXECUTE;
16
+ throw new TypeError(`unsupported action verb: ${normalized}`);
17
+ }
18
+
19
+ export function createActionRecord(input = {}) {
20
+ const { verb, ...fields } = input;
21
+ return {
22
+ ...fields,
23
+ active: true,
24
+ actionMode: actionModeForVerb(verb),
25
+ };
26
+ }
27
+
28
+ export function assertActionRecord(record) {
29
+ if (!record || typeof record !== "object" || typeof record.active !== "boolean") {
30
+ throw new TypeError("active must be boolean");
31
+ }
32
+ if (!Object.values(ACTION_MODES).includes(record.actionMode)) {
33
+ throw new TypeError("actionMode must be alert_only or execute");
34
+ }
35
+ return record;
36
+ }
37
+
38
+ export function isActiveAlert(record) {
39
+ return record?.active === true && record?.actionMode === ACTION_MODES.ALERT_ONLY;
40
+ }
41
+
42
+ export function isActiveExecution(record) {
43
+ return record?.active === true && record?.actionMode === ACTION_MODES.EXECUTE;
44
+ }
45
+
46
+ export function migrateLegacyWatchRecord(record = {}) {
47
+ if (
48
+ record.actionMode !== undefined &&
49
+ record.actionMode !== ACTION_MODES.ALERT_ONLY
50
+ ) {
51
+ throw new TypeError("non-alert record cannot enter alert-only watch migration");
52
+ }
53
+ const active =
54
+ typeof record.active === "boolean"
55
+ ? record.active
56
+ : ACTIVE_LEGACY_WATCH_STATUSES.has(String(record.status || "").toLowerCase());
57
+ return {
58
+ ...record,
59
+ active,
60
+ actionMode: ACTION_MODES.ALERT_ONLY,
61
+ };
62
+ }
@@ -98,6 +98,29 @@ registerProvider({
98
98
  description: "RH agent local HTTP (unauthenticated read routes)",
99
99
  });
100
100
 
101
+ registerProvider({
102
+ id: "portfolio",
103
+ venue: "multichain-wallet",
104
+ chainIds: [1, 10, 56, 137, 988, 999, 2741, 4663, 8453, 42161, 43114],
105
+ auth: "none",
106
+ ops: ["health", "balances", "snapshot", "history", "valueGraph"],
107
+ execution: "read-only",
108
+ description:
109
+ "Read-only balance aggregation across configured EVM chains, Solana, Bitcoin, and Hyperliquid, plus profile-local observation snapshots, history, and value graphs with explicit partial coverage.",
110
+ });
111
+
112
+ registerProvider({
113
+ id: "nft-portfolio",
114
+ venue: "multichain-nft-wallet",
115
+ chainIds: [1, 10, 137, 988, 999, 2741, 4663, 8453, 42161, 43114],
116
+ auth: "optionalApiKey",
117
+ ops: ["health", "inventory", "gallery", "pnl", "prepareList"],
118
+ execution: "prepare",
119
+ baseEnv: ["OPENSEA_API_KEY", "MAGICEDEN_API_KEY", "SATFLOW_API_KEY"],
120
+ description:
121
+ "Normalized EVM, Solana, and Bitcoin NFT inventory, static contact-sheet galleries, explicit PnL coverage, and user-confirmed unsigned listing preparation.",
122
+ });
123
+
101
124
  registerProvider({
102
125
  id: "evm-rpc",
103
126
  venue: "evm",
@@ -381,11 +404,12 @@ registerProvider({
381
404
  registerProvider({
382
405
  id: "opensea-nft",
383
406
  venue: "nft",
384
- chainIds: [1],
407
+ chainIds: [1, 10, 137, 988, 999, 2741, 4663, 8453, 42161, 43114],
385
408
  auth: "apiKey",
386
- ops: ["health", "collection", "floor"],
409
+ ops: ["health", "collection", "floor", "accountNfts", "accountPnl", "prepareList"],
410
+ execution: "prepare",
387
411
  baseEnv: ["OPENSEA_API_KEY", "OPENSEA_ENV_FILE"],
388
- description: "OpenSea collection + floor (OPENSEA_API_KEY)",
412
+ description: "OpenSea multichain NFT inventory, estimated values, indexed account PnL, collection floors, and unsigned listing actions",
389
413
  });
390
414
 
391
415
  registerProvider({
@@ -40,6 +40,9 @@ import * as balancer from "./providers/balancer.mjs";
40
40
  import * as pendle from "./providers/pendle.mjs";
41
41
  import * as odos from "./providers/odos.mjs";
42
42
  import * as blockscout from "./providers/blockscout.mjs";
43
+ import * as portfolio from "./providers/portfolio.mjs";
44
+ import * as portfolioHistory from "./providers/portfolio-history.mjs";
45
+ import * as nftPortfolio from "./providers/nft-portfolio.mjs";
43
46
  import * as paraswap from "./providers/paraswap.mjs";
44
47
 
45
48
  const OPS = {
@@ -90,6 +93,20 @@ const OPS = {
90
93
  erc20Balance: (o, a = {}) => rpc.erc20BalanceOf(a, o),
91
94
  transactionReceipt: (o, a = {}) => rpc.transactionReceipt(a, o),
92
95
  },
96
+ portfolio: {
97
+ health: (o) => portfolio.portfolioHealth(o),
98
+ balances: (o, a = {}) => portfolio.portfolioBalance(a, o),
99
+ snapshot: (o, a = {}) => portfolioHistory.portfolioSnapshot(a, o),
100
+ history: (o, a = {}) => portfolioHistory.portfolioHistory(a, o),
101
+ valueGraph: (o, a = {}) => portfolioHistory.portfolioValueGraph(a, o),
102
+ },
103
+ "nft-portfolio": {
104
+ health: (o) => nftPortfolio.nftHealth(o),
105
+ inventory: (o, a = {}) => nftPortfolio.nftInventory(a, o),
106
+ gallery: (o, a = {}) => nftPortfolio.nftPortfolioGallery(a, o),
107
+ pnl: (o, a = {}) => nftPortfolio.nftPnl(a, o),
108
+ prepareList: (o, a = {}) => nftPortfolio.nftPrepareList(a, o),
109
+ },
93
110
  "solana-rpc": {
94
111
  health: (o) => solana.solanaHealth(o),
95
112
  latestBlockhash: (o, a = {}) => solana.solanaLatestBlockhash(a, o),
@@ -213,6 +230,9 @@ const OPS = {
213
230
  health: (o) => osnft.openseaHealth(o),
214
231
  collection: (o, a = {}) => osnft.openseaCollection(a.slug, o),
215
232
  floor: (o, a = {}) => osnft.openseaFloor(a.slug, o),
233
+ accountNfts: (o, a = {}) => osnft.openseaAccountNfts(a, o),
234
+ accountPnl: (o, a = {}) => osnft.openseaAccountPnl(a, o),
235
+ prepareList: (o, a = {}) => osnft.openseaPrepareList(a, o),
216
236
  },
217
237
 
218
238
  "hl-outcome": {
@@ -570,6 +590,20 @@ export const data = {
570
590
  call: (chainId, method, params, o) =>
571
591
  dataCall("evm-rpc", "call", { chainId, method, params }, o),
572
592
  },
593
+ portfolio: {
594
+ balance: (a, o) => dataCall("portfolio", "balances", a || {}, o),
595
+ snapshot: (a, o) => dataCall("portfolio", "snapshot", a || {}, o),
596
+ history: (a, o) => dataCall("portfolio", "history", a || {}, o),
597
+ valueGraph: (a, o) => dataCall("portfolio", "valueGraph", a || {}, o),
598
+ },
599
+ nft: {
600
+ inventory: (a, o) => dataCall("nft-portfolio", "inventory", a || {}, o),
601
+ gallery: (a, o) => dataCall("nft-portfolio", "gallery", a || {}, o),
602
+ pnl: (a, o) => dataCall("nft-portfolio", "pnl", a || {}, o),
603
+ prepareList: (a, o) => dataCall("nft-portfolio", "prepareList", a || {}, o),
604
+ floor: (slug, o) => dataCall("opensea-nft", "floor", { slug }, o),
605
+ collection: (slug, o) => dataCall("opensea-nft", "collection", { slug }, o),
606
+ },
573
607
  solana: {
574
608
  health: (o) => dataCall("solana-rpc", "health", {}, o),
575
609
  latestBlockhash: (a, o) => dataCall("solana-rpc", "latestBlockhash", a || {}, o),
@@ -633,10 +667,6 @@ export const data = {
633
667
  cow: {
634
668
  quote: (a, o) => dataCall("cowswap", "quote", a || {}, o),
635
669
  },
636
- nft: {
637
- floor: (slug, o) => dataCall("opensea-nft", "floor", { slug }, o),
638
- collection: (slug, o) => dataCall("opensea-nft", "collection", { slug }, o),
639
- },
640
670
  hlWs: {
641
671
  allMids: (o) => dataCall("hl-ws", "allMids", {}, o),
642
672
  l2Book: (coin, o) => dataCall("hl-ws", "l2Book", { coin }, o),
@@ -89,6 +89,18 @@ function lamports(sol) {
89
89
  return Math.round(positiveNumber(sol, "priceSol") * LAMPORTS_PER_SOL);
90
90
  }
91
91
 
92
+ function listingExpiry(value) {
93
+ if (value == null || value === "") return null;
94
+ const expiry = Number(value);
95
+ if (!Number.isSafeInteger(expiry) || expiry < 0) {
96
+ throw new Error("magiceden: expiry must be a whole Unix timestamp in seconds or 0");
97
+ }
98
+ if (expiry !== 0 && expiry <= Math.floor(Date.now() / 1000)) {
99
+ throw new Error("magiceden: expiry must be in the future or 0 for no expiry");
100
+ }
101
+ return expiry;
102
+ }
103
+
92
104
  export async function magicEdenSolHealth(opts = {}) {
93
105
  try {
94
106
  const stats = await magicEdenSolStats({ symbol: "mad_lads" }, opts);
@@ -256,20 +268,26 @@ export async function magicEdenSolPrepareList(args = {}, opts = {}) {
256
268
  const tokenATA = solanaPubkey(args.tokenATA || args.tokenAddress, "tokenATA");
257
269
  const auctionHouse = solanaPubkey(args.auctionHouse, "auctionHouse");
258
270
  const priceSol = positiveNumber(args.priceSol ?? args.price, "priceSol");
271
+ const expiry = listingExpiry(args.expiry);
259
272
  const url = new URL(`${base(opts)}/instructions/sell`);
260
273
  url.searchParams.set("seller", seller);
261
274
  url.searchParams.set("auctionHouseAddress", auctionHouse);
262
275
  url.searchParams.set("tokenMint", tokenMint);
263
276
  url.searchParams.set("tokenAccount", tokenATA);
264
277
  url.searchParams.set("price", String(priceSol));
278
+ if (args.sellerReferral) url.searchParams.set("sellerReferral", solanaPubkey(args.sellerReferral, "sellerReferral"));
279
+ if (expiry != null) url.searchParams.set("expiry", String(expiry));
265
280
  const raw = await httpJson(url.toString(), {
266
281
  headers: headers(opts),
267
282
  fetchImpl: opts.fetchImpl,
268
283
  timeoutMs: opts.timeoutMs ?? 15_000,
269
284
  });
270
285
  const data = raw?.v0?.tx?.data ?? raw?.tx?.data ?? null;
271
- const transaction = Array.isArray(data) ? Buffer.from(data).toString("base64") : null;
272
- if (!transaction) throw new Error("magiceden: sell returned no transaction payload");
286
+ if (!Array.isArray(data)) {
287
+ if (raw?.txSigned) throw new Error("magiceden: refused a pre-signed transaction payload from the API");
288
+ throw new Error("magiceden: sell returned no unsigned transaction payload");
289
+ }
290
+ const transaction = Buffer.from(data).toString("base64");
273
291
  return stampPrepared({
274
292
  provider: "magiceden-sol",
275
293
  chain: "solana-mainnet-beta",
@@ -282,6 +300,7 @@ export async function magicEdenSolPrepareList(args = {}, opts = {}) {
282
300
  seller,
283
301
  tokenMint,
284
302
  priceSol,
303
+ expiry,
285
304
  transaction,
286
305
  transactionEncoding: "base64",
287
306
  raw,