@ripplesdotrun/agent-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/README.md +84 -0
- package/dist/abi.d.ts +756 -0
- package/dist/abi.js +75 -0
- package/dist/agent.d.ts +157 -0
- package/dist/agent.js +383 -0
- package/dist/chains.d.ts +17 -0
- package/dist/chains.js +33 -0
- package/dist/defaults.d.ts +29 -0
- package/dist/defaults.js +29 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +52 -0
package/dist/abi.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { parseAbi } from "viem";
|
|
2
|
+
const CHARTER = "(address operator,uint128 dailySpend,uint128 perCallSpend,uint128 dailySell,uint32 dailyMint,address[] payees)";
|
|
3
|
+
const LAUNCH_PARAMS = "(string name,string symbol,uint256 curveSupply,uint256 lpTokenSupply,uint256 vQuoteInit,uint256 vTokenInit,uint256 graduationQuote,uint64 lpUnlockAt,uint96 creatorTaxBps,address quote,string logo,string description,string twitter,string telegram,string discord,string website,string farcaster)";
|
|
4
|
+
const COLLECTION_PARAMS = "(string name,string symbol,uint256 priceQuote,uint256 maxSupply,uint256 perWalletCap,uint64 startTime,uint64 endTime,uint8 mode,string baseURI,string placeholderURI,uint96 royaltyBps,address quote)";
|
|
5
|
+
const LINKED_PARAMS = "(uint96 nftAllocationBps,uint96 mintToCurveBps,uint64 vestDuration,uint64 vestCliff)";
|
|
6
|
+
const DEV_BUY = "(uint256 initialBuy,uint256 minTokensOut,address[] snipeExempt)";
|
|
7
|
+
export const AGENT_FACTORY_ABI = parseAbi([
|
|
8
|
+
`function createAndLaunch(${CHARTER} charter,${LAUNCH_PARAMS} tp,${COLLECTION_PARAMS} np,${LINKED_PARAMS} lp,${DEV_BUY} d,uint256 runway) returns (address treasury,address token,address locker,address collection,address vesting)`,
|
|
9
|
+
`function create(${CHARTER} charter) returns (address)`,
|
|
10
|
+
"function isFromFactory(address treasury) view returns (bool)",
|
|
11
|
+
"function treasuryCount() view returns (uint256)",
|
|
12
|
+
"function treasuries(uint256 offset,uint256 limit) view returns (address[])",
|
|
13
|
+
"function RUNWAY_ASSET() view returns (address)",
|
|
14
|
+
"function TOKEN_FACTORY() view returns (address)",
|
|
15
|
+
"event AgentTreasuryCreated(address indexed creator,address indexed treasury,address indexed operator)",
|
|
16
|
+
"error DuplicatePayee()",
|
|
17
|
+
"error InvalidSpendLimits()",
|
|
18
|
+
"error NoRunway()",
|
|
19
|
+
"error NotAContract()",
|
|
20
|
+
"error TooManyPayees()",
|
|
21
|
+
"error WrongPayment()",
|
|
22
|
+
"error ZeroAddress()",
|
|
23
|
+
]);
|
|
24
|
+
export const AGENT_TREASURY_ABI = parseAbi([
|
|
25
|
+
"function buy(uint256 amountIn,uint256 minAmountOut,uint256 deadline) returns (uint256 spent,uint256 received)",
|
|
26
|
+
"function sell(uint256 amountIn,uint256 minAmountOut,uint256 deadline) returns (uint256 sold,uint256 received)",
|
|
27
|
+
"function mint(uint256 quantity,uint256 minRoutedTotal)",
|
|
28
|
+
"function pay(address to,uint256 amount)",
|
|
29
|
+
"function note(bytes32 digest,string uri)",
|
|
30
|
+
"function claimFees() returns (uint256 quoteAmount,uint256 tokenAmount)",
|
|
31
|
+
"function claimVested() returns (uint256 amount)",
|
|
32
|
+
"function setCollectionBaseURI(string uri)",
|
|
33
|
+
"function setCollectionPlaceholderURI(string uri)",
|
|
34
|
+
"function freezeCollectionMetadata()",
|
|
35
|
+
"function charter() view returns (address operator,uint128 dailySpend,uint128 perCallSpend,uint128 dailySell,uint32 dailyMint,address[] payees)",
|
|
36
|
+
"function remainingToday() view returns (uint256 spend,uint256 sellable,uint256 mintable)",
|
|
37
|
+
"function payees() view returns (address[])",
|
|
38
|
+
"function CREATOR() view returns (address)",
|
|
39
|
+
"function OPERATOR() view returns (address)",
|
|
40
|
+
"function QUOTE() view returns (address)",
|
|
41
|
+
"function token() view returns (address)",
|
|
42
|
+
"function locker() view returns (address)",
|
|
43
|
+
"function collection() view returns (address)",
|
|
44
|
+
"function vesting() view returns (address)",
|
|
45
|
+
"event Launched(address indexed token,address indexed locker,address collection,address vesting)",
|
|
46
|
+
"event Bought(uint256 spent,uint256 received)",
|
|
47
|
+
"event Sold(uint256 sold,uint256 received)",
|
|
48
|
+
"event Minted(uint256 quantity,uint256 cost)",
|
|
49
|
+
"event Paid(address indexed to,uint256 amount)",
|
|
50
|
+
"event FeesClaimed(uint256 quoteAmount,uint256 tokenAmount)",
|
|
51
|
+
"event VestedClaimed(uint256 amount)",
|
|
52
|
+
"event Noted(bytes32 indexed digest,string uri)",
|
|
53
|
+
"error AlreadyLaunched()",
|
|
54
|
+
"error AmountTooLarge()",
|
|
55
|
+
"error NotCreator()",
|
|
56
|
+
"error NotLaunched()",
|
|
57
|
+
"error NotOperator()",
|
|
58
|
+
"error NotPayee(address to)",
|
|
59
|
+
"error NoVenue()",
|
|
60
|
+
"error OverCallSpend(uint256 requested,uint256 limit)",
|
|
61
|
+
"error OverDailyMint(uint256 requested,uint256 limit)",
|
|
62
|
+
"error OverDailySell(uint256 requested,uint256 limit)",
|
|
63
|
+
"error OverDailySpend(uint256 requested,uint256 limit)",
|
|
64
|
+
"error QuoteMismatch()",
|
|
65
|
+
"error UnexpectedBalance()",
|
|
66
|
+
"error ZeroAmount()",
|
|
67
|
+
]);
|
|
68
|
+
export const ERC20_ABI = parseAbi([
|
|
69
|
+
"function approve(address spender,uint256 value) returns (bool)",
|
|
70
|
+
"function allowance(address owner,address spender) view returns (uint256)",
|
|
71
|
+
"function balanceOf(address account) view returns (uint256)",
|
|
72
|
+
"function decimals() view returns (uint8)",
|
|
73
|
+
"function symbol() view returns (string)",
|
|
74
|
+
]);
|
|
75
|
+
export const LAUNCH_FACTORY_ABI = parseAbi(["function launchFee() view returns (uint256)"]);
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type Account, type Address, type Hash, type Transport } from "viem";
|
|
2
|
+
import { type RipplesDeployment, type RipplesNetwork } from "./chains.js";
|
|
3
|
+
import { LINKED_DEFAULTS } from "./defaults.js";
|
|
4
|
+
/**
|
|
5
|
+
* What the agent may do with its own money, fixed when its treasury is deployed.
|
|
6
|
+
*
|
|
7
|
+
* Amounts are written the way a person writes them: `"0.05"` is 0.05 of the settlement asset,
|
|
8
|
+
* and `dailySell` is in the launch's own token. There is no setter for any of this, so these
|
|
9
|
+
* are the limits for the life of the launch.
|
|
10
|
+
*/
|
|
11
|
+
export type Charter = {
|
|
12
|
+
/** The key that works the agent. The only address that can spend, and only the five ways. */
|
|
13
|
+
operator: Address;
|
|
14
|
+
/** Everything it spends counts against this: buying, minting and paying share one budget. */
|
|
15
|
+
dailySpend: string;
|
|
16
|
+
/** At most the daily figure, so a day's budget cannot leave in one transaction. */
|
|
17
|
+
perCallSpend: string;
|
|
18
|
+
/** In the launch's own token. Omit it and the agent can never sell what it launched. */
|
|
19
|
+
dailySell?: string;
|
|
20
|
+
/** Pieces it may mint from its own collection each day. Omit it and it never mints. */
|
|
21
|
+
dailyMint?: number;
|
|
22
|
+
/** Where its running costs go. It can pay these addresses and no others, ever. */
|
|
23
|
+
payees?: readonly Address[];
|
|
24
|
+
};
|
|
25
|
+
export type TokenPlan = {
|
|
26
|
+
name: string;
|
|
27
|
+
symbol: string;
|
|
28
|
+
/** A public image URL. The token carries it on chain, which is what terminals read. */
|
|
29
|
+
image: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
website?: string;
|
|
32
|
+
twitter?: string;
|
|
33
|
+
telegram?: string;
|
|
34
|
+
discord?: string;
|
|
35
|
+
farcaster?: string;
|
|
36
|
+
};
|
|
37
|
+
export type CollectionPlan = {
|
|
38
|
+
name: string;
|
|
39
|
+
symbol: string;
|
|
40
|
+
/** Price per piece, in the settlement asset. */
|
|
41
|
+
price: string;
|
|
42
|
+
supply: number;
|
|
43
|
+
/** Where the finished artwork lives, with a trailing slash. */
|
|
44
|
+
baseUri: string;
|
|
45
|
+
placeholderUri?: string;
|
|
46
|
+
/** Secondary-sale royalty, in basis points. At most 1000. */
|
|
47
|
+
royaltyBps?: number;
|
|
48
|
+
perWalletCap?: number;
|
|
49
|
+
/** Unix seconds. Omit for a mint that never closes. */
|
|
50
|
+
endTime?: number;
|
|
51
|
+
};
|
|
52
|
+
export type LaunchPlan = {
|
|
53
|
+
charter: Charter;
|
|
54
|
+
token: TokenPlan;
|
|
55
|
+
collection: CollectionPlan;
|
|
56
|
+
/** What goes into the treasury. It has to cover the launch fee. */
|
|
57
|
+
runway: string;
|
|
58
|
+
/** The coupling, if the defaults are not wanted. Frozen at create either way. */
|
|
59
|
+
linked?: Partial<typeof LINKED_DEFAULTS>;
|
|
60
|
+
};
|
|
61
|
+
export type LaunchResult = {
|
|
62
|
+
treasury: Address;
|
|
63
|
+
token: Address;
|
|
64
|
+
market: Address;
|
|
65
|
+
collection: Address;
|
|
66
|
+
vesting: Address;
|
|
67
|
+
transaction: Hash;
|
|
68
|
+
};
|
|
69
|
+
export type AgentCharter = {
|
|
70
|
+
operator: Address;
|
|
71
|
+
dailySpend: string;
|
|
72
|
+
perCallSpend: string;
|
|
73
|
+
dailySell: string;
|
|
74
|
+
dailyMint: number;
|
|
75
|
+
payees: readonly Address[];
|
|
76
|
+
};
|
|
77
|
+
export type Budget = {
|
|
78
|
+
spend: string;
|
|
79
|
+
sellable: string;
|
|
80
|
+
mintable: number;
|
|
81
|
+
};
|
|
82
|
+
export type Activity = {
|
|
83
|
+
kind: "launched" | "bought" | "sold" | "minted" | "paid" | "claimed" | "vested" | "noted";
|
|
84
|
+
block: bigint;
|
|
85
|
+
transaction: Hash;
|
|
86
|
+
detail: string;
|
|
87
|
+
};
|
|
88
|
+
export type RipplesAgentOptions = {
|
|
89
|
+
network?: RipplesNetwork;
|
|
90
|
+
/** Signs. Omit for a read-only client. */
|
|
91
|
+
account?: Account;
|
|
92
|
+
/** Overrides the network's public endpoint. */
|
|
93
|
+
rpcUrl?: string;
|
|
94
|
+
transport?: Transport;
|
|
95
|
+
/** An existing treasury to work. `launch` fills this in when it creates one. */
|
|
96
|
+
treasury?: Address;
|
|
97
|
+
};
|
|
98
|
+
export declare class RipplesAgentError extends Error {
|
|
99
|
+
constructor(message: string, options?: {
|
|
100
|
+
cause?: unknown;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export type AgentClient = ReturnType<typeof createRipplesAgent>;
|
|
104
|
+
export declare function createRipplesAgent(options?: RipplesAgentOptions): {
|
|
105
|
+
network: RipplesNetwork;
|
|
106
|
+
deployment: RipplesDeployment;
|
|
107
|
+
readonly treasury: Address | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Create the agent, its market and its collection, in one transaction.
|
|
110
|
+
*
|
|
111
|
+
* Two wallet requests: a spending permission for the runway, then the launch itself. The
|
|
112
|
+
* factory deploys the treasury, moves the runway in, and has the treasury open the launch,
|
|
113
|
+
* so the treasury is the launch's creator of record and the creator's share of every trade
|
|
114
|
+
* fee is the agent's working capital. A failure anywhere unwinds all of it.
|
|
115
|
+
*/
|
|
116
|
+
launch(plan: LaunchPlan): Promise<LaunchResult>;
|
|
117
|
+
/** Buy the agent's own token on its own market, into the treasury. */
|
|
118
|
+
buy(amount: string, options?: {
|
|
119
|
+
minReceived?: string;
|
|
120
|
+
deadline?: number;
|
|
121
|
+
}): Promise<`0x${string}`>;
|
|
122
|
+
/** Sell it back, against the charter's daily ceiling. Refused outright when that is zero. */
|
|
123
|
+
sell(amount: string, options?: {
|
|
124
|
+
minReceived?: string;
|
|
125
|
+
deadline?: number;
|
|
126
|
+
}): Promise<`0x${string}`>;
|
|
127
|
+
/** Mint from its own collection. The pieces stay with the treasury. */
|
|
128
|
+
mint(quantity: number, options?: {
|
|
129
|
+
minRouted?: string;
|
|
130
|
+
}): Promise<`0x${string}`>;
|
|
131
|
+
/** Pay one of the charter's published addresses. */
|
|
132
|
+
pay(to: Address, amount: string): Promise<`0x${string}`>;
|
|
133
|
+
/** Write an entry in the agent's public run log. */
|
|
134
|
+
note(text: string, uri?: string): Promise<`0x${string}`>;
|
|
135
|
+
/** Take in the creator's share of every trade and the collection's share of every mint. */
|
|
136
|
+
claim(): Promise<`0x${string}`>;
|
|
137
|
+
/** Take in the token slice the agent's own mints earned it, once the market has graduated. */
|
|
138
|
+
claimVested(): Promise<`0x${string}`>;
|
|
139
|
+
/** The limits, as published. None of them can change. */
|
|
140
|
+
charter(): Promise<AgentCharter>;
|
|
141
|
+
/** What is left of each ceiling today. The window is the UTC day. */
|
|
142
|
+
budget(): Promise<Budget>;
|
|
143
|
+
/** Everything the agent has done, newest first. */
|
|
144
|
+
activity(limit?: number): Promise<readonly Activity[]>;
|
|
145
|
+
/** The launch this treasury opened. */
|
|
146
|
+
launched(): Promise<{
|
|
147
|
+
token: `0x${string}`;
|
|
148
|
+
market: `0x${string}`;
|
|
149
|
+
collection: `0x${string}`;
|
|
150
|
+
vesting: `0x${string}`;
|
|
151
|
+
}>;
|
|
152
|
+
};
|
|
153
|
+
/** Whether this chain's factory deployed `treasury`, which is what makes its charter worth reading. */
|
|
154
|
+
export declare function readAgent(treasury: Address, options?: {
|
|
155
|
+
network?: RipplesNetwork;
|
|
156
|
+
rpcUrl?: string;
|
|
157
|
+
}): Promise<boolean>;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { createPublicClient, createWalletClient, formatUnits, http, keccak256, parseUnits, toHex, } from "viem";
|
|
2
|
+
import { AGENT_FACTORY_ABI, AGENT_TREASURY_ABI, ERC20_ABI, LAUNCH_FACTORY_ABI } from "./abi.js";
|
|
3
|
+
import { chainOf, DEPLOYMENTS } from "./chains.js";
|
|
4
|
+
import { LINKED_DEFAULTS, LINKED_RESERVES, MAX_PAYEES, PREGEN } from "./defaults.js";
|
|
5
|
+
const DAY = 86400n;
|
|
6
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
7
|
+
export class RipplesAgentError extends Error {
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = "RipplesAgentError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createRipplesAgent(options = {}) {
|
|
14
|
+
const network = options.network ?? "robinhood";
|
|
15
|
+
const record = DEPLOYMENTS[network];
|
|
16
|
+
const chain = chainOf(network);
|
|
17
|
+
const transport = options.transport ?? http(options.rpcUrl ?? record.rpcUrl);
|
|
18
|
+
const publicClient = createPublicClient({ chain, transport });
|
|
19
|
+
const wallet = options.account
|
|
20
|
+
? createWalletClient({ account: options.account, chain, transport })
|
|
21
|
+
: undefined;
|
|
22
|
+
let treasury = options.treasury;
|
|
23
|
+
const quote = (value) => parseUnits(value, record.quoteDecimals);
|
|
24
|
+
const showQuote = (value) => formatUnits(value, record.quoteDecimals);
|
|
25
|
+
const token = (value) => parseUnits(value, 18);
|
|
26
|
+
const showToken = (value) => formatUnits(value, 18);
|
|
27
|
+
function signer() {
|
|
28
|
+
if (!wallet || !options.account) {
|
|
29
|
+
throw new RipplesAgentError("This client is read-only. Pass an `account` to send transactions.");
|
|
30
|
+
}
|
|
31
|
+
return { wallet, account: options.account };
|
|
32
|
+
}
|
|
33
|
+
function held() {
|
|
34
|
+
if (!treasury) {
|
|
35
|
+
throw new RipplesAgentError("No treasury. Call `launch` first, or pass `treasury` when you create the client.");
|
|
36
|
+
}
|
|
37
|
+
return treasury;
|
|
38
|
+
}
|
|
39
|
+
async function send(request) {
|
|
40
|
+
const { wallet } = signer();
|
|
41
|
+
const hash = await wallet.writeContract(request);
|
|
42
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
43
|
+
if (receipt.status !== "success") {
|
|
44
|
+
throw new RipplesAgentError(`The transaction reverted. ${record.explorer}/tx/${hash}`);
|
|
45
|
+
}
|
|
46
|
+
return hash;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
network,
|
|
50
|
+
deployment: record,
|
|
51
|
+
get treasury() {
|
|
52
|
+
return treasury;
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Create the agent, its market and its collection, in one transaction.
|
|
56
|
+
*
|
|
57
|
+
* Two wallet requests: a spending permission for the runway, then the launch itself. The
|
|
58
|
+
* factory deploys the treasury, moves the runway in, and has the treasury open the launch,
|
|
59
|
+
* so the treasury is the launch's creator of record and the creator's share of every trade
|
|
60
|
+
* fee is the agent's working capital. A failure anywhere unwinds all of it.
|
|
61
|
+
*/
|
|
62
|
+
async launch(plan) {
|
|
63
|
+
const { account } = signer();
|
|
64
|
+
const runway = quote(plan.runway);
|
|
65
|
+
const fee = await publicClient.readContract({
|
|
66
|
+
address: record.tokenLaunchFactory,
|
|
67
|
+
abi: LAUNCH_FACTORY_ABI,
|
|
68
|
+
functionName: "launchFee",
|
|
69
|
+
});
|
|
70
|
+
if (runway < fee) {
|
|
71
|
+
throw new RipplesAgentError(`The runway has to cover the ${showQuote(fee)} ${record.quoteSymbol} launch fee.`);
|
|
72
|
+
}
|
|
73
|
+
const charter = chartered(plan.charter, quote, token);
|
|
74
|
+
const balance = await publicClient.readContract({
|
|
75
|
+
address: record.quote,
|
|
76
|
+
abi: ERC20_ABI,
|
|
77
|
+
functionName: "balanceOf",
|
|
78
|
+
args: [account.address],
|
|
79
|
+
});
|
|
80
|
+
if (balance < runway) {
|
|
81
|
+
throw new RipplesAgentError(`This wallet holds ${showQuote(balance)} ${record.quoteSymbol} and the runway is ${plan.runway}.`);
|
|
82
|
+
}
|
|
83
|
+
const allowance = await publicClient.readContract({
|
|
84
|
+
address: record.quote,
|
|
85
|
+
abi: ERC20_ABI,
|
|
86
|
+
functionName: "allowance",
|
|
87
|
+
args: [account.address, record.agentTreasuryFactory],
|
|
88
|
+
});
|
|
89
|
+
if (allowance < runway) {
|
|
90
|
+
await send({
|
|
91
|
+
address: record.quote,
|
|
92
|
+
abi: ERC20_ABI,
|
|
93
|
+
functionName: "approve",
|
|
94
|
+
args: [record.agentTreasuryFactory, runway],
|
|
95
|
+
account,
|
|
96
|
+
chain,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const linked = { ...LINKED_DEFAULTS, ...plan.linked };
|
|
100
|
+
const request = {
|
|
101
|
+
address: record.agentTreasuryFactory,
|
|
102
|
+
abi: AGENT_FACTORY_ABI,
|
|
103
|
+
functionName: "createAndLaunch",
|
|
104
|
+
args: [
|
|
105
|
+
charter,
|
|
106
|
+
{
|
|
107
|
+
name: plan.token.name,
|
|
108
|
+
symbol: plan.token.symbol,
|
|
109
|
+
...LINKED_RESERVES,
|
|
110
|
+
creatorTaxBps: 0n,
|
|
111
|
+
quote: ZERO,
|
|
112
|
+
logo: plan.token.image,
|
|
113
|
+
description: plan.token.description ?? "",
|
|
114
|
+
twitter: plan.token.twitter ?? "",
|
|
115
|
+
telegram: plan.token.telegram ?? "",
|
|
116
|
+
discord: plan.token.discord ?? "",
|
|
117
|
+
website: plan.token.website ?? "",
|
|
118
|
+
farcaster: plan.token.farcaster ?? "",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: plan.collection.name,
|
|
122
|
+
symbol: plan.collection.symbol,
|
|
123
|
+
priceQuote: quote(plan.collection.price),
|
|
124
|
+
maxSupply: BigInt(plan.collection.supply),
|
|
125
|
+
perWalletCap: BigInt(plan.collection.perWalletCap ?? 0),
|
|
126
|
+
startTime: 0n,
|
|
127
|
+
endTime: BigInt(plan.collection.endTime ?? 0),
|
|
128
|
+
mode: PREGEN,
|
|
129
|
+
baseURI: plan.collection.baseUri,
|
|
130
|
+
placeholderURI: plan.collection.placeholderUri ?? "",
|
|
131
|
+
royaltyBps: BigInt(plan.collection.royaltyBps ?? 500),
|
|
132
|
+
quote: ZERO,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
nftAllocationBps: BigInt(linked.allocationBps),
|
|
136
|
+
mintToCurveBps: BigInt(linked.mintToCurveBps),
|
|
137
|
+
vestDuration: BigInt(linked.vestDurationDays) * DAY,
|
|
138
|
+
vestCliff: BigInt(linked.vestCliffDays) * DAY,
|
|
139
|
+
},
|
|
140
|
+
{ initialBuy: 0n, minTokensOut: 0n, snipeExempt: [] },
|
|
141
|
+
runway,
|
|
142
|
+
],
|
|
143
|
+
account,
|
|
144
|
+
chain,
|
|
145
|
+
};
|
|
146
|
+
const { result } = await publicClient.simulateContract(request);
|
|
147
|
+
const transaction = await send(request);
|
|
148
|
+
const [created, launchToken, market, collection, vesting] = result;
|
|
149
|
+
treasury = created;
|
|
150
|
+
return {
|
|
151
|
+
treasury: created,
|
|
152
|
+
token: launchToken,
|
|
153
|
+
market: market,
|
|
154
|
+
collection: collection,
|
|
155
|
+
vesting: vesting,
|
|
156
|
+
transaction,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
/** Buy the agent's own token on its own market, into the treasury. */
|
|
160
|
+
async buy(amount, options = {}) {
|
|
161
|
+
const { account } = signer();
|
|
162
|
+
return send({
|
|
163
|
+
address: held(),
|
|
164
|
+
abi: AGENT_TREASURY_ABI,
|
|
165
|
+
functionName: "buy",
|
|
166
|
+
args: [
|
|
167
|
+
quote(amount),
|
|
168
|
+
options.minReceived ? token(options.minReceived) : 0n,
|
|
169
|
+
BigInt(options.deadline ?? Math.floor(Date.now() / 1000) + 300),
|
|
170
|
+
],
|
|
171
|
+
account,
|
|
172
|
+
chain,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
/** Sell it back, against the charter's daily ceiling. Refused outright when that is zero. */
|
|
176
|
+
async sell(amount, options = {}) {
|
|
177
|
+
const { account } = signer();
|
|
178
|
+
return send({
|
|
179
|
+
address: held(),
|
|
180
|
+
abi: AGENT_TREASURY_ABI,
|
|
181
|
+
functionName: "sell",
|
|
182
|
+
args: [
|
|
183
|
+
token(amount),
|
|
184
|
+
options.minReceived ? quote(options.minReceived) : 0n,
|
|
185
|
+
BigInt(options.deadline ?? Math.floor(Date.now() / 1000) + 300),
|
|
186
|
+
],
|
|
187
|
+
account,
|
|
188
|
+
chain,
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
/** Mint from its own collection. The pieces stay with the treasury. */
|
|
192
|
+
async mint(quantity, options = {}) {
|
|
193
|
+
const { account } = signer();
|
|
194
|
+
return send({
|
|
195
|
+
address: held(),
|
|
196
|
+
abi: AGENT_TREASURY_ABI,
|
|
197
|
+
functionName: "mint",
|
|
198
|
+
args: [BigInt(quantity), options.minRouted ? quote(options.minRouted) : 0n],
|
|
199
|
+
account,
|
|
200
|
+
chain,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
/** Pay one of the charter's published addresses. */
|
|
204
|
+
async pay(to, amount) {
|
|
205
|
+
const { account } = signer();
|
|
206
|
+
return send({
|
|
207
|
+
address: held(),
|
|
208
|
+
abi: AGENT_TREASURY_ABI,
|
|
209
|
+
functionName: "pay",
|
|
210
|
+
args: [to, quote(amount)],
|
|
211
|
+
account,
|
|
212
|
+
chain,
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
/** Write an entry in the agent's public run log. */
|
|
216
|
+
async note(text, uri = "") {
|
|
217
|
+
const { account } = signer();
|
|
218
|
+
return send({
|
|
219
|
+
address: held(),
|
|
220
|
+
abi: AGENT_TREASURY_ABI,
|
|
221
|
+
functionName: "note",
|
|
222
|
+
args: [keccak256(toHex(text)), uri],
|
|
223
|
+
account,
|
|
224
|
+
chain,
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
/** Take in the creator's share of every trade and the collection's share of every mint. */
|
|
228
|
+
async claim() {
|
|
229
|
+
const { account } = signer();
|
|
230
|
+
return send({
|
|
231
|
+
address: held(),
|
|
232
|
+
abi: AGENT_TREASURY_ABI,
|
|
233
|
+
functionName: "claimFees",
|
|
234
|
+
args: [],
|
|
235
|
+
account,
|
|
236
|
+
chain,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
/** Take in the token slice the agent's own mints earned it, once the market has graduated. */
|
|
240
|
+
async claimVested() {
|
|
241
|
+
const { account } = signer();
|
|
242
|
+
return send({
|
|
243
|
+
address: held(),
|
|
244
|
+
abi: AGENT_TREASURY_ABI,
|
|
245
|
+
functionName: "claimVested",
|
|
246
|
+
args: [],
|
|
247
|
+
account,
|
|
248
|
+
chain,
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
/** The limits, as published. None of them can change. */
|
|
252
|
+
async charter() {
|
|
253
|
+
const [operator, dailySpend, perCallSpend, dailySell, dailyMint, payees] = await publicClient.readContract({
|
|
254
|
+
address: held(),
|
|
255
|
+
abi: AGENT_TREASURY_ABI,
|
|
256
|
+
functionName: "charter",
|
|
257
|
+
});
|
|
258
|
+
return {
|
|
259
|
+
operator,
|
|
260
|
+
dailySpend: showQuote(dailySpend),
|
|
261
|
+
perCallSpend: showQuote(perCallSpend),
|
|
262
|
+
dailySell: showToken(dailySell),
|
|
263
|
+
dailyMint: Number(dailyMint),
|
|
264
|
+
payees,
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
/** What is left of each ceiling today. The window is the UTC day. */
|
|
268
|
+
async budget() {
|
|
269
|
+
const [spend, sellable, mintable] = await publicClient.readContract({
|
|
270
|
+
address: held(),
|
|
271
|
+
abi: AGENT_TREASURY_ABI,
|
|
272
|
+
functionName: "remainingToday",
|
|
273
|
+
});
|
|
274
|
+
return { spend: showQuote(spend), sellable: showToken(sellable), mintable: Number(mintable) };
|
|
275
|
+
},
|
|
276
|
+
/** Everything the agent has done, newest first. */
|
|
277
|
+
async activity(limit = 50) {
|
|
278
|
+
return readActivity(publicClient, held(), { limit, showQuote, showToken });
|
|
279
|
+
},
|
|
280
|
+
/** The launch this treasury opened. */
|
|
281
|
+
async launched() {
|
|
282
|
+
const address = held();
|
|
283
|
+
const [launchToken, market, collection, vesting] = await Promise.all([
|
|
284
|
+
publicClient.readContract({ address, abi: AGENT_TREASURY_ABI, functionName: "token" }),
|
|
285
|
+
publicClient.readContract({ address, abi: AGENT_TREASURY_ABI, functionName: "locker" }),
|
|
286
|
+
publicClient.readContract({ address, abi: AGENT_TREASURY_ABI, functionName: "collection" }),
|
|
287
|
+
publicClient.readContract({ address, abi: AGENT_TREASURY_ABI, functionName: "vesting" }),
|
|
288
|
+
]);
|
|
289
|
+
return { token: launchToken, market, collection, vesting };
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** Whether this chain's factory deployed `treasury`, which is what makes its charter worth reading. */
|
|
294
|
+
export async function readAgent(treasury, options = {}) {
|
|
295
|
+
const network = options.network ?? "robinhood";
|
|
296
|
+
const record = DEPLOYMENTS[network];
|
|
297
|
+
const client = createPublicClient({
|
|
298
|
+
chain: chainOf(network),
|
|
299
|
+
transport: http(options.rpcUrl ?? record.rpcUrl),
|
|
300
|
+
});
|
|
301
|
+
return client.readContract({
|
|
302
|
+
address: record.agentTreasuryFactory,
|
|
303
|
+
abi: AGENT_FACTORY_ABI,
|
|
304
|
+
functionName: "isFromFactory",
|
|
305
|
+
args: [treasury],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function chartered(charter, quote, token) {
|
|
309
|
+
const payees = charter.payees ?? [];
|
|
310
|
+
if (payees.length > MAX_PAYEES) {
|
|
311
|
+
throw new RipplesAgentError(`A charter names at most ${MAX_PAYEES} payees.`);
|
|
312
|
+
}
|
|
313
|
+
const dailySpend = quote(charter.dailySpend);
|
|
314
|
+
const perCallSpend = quote(charter.perCallSpend);
|
|
315
|
+
if (perCallSpend > dailySpend) {
|
|
316
|
+
throw new RipplesAgentError("`perCallSpend` cannot be above `dailySpend`.");
|
|
317
|
+
}
|
|
318
|
+
if (dailySpend > 0n && perCallSpend === 0n) {
|
|
319
|
+
throw new RipplesAgentError("A per-transaction limit of zero stops every payment the agent could make, for good.");
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
operator: charter.operator,
|
|
323
|
+
dailySpend,
|
|
324
|
+
perCallSpend,
|
|
325
|
+
dailySell: charter.dailySell ? token(charter.dailySell) : 0n,
|
|
326
|
+
dailyMint: charter.dailyMint ?? 0,
|
|
327
|
+
payees: [...payees],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
async function readActivity(client, treasury, units) {
|
|
331
|
+
const logs = await client.getLogs({
|
|
332
|
+
address: treasury,
|
|
333
|
+
events: AGENT_TREASURY_ABI.filter((entry) => entry.type === "event"),
|
|
334
|
+
fromBlock: 0n,
|
|
335
|
+
toBlock: "latest",
|
|
336
|
+
});
|
|
337
|
+
const ordered = [...logs].sort((left, right) => left.blockNumber === right.blockNumber
|
|
338
|
+
? Number((right.logIndex ?? 0n) - (left.logIndex ?? 0n))
|
|
339
|
+
: right.blockNumber > left.blockNumber
|
|
340
|
+
? 1
|
|
341
|
+
: -1);
|
|
342
|
+
const out = [];
|
|
343
|
+
for (const log of ordered) {
|
|
344
|
+
const args = log.args ?? {};
|
|
345
|
+
const name = log.eventName;
|
|
346
|
+
const base = { block: log.blockNumber, transaction: log.transactionHash };
|
|
347
|
+
const q = (value) => units.showQuote((value ?? 0n));
|
|
348
|
+
const t = (value) => units.showToken((value ?? 0n));
|
|
349
|
+
switch (name) {
|
|
350
|
+
case "Launched":
|
|
351
|
+
out.push({ kind: "launched", ...base, detail: `Opened its launch, token ${args.token}` });
|
|
352
|
+
break;
|
|
353
|
+
case "Bought":
|
|
354
|
+
out.push({ kind: "bought", ...base, detail: `Bought ${t(args.received)} for ${q(args.spent)}` });
|
|
355
|
+
break;
|
|
356
|
+
case "Sold":
|
|
357
|
+
out.push({ kind: "sold", ...base, detail: `Sold ${t(args.sold)} for ${q(args.received)}` });
|
|
358
|
+
break;
|
|
359
|
+
case "Minted":
|
|
360
|
+
out.push({ kind: "minted", ...base, detail: `Minted ${args.quantity} for ${q(args.cost)}` });
|
|
361
|
+
break;
|
|
362
|
+
case "Paid":
|
|
363
|
+
out.push({ kind: "paid", ...base, detail: `Paid ${q(args.amount)} to ${args.to}` });
|
|
364
|
+
break;
|
|
365
|
+
case "FeesClaimed":
|
|
366
|
+
out.push({
|
|
367
|
+
kind: "claimed",
|
|
368
|
+
...base,
|
|
369
|
+
detail: `Took in ${q(args.quoteAmount)} and ${t(args.tokenAmount)} of income`,
|
|
370
|
+
});
|
|
371
|
+
break;
|
|
372
|
+
case "VestedClaimed":
|
|
373
|
+
out.push({ kind: "vested", ...base, detail: `Took in ${t(args.amount)} it had vested` });
|
|
374
|
+
break;
|
|
375
|
+
case "Noted":
|
|
376
|
+
out.push({ kind: "noted", ...base, detail: String(args.uri || "Wrote to its run log") });
|
|
377
|
+
break;
|
|
378
|
+
default:
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return out.slice(0, units.limit);
|
|
383
|
+
}
|
package/dist/chains.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Address, Chain } from "viem";
|
|
2
|
+
/** The networks an agent launch exists on. Robinhood Chain only; Solana takes a program upgrade. */
|
|
3
|
+
export type RipplesNetwork = "robinhood" | "robinhood-testnet";
|
|
4
|
+
export type RipplesDeployment = {
|
|
5
|
+
readonly chainId: number;
|
|
6
|
+
readonly rpcUrl: string;
|
|
7
|
+
readonly explorer: string;
|
|
8
|
+
/** Deploys treasuries and vouches for them. Every agent call starts here. */
|
|
9
|
+
readonly agentTreasuryFactory: Address;
|
|
10
|
+
readonly tokenLaunchFactory: Address;
|
|
11
|
+
/** What a treasury holds and meters, and what a launch settles in. */
|
|
12
|
+
readonly quote: Address;
|
|
13
|
+
readonly quoteSymbol: string;
|
|
14
|
+
readonly quoteDecimals: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const DEPLOYMENTS: Readonly<Record<RipplesNetwork, RipplesDeployment>>;
|
|
17
|
+
export declare function chainOf(network: RipplesNetwork): Chain;
|
package/dist/chains.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const DEPLOYMENTS = {
|
|
2
|
+
robinhood: {
|
|
3
|
+
chainId: 4663,
|
|
4
|
+
rpcUrl: "https://rpc.mainnet.chain.robinhood.com",
|
|
5
|
+
explorer: "https://robinhoodchain.blockscout.com",
|
|
6
|
+
agentTreasuryFactory: "0xE284D60A8FCF55C72952454331A32ace0aBB5372",
|
|
7
|
+
tokenLaunchFactory: "0x149eB358fF19056c0952577fb248C7f7c861eCaF",
|
|
8
|
+
quote: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
|
|
9
|
+
quoteSymbol: "WETH",
|
|
10
|
+
quoteDecimals: 18,
|
|
11
|
+
},
|
|
12
|
+
"robinhood-testnet": {
|
|
13
|
+
chainId: 46630,
|
|
14
|
+
rpcUrl: "https://rpc.testnet.chain.robinhood.com",
|
|
15
|
+
explorer: "https://robinhoodchain-testnet.blockscout.com",
|
|
16
|
+
agentTreasuryFactory: "0xDA9a2E90A30056898C57Eba987B58AE80347AEBA",
|
|
17
|
+
tokenLaunchFactory: "0x0D39b557D154F2DD0D4231979FDbCFa5d667Ca76",
|
|
18
|
+
quote: "0x1F852eF3Afa13156b807204d838BbB46B4b452eb",
|
|
19
|
+
quoteSymbol: "WETH",
|
|
20
|
+
quoteDecimals: 18,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export function chainOf(network) {
|
|
24
|
+
const record = DEPLOYMENTS[network];
|
|
25
|
+
return {
|
|
26
|
+
id: record.chainId,
|
|
27
|
+
name: network === "robinhood" ? "Robinhood Chain" : "Robinhood Chain testnet",
|
|
28
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
29
|
+
rpcUrls: { default: { http: [record.rpcUrl] } },
|
|
30
|
+
blockExplorers: { default: { name: "Blockscout", url: record.explorer } },
|
|
31
|
+
testnet: network !== "robinhood",
|
|
32
|
+
};
|
|
33
|
+
}
|