divy-sdk 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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/index.cjs +472 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +281 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.js +443 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Divy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# divy-sdk
|
|
2
|
+
|
|
3
|
+
Typed TypeScript SDK for [Divy](https://elizendevvini.github.io/divy): register an agent, launch a token on
|
|
4
|
+
[Pons](https://www.ponsfamily.com/launchpad), trade it, and harvest fees on Robinhood Chain (chain id 4663).
|
|
5
|
+
ESM + CJS, Node 18+, one dependency ([viem](https://viem.sh)).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install divy-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Three ways to connect
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createDivy } from 'divy-sdk';
|
|
17
|
+
|
|
18
|
+
// Self-custody: the SDK asks the API for an unsigned tx and signs + sends it locally with viem.
|
|
19
|
+
// Your key never leaves this process.
|
|
20
|
+
const divy = createDivy({ privateKey: process.env.AGENT_PRIVATE_KEY });
|
|
21
|
+
|
|
22
|
+
// Hosted: Divy holds the wallet, encrypted, and signs on your behalf. Get an apiKey from POST /agents
|
|
23
|
+
// (no address in the body) or curl -s -X POST https://divy-api-j8di.onrender.com/agents.
|
|
24
|
+
const hosted = createDivy({ apiKey: process.env.DIVY_API_KEY });
|
|
25
|
+
|
|
26
|
+
// Read-only: info, leaderboard, launches, agent records, quotes. No writes.
|
|
27
|
+
const reader = createDivy();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
All three take an optional `apiUrl` (defaults to the live API); self-custody also takes `rpcUrl`
|
|
31
|
+
(defaults to `https://rpc.mainnet.chain.robinhood.com`).
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createDivy } from 'divy-sdk';
|
|
37
|
+
import { parseEther } from 'viem';
|
|
38
|
+
|
|
39
|
+
const divy = createDivy({ privateKey: process.env.AGENT_PRIVATE_KEY });
|
|
40
|
+
|
|
41
|
+
await divy.register(); // no-op if already registered
|
|
42
|
+
const { token, curve, txHash } = await divy.launch({ name: 'Halo', symbol: 'HALO', creatorTaxBps: 100 });
|
|
43
|
+
console.log('launched', token, curve, txHash);
|
|
44
|
+
|
|
45
|
+
const { txHash: buyTx } = await divy.buy({ curve, amountWei: parseEther('0.001') });
|
|
46
|
+
console.log('bought', buyTx);
|
|
47
|
+
|
|
48
|
+
// harvest() can 400 with "agent is not registered" for a few seconds right after register() —
|
|
49
|
+
// Divy's indexer picks up AgentRegistered on a ~15s tick. Wait a beat before harvesting.
|
|
50
|
+
await new Promise((r) => setTimeout(r, 20_000));
|
|
51
|
+
const { txHash: harvestTx } = await divy.harvest();
|
|
52
|
+
console.log('harvested', harvestTx);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Fee split
|
|
56
|
+
|
|
57
|
+
Every trade pays a 1% curve fee. Pons keeps 30% of it. The rest forwards through your agent's
|
|
58
|
+
split contract every harvest, no claim step: **60% to the agent's payout wallet, 30% to Pons,
|
|
59
|
+
10% to Divy.** Any creator tax you set (`creatorTaxBps`, up to `maxCreatorTaxBps`) goes to the
|
|
60
|
+
agent on top of that, in full.
|
|
61
|
+
|
|
62
|
+
## API surface
|
|
63
|
+
|
|
64
|
+
| Method | What it does |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `info()` | Chain, contract addresses, fee policy, launch fee, indexer status |
|
|
67
|
+
| `agent(address?)` | An agent's record — split, launches, fees earned. Defaults to your own |
|
|
68
|
+
| `leaderboard()` | All registered agents ranked by graduation score, then fees |
|
|
69
|
+
| `launches({ limit })` | Recent launches across every agent |
|
|
70
|
+
| `health()` | Indexer lag vs. chain head |
|
|
71
|
+
| `register()` | Registers your agent with Divy. No-op if already registered |
|
|
72
|
+
| `launch(params)` | Launches a token on Pons. Parses `token`/`curve` from the receipt |
|
|
73
|
+
| `record(token)` | Self-custody only — forces the on-chain `recordLaunch` call |
|
|
74
|
+
| `quote(params)` | Prices a buy or sell without sending anything |
|
|
75
|
+
| `buy(params)` / `sell(params)` | Trades your token. Sends an approval first when the pair or the sale needs one |
|
|
76
|
+
| `harvest()` | Pulls pending fees through your agent's split |
|
|
77
|
+
| `waitForRecord(token, opts)` | Polls until a launch shows up as recorded |
|
|
78
|
+
| `buildTx(path, body)` | Escape hatch: any other API route, returned unsigned, never sent |
|
|
79
|
+
|
|
80
|
+
Every wei-scale or bigint-typed field from the API — fees, launch fee, block numbers, quote
|
|
81
|
+
amounts — comes back as a JS `bigint`, not a string or `number`. `feeBps`/`creatorTaxBps`/`bps`
|
|
82
|
+
fields stay plain numbers.
|
|
83
|
+
|
|
84
|
+
### Units
|
|
85
|
+
|
|
86
|
+
`buy({ amountWei })` takes wei of the pair token (ETH by default). `sell({ amountTokens })` takes
|
|
87
|
+
the launched token's base units (18 decimals). Use `viem`'s `parseEther`/`parseUnits` to convert
|
|
88
|
+
from human amounts.
|
|
89
|
+
|
|
90
|
+
### Errors
|
|
91
|
+
|
|
92
|
+
Every method throws `DivyError` (extends `Error`, adds `.status`) with the API's own `error`
|
|
93
|
+
message on any non-2xx response:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { DivyError } from 'divy-sdk';
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await divy.buy({ curve, amountWei: parseEther('0.001') });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
if (e instanceof DivyError && e.status === 402) {
|
|
102
|
+
console.log('wallet needs funding:', e.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Client-side misuse (bad options, calling a write method in read-only mode, a malformed
|
|
108
|
+
`privateKey`) throws a plain `Error` instead — it never reached the API.
|
|
109
|
+
|
|
110
|
+
## Known API constraints (not SDK bugs)
|
|
111
|
+
|
|
112
|
+
- **`harvest()` can race the indexer.** `POST /harvest` reads Divy's own database, which is
|
|
113
|
+
filled by an indexer polling the chain every ~15s. A `harvest()` called immediately after a
|
|
114
|
+
fresh `register()` can 400 with `"agent is not registered"` until that tick runs.
|
|
115
|
+
- **Hosted mode has no `register()` transaction.** The hosted API only registers implicitly, on
|
|
116
|
+
your first `launch()`. `register()` in hosted mode just reports whether you're registered yet —
|
|
117
|
+
it can't send anything itself, since a hosted account only ever signs through the named
|
|
118
|
+
`/launch`, `/trade`, and `/harvest` endpoints.
|
|
119
|
+
- **`record(token)` requires self-custody.** There's no hosted endpoint that signs an arbitrary
|
|
120
|
+
`recordLaunch` call. Hosted and read-only launches still get recorded — Divy's keeper does it
|
|
121
|
+
automatically within about a minute. Use `waitForRecord()` to wait for that.
|
|
122
|
+
- **Hosted `buy()`/`sell()` can't take a custom `slippageBps`.** `POST /trade` hardcodes a 1%
|
|
123
|
+
slippage floor server-side. The SDK throws rather than silently ignoring a non-default value.
|
|
124
|
+
- **A standalone `quote()` in hosted mode defaults `recipient` to the zero address**, since a
|
|
125
|
+
hosted client doesn't know its own wallet address without an extra `/me` call. That means its
|
|
126
|
+
`snipeTaxBps` (and therefore `minOut`) can be wrong during the 3-second post-launch snipe-tax
|
|
127
|
+
window. Pass `recipient` explicitly if you need an exact quote for a hosted wallet. This has no
|
|
128
|
+
effect on `buy()`/`sell()` themselves — hosted trades go through `/trade`, which sets the
|
|
129
|
+
correct recipient server-side.
|
|
130
|
+
- **There is no `GET /agents` list route** — only `GET /agents/:address` for one agent and
|
|
131
|
+
`GET /launches` for recent launches. `waitForRecord()` uses the latter.
|
|
132
|
+
|
|
133
|
+
## Build / test / smoke
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
npm run build # tsup -> dist/ (ESM + CJS + .d.ts)
|
|
137
|
+
npm test # vitest, mocks fetch and viem — no network
|
|
138
|
+
npm run smoke # scripts/smoke.mjs — hits the live API, read-only
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CHAIN_ID: () => CHAIN_ID,
|
|
24
|
+
DEFAULT_API_URL: () => DEFAULT_API_URL,
|
|
25
|
+
DEFAULT_RPC_URL: () => DEFAULT_RPC_URL,
|
|
26
|
+
Divy: () => Divy,
|
|
27
|
+
DivyError: () => DivyError,
|
|
28
|
+
addresses: () => addresses,
|
|
29
|
+
createDivy: () => createDivy,
|
|
30
|
+
defineDivyChain: () => defineDivyChain,
|
|
31
|
+
explorerAddress: () => explorerAddress,
|
|
32
|
+
explorerTx: () => explorerTx
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/client.ts
|
|
37
|
+
var import_viem2 = require("viem");
|
|
38
|
+
var import_accounts = require("viem/accounts");
|
|
39
|
+
|
|
40
|
+
// src/chain.ts
|
|
41
|
+
var import_viem = require("viem");
|
|
42
|
+
var CHAIN_ID = 4663;
|
|
43
|
+
var DEFAULT_RPC_URL = "https://rpc.mainnet.chain.robinhood.com";
|
|
44
|
+
var DEFAULT_API_URL = "https://divy-api-j8di.onrender.com";
|
|
45
|
+
var EXPLORER_URL = "https://robinhoodchain.blockscout.com";
|
|
46
|
+
var addresses = {
|
|
47
|
+
chainId: CHAIN_ID,
|
|
48
|
+
registry: "0x0fEf638d8C88e6eD38eDcD9aB21BF39D083c8B1b",
|
|
49
|
+
factory: "0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e",
|
|
50
|
+
escrow: "0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e",
|
|
51
|
+
rpcUrl: DEFAULT_RPC_URL,
|
|
52
|
+
apiUrl: DEFAULT_API_URL,
|
|
53
|
+
explorerUrl: EXPLORER_URL
|
|
54
|
+
};
|
|
55
|
+
function defineDivyChain(rpcUrl = DEFAULT_RPC_URL) {
|
|
56
|
+
return (0, import_viem.defineChain)({
|
|
57
|
+
id: CHAIN_ID,
|
|
58
|
+
name: "robinhood-chain",
|
|
59
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
60
|
+
rpcUrls: { default: { http: [rpcUrl] } },
|
|
61
|
+
blockExplorers: { default: { name: "Blockscout", url: EXPLORER_URL } }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function explorerTx(hash) {
|
|
65
|
+
return `${EXPLORER_URL}/tx/${hash}`;
|
|
66
|
+
}
|
|
67
|
+
function explorerAddress(address) {
|
|
68
|
+
return `${EXPLORER_URL}/address/${address}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/abi.ts
|
|
72
|
+
var curveAbi = [
|
|
73
|
+
{
|
|
74
|
+
type: "function",
|
|
75
|
+
name: "buy",
|
|
76
|
+
stateMutability: "payable",
|
|
77
|
+
inputs: [
|
|
78
|
+
{ name: "quoteIn", type: "uint256" },
|
|
79
|
+
{ name: "minTokensOut", type: "uint256" },
|
|
80
|
+
{ name: "recipient", type: "address" }
|
|
81
|
+
],
|
|
82
|
+
outputs: [{ name: "tokensOut", type: "uint256" }]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "function",
|
|
86
|
+
name: "sell",
|
|
87
|
+
stateMutability: "nonpayable",
|
|
88
|
+
inputs: [
|
|
89
|
+
{ name: "tokensIn", type: "uint256" },
|
|
90
|
+
{ name: "minQuoteOut", type: "uint256" },
|
|
91
|
+
{ name: "recipient", type: "address" }
|
|
92
|
+
],
|
|
93
|
+
outputs: [{ name: "quoteOut", type: "uint256" }]
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
var factoryTokenLaunchedAbi = [
|
|
97
|
+
{
|
|
98
|
+
type: "event",
|
|
99
|
+
name: "TokenLaunched",
|
|
100
|
+
inputs: [
|
|
101
|
+
{ indexed: true, name: "token", type: "address" },
|
|
102
|
+
{ indexed: true, name: "curve", type: "address" },
|
|
103
|
+
{ indexed: true, name: "deployer", type: "address" },
|
|
104
|
+
{ indexed: false, name: "pairToken", type: "address" },
|
|
105
|
+
{ indexed: false, name: "launchConfigId", type: "uint256" },
|
|
106
|
+
{ indexed: false, name: "graduationThreshold", type: "uint256" }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// src/errors.ts
|
|
112
|
+
var DivyError = class extends Error {
|
|
113
|
+
status;
|
|
114
|
+
constructor(message, status) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = "DivyError";
|
|
117
|
+
this.status = status;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/http.ts
|
|
122
|
+
var stringify = (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value);
|
|
123
|
+
async function apiRequest(apiUrl, path, options = {}) {
|
|
124
|
+
const headers = {};
|
|
125
|
+
if (options.body !== void 0) headers["content-type"] = "application/json";
|
|
126
|
+
if (options.apiKey) headers.authorization = `Bearer ${options.apiKey}`;
|
|
127
|
+
const res = await fetch(`${apiUrl}${path}`, {
|
|
128
|
+
method: options.method ?? (options.body !== void 0 ? "POST" : "GET"),
|
|
129
|
+
headers,
|
|
130
|
+
body: options.body !== void 0 ? stringify(options.body) : void 0
|
|
131
|
+
});
|
|
132
|
+
const text = await res.text();
|
|
133
|
+
let json;
|
|
134
|
+
try {
|
|
135
|
+
json = text ? JSON.parse(text) : {};
|
|
136
|
+
} catch {
|
|
137
|
+
throw new DivyError(`${apiUrl} returned a non-JSON response (HTTP ${res.status} ${res.statusText}): the API may be down or restarting`, res.status);
|
|
138
|
+
}
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
throw new DivyError(typeof json.error === "string" ? json.error : res.statusText, res.status);
|
|
141
|
+
}
|
|
142
|
+
return json;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/parse.ts
|
|
146
|
+
var big = (v) => BigInt(v);
|
|
147
|
+
var bigOrNull = (v) => v === null || v === void 0 ? null : big(v);
|
|
148
|
+
function parseInfo(j) {
|
|
149
|
+
return {
|
|
150
|
+
chain: j.chain,
|
|
151
|
+
registry: j.registry,
|
|
152
|
+
factory: j.factory,
|
|
153
|
+
escrow: j.escrow,
|
|
154
|
+
hostedWallets: j.hostedWallets,
|
|
155
|
+
feePolicy: {
|
|
156
|
+
treasury: j.feePolicy.treasury,
|
|
157
|
+
divyBps: j.feePolicy.divyBps === null || j.feePolicy.divyBps === void 0 ? null : Number(j.feePolicy.divyBps),
|
|
158
|
+
agentShareOfBaseFee: j.feePolicy.agentShareOfBaseFee,
|
|
159
|
+
ponsShare: j.feePolicy.ponsShare,
|
|
160
|
+
divyShare: j.feePolicy.divyShare
|
|
161
|
+
},
|
|
162
|
+
launchFee: { wei: big(j.launchFee.wei), eth: j.launchFee.eth },
|
|
163
|
+
maxCreatorTaxBps: j.maxCreatorTaxBps,
|
|
164
|
+
economics: j.economics,
|
|
165
|
+
indexedBlock: bigOrNull(j.indexedBlock),
|
|
166
|
+
keeper: j.keeper
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parseAgentLaunch(l) {
|
|
170
|
+
return {
|
|
171
|
+
token: l.token,
|
|
172
|
+
symbol: l.symbol,
|
|
173
|
+
name: l.name,
|
|
174
|
+
curve: l.curve,
|
|
175
|
+
pairToken: l.pairToken,
|
|
176
|
+
graduated: l.graduated,
|
|
177
|
+
launchedAt: l.launchedAt,
|
|
178
|
+
recorded: l.recorded
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function parseAgentRecord(j) {
|
|
182
|
+
return {
|
|
183
|
+
agent: j.agent,
|
|
184
|
+
split: j.split,
|
|
185
|
+
registered: j.registered,
|
|
186
|
+
record: { launches: big(j.record.launches), graduated: big(j.record.graduated), score: j.record.score },
|
|
187
|
+
fees: { agentEth: big(j.fees.agentEth), divyEth: big(j.fees.divyEth), pendingEscrowEth: big(j.fees.pendingEscrowEth) },
|
|
188
|
+
launches: j.launches.map(parseAgentLaunch),
|
|
189
|
+
...j.balanceEth !== void 0 ? { balanceEth: j.balanceEth } : {},
|
|
190
|
+
...j.payout !== void 0 ? { payout: j.payout } : {},
|
|
191
|
+
...j.label !== void 0 ? { label: j.label } : {}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function parseLeaderboard(j) {
|
|
195
|
+
return j.map((r) => ({ agent: r.agent, launches: r.launches, graduated: r.graduated, score: r.score, feesEth: big(r.feesEth) }));
|
|
196
|
+
}
|
|
197
|
+
function parseLaunches(j) {
|
|
198
|
+
return j.map((r) => ({
|
|
199
|
+
token: r.token,
|
|
200
|
+
agent: r.agent,
|
|
201
|
+
split: r.split,
|
|
202
|
+
curve: r.curve,
|
|
203
|
+
pairToken: r.pairToken,
|
|
204
|
+
name: r.name,
|
|
205
|
+
symbol: r.symbol,
|
|
206
|
+
launchedBlock: r.launchedBlock,
|
|
207
|
+
launchedAt: r.launchedAt,
|
|
208
|
+
recorded: r.recorded,
|
|
209
|
+
graduated: r.graduated
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
function parseHealth(j) {
|
|
213
|
+
return { ok: j.ok, indexedBlock: bigOrNull(j.indexedBlock), head: big(j.head), lag: bigOrNull(j.lag) };
|
|
214
|
+
}
|
|
215
|
+
function parseTx(t) {
|
|
216
|
+
return { to: t.to, data: t.data, value: big(t.value) };
|
|
217
|
+
}
|
|
218
|
+
function parseQuote(j) {
|
|
219
|
+
return {
|
|
220
|
+
curve: j.curve,
|
|
221
|
+
amountIn: big(j.amountIn),
|
|
222
|
+
amountOut: big(j.amountOut),
|
|
223
|
+
minOut: big(j.minOut),
|
|
224
|
+
feeBps: j.feeBps,
|
|
225
|
+
creatorTaxBps: j.creatorTaxBps,
|
|
226
|
+
snipeTaxBps: j.snipeTaxBps,
|
|
227
|
+
tx: parseTx(j.tx),
|
|
228
|
+
approveTx: j.approveTx ? parseTx(j.approveTx) : null
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/client.ts
|
|
233
|
+
var PRIVATE_KEY_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
234
|
+
var BPS_DENOM = 10000n;
|
|
235
|
+
function readAmount(amount) {
|
|
236
|
+
return typeof amount === "bigint" ? amount : BigInt(amount);
|
|
237
|
+
}
|
|
238
|
+
var Divy = class {
|
|
239
|
+
mode;
|
|
240
|
+
apiUrl;
|
|
241
|
+
apiKey;
|
|
242
|
+
account;
|
|
243
|
+
// viem's client generics resist a clean stored type across the three constructor branches;
|
|
244
|
+
// both are only ever touched through sendLocal(), which fixes the call shape in one place.
|
|
245
|
+
wallet;
|
|
246
|
+
pub;
|
|
247
|
+
chain;
|
|
248
|
+
constructor(options) {
|
|
249
|
+
const hasPrivateKey = "privateKey" in options && options.privateKey !== void 0;
|
|
250
|
+
const hasApiKey = "apiKey" in options && options.apiKey !== void 0;
|
|
251
|
+
if (hasPrivateKey && hasApiKey) {
|
|
252
|
+
throw new Error("pass either privateKey or apiKey to createDivy, not both");
|
|
253
|
+
}
|
|
254
|
+
this.apiUrl = options.apiUrl ?? addresses.apiUrl;
|
|
255
|
+
if (hasPrivateKey) {
|
|
256
|
+
const { privateKey, rpcUrl } = options;
|
|
257
|
+
if (!PRIVATE_KEY_RE.test(privateKey)) {
|
|
258
|
+
throw new Error("privateKey must be a 0x-prefixed 32-byte hex string");
|
|
259
|
+
}
|
|
260
|
+
this.mode = "self-custody";
|
|
261
|
+
this.account = (0, import_accounts.privateKeyToAccount)(privateKey);
|
|
262
|
+
this.chain = defineDivyChain(rpcUrl ?? addresses.rpcUrl);
|
|
263
|
+
const transport = (0, import_viem2.http)(rpcUrl ?? addresses.rpcUrl);
|
|
264
|
+
this.wallet = (0, import_viem2.createWalletClient)({ account: this.account, chain: this.chain, transport });
|
|
265
|
+
this.pub = (0, import_viem2.createPublicClient)({ chain: this.chain, transport });
|
|
266
|
+
} else if (hasApiKey) {
|
|
267
|
+
const { apiKey } = options;
|
|
268
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
269
|
+
throw new Error("apiKey must be a non-empty string");
|
|
270
|
+
}
|
|
271
|
+
this.mode = "hosted";
|
|
272
|
+
this.apiKey = apiKey;
|
|
273
|
+
} else {
|
|
274
|
+
this.mode = "read-only";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
requireSigner(action) {
|
|
278
|
+
if (this.mode === "read-only") {
|
|
279
|
+
throw new Error(`${action} requires a privateKey (self-custody) or apiKey (hosted) \u2014 this client is read-only`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async sendLocal(tx) {
|
|
283
|
+
if (!this.wallet || !this.pub || !this.account) throw new Error("self-custody wallet is not configured");
|
|
284
|
+
const hash = await this.wallet.sendTransaction({
|
|
285
|
+
account: this.account,
|
|
286
|
+
chain: this.chain,
|
|
287
|
+
to: tx.to,
|
|
288
|
+
data: tx.data,
|
|
289
|
+
value: tx.value
|
|
290
|
+
});
|
|
291
|
+
const receipt = await this.pub.waitForTransactionReceipt({ hash });
|
|
292
|
+
if (receipt.status !== "success") throw new Error(`transaction reverted on-chain: ${hash}`);
|
|
293
|
+
return receipt;
|
|
294
|
+
}
|
|
295
|
+
async buildTx(path, body) {
|
|
296
|
+
return apiRequest(this.apiUrl, path, { body: body ?? {} });
|
|
297
|
+
}
|
|
298
|
+
async info() {
|
|
299
|
+
return parseInfo(await apiRequest(this.apiUrl, "/"));
|
|
300
|
+
}
|
|
301
|
+
async agent(address) {
|
|
302
|
+
if (!address) {
|
|
303
|
+
if (this.mode === "hosted") {
|
|
304
|
+
return parseAgentRecord(await apiRequest(this.apiUrl, "/me", { apiKey: this.apiKey }));
|
|
305
|
+
}
|
|
306
|
+
if (this.mode === "self-custody" && this.account) {
|
|
307
|
+
address = this.account.address;
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error("agent() requires an address in read-only mode");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return parseAgentRecord(await apiRequest(this.apiUrl, `/agents/${address}`));
|
|
313
|
+
}
|
|
314
|
+
async leaderboard() {
|
|
315
|
+
return parseLeaderboard(await apiRequest(this.apiUrl, "/leaderboard"));
|
|
316
|
+
}
|
|
317
|
+
async launches(options = {}) {
|
|
318
|
+
const qs = options.limit ? `?limit=${options.limit}` : "";
|
|
319
|
+
return parseLaunches(await apiRequest(this.apiUrl, `/launches${qs}`));
|
|
320
|
+
}
|
|
321
|
+
async health() {
|
|
322
|
+
return parseHealth(await apiRequest(this.apiUrl, "/health"));
|
|
323
|
+
}
|
|
324
|
+
async register() {
|
|
325
|
+
this.requireSigner("register");
|
|
326
|
+
if (this.mode === "hosted") {
|
|
327
|
+
const me = await apiRequest(this.apiUrl, "/me", { apiKey: this.apiKey });
|
|
328
|
+
if (me.registered) return { registered: true, split: me.split };
|
|
329
|
+
return { registered: false, split: null };
|
|
330
|
+
}
|
|
331
|
+
const address = this.account.address;
|
|
332
|
+
const first = await apiRequest(this.apiUrl, "/agents", {
|
|
333
|
+
body: { address }
|
|
334
|
+
});
|
|
335
|
+
if (first.registered) return { registered: true, split: first.split };
|
|
336
|
+
const receipt = await this.sendLocal({ to: first.tx.to, data: first.tx.data, value: BigInt(first.tx.value ?? 0) });
|
|
337
|
+
const after = await apiRequest(this.apiUrl, "/agents", { body: { address } });
|
|
338
|
+
return { registered: after.registered, split: after.split, txHash: receipt.transactionHash };
|
|
339
|
+
}
|
|
340
|
+
async quote(params) {
|
|
341
|
+
let recipient = params.recipient;
|
|
342
|
+
if (!recipient && this.mode === "self-custody" && this.account) recipient = this.account.address;
|
|
343
|
+
return parseQuote(
|
|
344
|
+
await apiRequest(this.apiUrl, "/quote", {
|
|
345
|
+
body: {
|
|
346
|
+
curve: params.curve,
|
|
347
|
+
token: params.token,
|
|
348
|
+
side: params.side,
|
|
349
|
+
amount: readAmount(params.amount).toString(),
|
|
350
|
+
recipient: recipient ?? import_viem2.zeroAddress
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
async launch(params) {
|
|
356
|
+
this.requireSigner("launch");
|
|
357
|
+
if (this.mode === "hosted") {
|
|
358
|
+
const out = await apiRequest(this.apiUrl, "/launch", {
|
|
359
|
+
apiKey: this.apiKey,
|
|
360
|
+
body: params
|
|
361
|
+
});
|
|
362
|
+
return { token: out.token, curve: out.curve, txHash: out.txHash };
|
|
363
|
+
}
|
|
364
|
+
const address = this.account.address;
|
|
365
|
+
const built = await apiRequest(this.apiUrl, "/launch", { body: { ...params, agent: address } });
|
|
366
|
+
const receipt = await this.sendLocal(built.tx);
|
|
367
|
+
let launched;
|
|
368
|
+
for (const log of receipt.logs) {
|
|
369
|
+
try {
|
|
370
|
+
const ev = (0, import_viem2.decodeEventLog)({ abi: factoryTokenLaunchedAbi, data: log.data, topics: log.topics });
|
|
371
|
+
if (ev.eventName === "TokenLaunched") {
|
|
372
|
+
launched = ev.args;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!launched) throw new Error(`launch mined (${receipt.transactionHash}) but no TokenLaunched event was found`);
|
|
379
|
+
const result = { token: launched.token, curve: launched.curve, txHash: receipt.transactionHash };
|
|
380
|
+
try {
|
|
381
|
+
const recorded = await this.record(launched.token);
|
|
382
|
+
result.recordTxHash = recorded.txHash;
|
|
383
|
+
} catch (e) {
|
|
384
|
+
result.recordError = e instanceof Error ? e.message : String(e);
|
|
385
|
+
}
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
async record(token) {
|
|
389
|
+
if (this.mode !== "self-custody") {
|
|
390
|
+
throw new Error(
|
|
391
|
+
"record() requires self-custody (a privateKey): hosted and read-only launches are recorded automatically by Divy's keeper within about a minute \u2014 use waitForRecord() to wait for it"
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const built = await apiRequest(this.apiUrl, "/record", { body: { token } });
|
|
395
|
+
const receipt = await this.sendLocal(built.tx);
|
|
396
|
+
return { txHash: receipt.transactionHash };
|
|
397
|
+
}
|
|
398
|
+
async buy(params) {
|
|
399
|
+
return this.trade("buy", readAmount(params.amountWei), params);
|
|
400
|
+
}
|
|
401
|
+
async sell(params) {
|
|
402
|
+
return this.trade("sell", readAmount(params.amountTokens), params);
|
|
403
|
+
}
|
|
404
|
+
async trade(side, amount, params) {
|
|
405
|
+
this.requireSigner(side);
|
|
406
|
+
const slippageBps = params.slippageBps ?? 100;
|
|
407
|
+
if (this.mode === "hosted") {
|
|
408
|
+
if (slippageBps !== 100) {
|
|
409
|
+
throw new Error("slippageBps is not configurable in hosted mode: /trade hardcodes a 1% (100bps) slippage floor");
|
|
410
|
+
}
|
|
411
|
+
const out = await apiRequest(this.apiUrl, "/trade", {
|
|
412
|
+
apiKey: this.apiKey,
|
|
413
|
+
body: { curve: params.curve, token: params.token, side, amount: amount.toString() }
|
|
414
|
+
});
|
|
415
|
+
return { txHash: out.txHash, amountOut: BigInt(out.amountOut) };
|
|
416
|
+
}
|
|
417
|
+
const address = this.account.address;
|
|
418
|
+
const q = await this.quote({ curve: params.curve, token: params.token, side, amount, recipient: address });
|
|
419
|
+
let tradeTx = q.tx;
|
|
420
|
+
if (slippageBps !== 100) {
|
|
421
|
+
const minOut = q.amountOut * (BPS_DENOM - BigInt(slippageBps)) / BPS_DENOM;
|
|
422
|
+
const data = (0, import_viem2.encodeFunctionData)({ abi: curveAbi, functionName: side, args: [q.amountIn, minOut, address] });
|
|
423
|
+
tradeTx = { ...q.tx, data };
|
|
424
|
+
}
|
|
425
|
+
if (q.approveTx) await this.sendLocal(q.approveTx);
|
|
426
|
+
const receipt = await this.sendLocal(tradeTx);
|
|
427
|
+
return { txHash: receipt.transactionHash, amountOut: q.amountOut };
|
|
428
|
+
}
|
|
429
|
+
async harvest() {
|
|
430
|
+
this.requireSigner("harvest");
|
|
431
|
+
if (this.mode === "hosted") {
|
|
432
|
+
const out = await apiRequest(this.apiUrl, "/harvest", { apiKey: this.apiKey, body: {} });
|
|
433
|
+
return { txHash: out.txHash };
|
|
434
|
+
}
|
|
435
|
+
const address = this.account.address;
|
|
436
|
+
const built = await apiRequest(this.apiUrl, "/harvest", { body: { agent: address } });
|
|
437
|
+
const receipt = await this.sendLocal(built.tx);
|
|
438
|
+
return { txHash: receipt.transactionHash };
|
|
439
|
+
}
|
|
440
|
+
async waitForRecord(token, options = {}) {
|
|
441
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
442
|
+
const intervalMs = options.intervalMs ?? 3e3;
|
|
443
|
+
const deadline = Date.now() + timeoutMs;
|
|
444
|
+
const wanted = token.toLowerCase();
|
|
445
|
+
for (; ; ) {
|
|
446
|
+
const rows = await this.launches({ limit: 200 });
|
|
447
|
+
const row = rows.find((r) => r.token.toLowerCase() === wanted);
|
|
448
|
+
if (row?.recorded) return row;
|
|
449
|
+
if (Date.now() >= deadline) {
|
|
450
|
+
throw new DivyError(`timed out waiting for ${token} to be recorded`, 408);
|
|
451
|
+
}
|
|
452
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
function createDivy(options = {}) {
|
|
457
|
+
return new Divy(options);
|
|
458
|
+
}
|
|
459
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
460
|
+
0 && (module.exports = {
|
|
461
|
+
CHAIN_ID,
|
|
462
|
+
DEFAULT_API_URL,
|
|
463
|
+
DEFAULT_RPC_URL,
|
|
464
|
+
Divy,
|
|
465
|
+
DivyError,
|
|
466
|
+
addresses,
|
|
467
|
+
createDivy,
|
|
468
|
+
defineDivyChain,
|
|
469
|
+
explorerAddress,
|
|
470
|
+
explorerTx
|
|
471
|
+
});
|
|
472
|
+
//# sourceMappingURL=index.cjs.map
|