@peddles/sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE_PROMPT.md +159 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/abis.d.ts +19 -0
- package/dist/abis.d.ts.map +1 -0
- package/dist/abis.generated.d.ts +2029 -0
- package/dist/abis.generated.d.ts.map +1 -0
- package/dist/abis.generated.js +2723 -0
- package/dist/abis.generated.js.map +1 -0
- package/dist/abis.js +19 -0
- package/dist/abis.js.map +1 -0
- package/dist/artDex.d.ts +937 -0
- package/dist/artDex.d.ts.map +1 -0
- package/dist/artDex.js +155 -0
- package/dist/artDex.js.map +1 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/clog.d.ts +75 -0
- package/dist/clog.d.ts.map +1 -0
- package/dist/clog.js +91 -0
- package/dist/clog.js.map +1 -0
- package/dist/clogInflow.d.ts +136 -0
- package/dist/clogInflow.d.ts.map +1 -0
- package/dist/clogInflow.js +177 -0
- package/dist/clogInflow.js.map +1 -0
- package/dist/deployments.d.ts +30 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.generated.d.ts +92 -0
- package/dist/deployments.generated.d.ts.map +1 -0
- package/dist/deployments.generated.js +93 -0
- package/dist/deployments.generated.js.map +1 -0
- package/dist/deployments.js +52 -0
- package/dist/deployments.js.map +1 -0
- package/dist/feeSplit.d.ts +54 -0
- package/dist/feeSplit.d.ts.map +1 -0
- package/dist/feeSplit.js +69 -0
- package/dist/feeSplit.js.map +1 -0
- package/dist/feeTerms.d.ts +46 -0
- package/dist/feeTerms.d.ts.map +1 -0
- package/dist/feeTerms.js +80 -0
- package/dist/feeTerms.js.map +1 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/launch/abi.generated.d.ts +2667 -0
- package/dist/launch/abi.generated.d.ts.map +1 -0
- package/dist/launch/abi.generated.js +2472 -0
- package/dist/launch/abi.generated.js.map +1 -0
- package/dist/launch/addresses.d.ts +24 -0
- package/dist/launch/addresses.d.ts.map +1 -0
- package/dist/launch/addresses.js +35 -0
- package/dist/launch/addresses.js.map +1 -0
- package/dist/launch/feeTerms.d.ts +52 -0
- package/dist/launch/feeTerms.d.ts.map +1 -0
- package/dist/launch/feeTerms.js +72 -0
- package/dist/launch/feeTerms.js.map +1 -0
- package/dist/launch/index.d.ts +33 -0
- package/dist/launch/index.d.ts.map +1 -0
- package/dist/launch/index.js +24 -0
- package/dist/launch/index.js.map +1 -0
- package/dist/launch/revert.d.ts +23 -0
- package/dist/launch/revert.d.ts.map +1 -0
- package/dist/launch/revert.js +169 -0
- package/dist/launch/revert.js.map +1 -0
- package/dist/launch/salt.d.ts +90 -0
- package/dist/launch/salt.d.ts.map +1 -0
- package/dist/launch/salt.js +153 -0
- package/dist/launch/salt.js.map +1 -0
- package/dist/launch/stockCall.d.ts +122 -0
- package/dist/launch/stockCall.d.ts.map +1 -0
- package/dist/launch/stockCall.js +144 -0
- package/dist/launch/stockCall.js.map +1 -0
- package/dist/launch/tickMath.d.ts +39 -0
- package/dist/launch/tickMath.d.ts.map +1 -0
- package/dist/launch/tickMath.js +109 -0
- package/dist/launch/tickMath.js.map +1 -0
- package/dist/launch/variants.d.ts +64 -0
- package/dist/launch/variants.d.ts.map +1 -0
- package/dist/launch/variants.js +131 -0
- package/dist/launch/variants.js.map +1 -0
- package/dist/launch/wethCall.d.ts +133 -0
- package/dist/launch/wethCall.d.ts.map +1 -0
- package/dist/launch/wethCall.js +173 -0
- package/dist/launch/wethCall.js.map +1 -0
- package/dist/launch/wethPlan.d.ts +71 -0
- package/dist/launch/wethPlan.d.ts.map +1 -0
- package/dist/launch/wethPlan.js +146 -0
- package/dist/launch/wethPlan.js.map +1 -0
- package/dist/perps/abi.generated.d.ts +794 -0
- package/dist/perps/abi.generated.d.ts.map +1 -0
- package/dist/perps/abi.generated.js +738 -0
- package/dist/perps/abi.generated.js.map +1 -0
- package/dist/perps/index.d.ts +160 -0
- package/dist/perps/index.d.ts.map +1 -0
- package/dist/perps/index.js +147 -0
- package/dist/perps/index.js.map +1 -0
- package/package.json +105 -0
- package/src/abis.generated.ts +2738 -0
- package/src/abis.ts +35 -0
- package/src/artDex.ts +254 -0
- package/src/client.ts +10 -0
- package/src/clog.ts +171 -0
- package/src/clogInflow.ts +217 -0
- package/src/deployments.generated.ts +98 -0
- package/src/deployments.ts +60 -0
- package/src/feeSplit.ts +76 -0
- package/src/feeTerms.ts +137 -0
- package/src/index.ts +233 -0
- package/src/launch/abi.generated.ts +2481 -0
- package/src/launch/addresses.ts +58 -0
- package/src/launch/feeTerms.ts +93 -0
- package/src/launch/index.ts +93 -0
- package/src/launch/revert.ts +188 -0
- package/src/launch/salt.ts +216 -0
- package/src/launch/stockCall.ts +223 -0
- package/src/launch/tickMath.ts +139 -0
- package/src/launch/variants.ts +183 -0
- package/src/launch/wethCall.ts +240 -0
- package/src/launch/wethPlan.ts +257 -0
- package/src/perps/abi.generated.ts +741 -0
- package/src/perps/index.ts +263 -0
package/src/abis.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ABI fragments the SDK's public surface needs — and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* DELIBERATELY PARTIAL. These are narrowed to the functions this package
|
|
5
|
+
* exposes, not dumps of the full artifacts. A published ABI is API: every
|
|
6
|
+
* fragment shipped here is one this package promises to keep working, so the
|
|
7
|
+
* ones an integrator has no business calling (owner setters, one-shot bootstrap
|
|
8
|
+
* calls, indexer hooks) are absent rather than available-but-undocumented.
|
|
9
|
+
*
|
|
10
|
+
* NARROWED, NOT HAND-TYPED. Which names ship is decided in
|
|
11
|
+
* `script/generate-abis.mjs`; every fragment's shape is copied from the
|
|
12
|
+
* compiler's own output (`contracts/out`) into `abis.generated.ts`.
|
|
13
|
+
*
|
|
14
|
+
* `PeddlesCreatorFeeHook` is intentionally absent: it is not part of the
|
|
15
|
+
* fixed-terms fee model. Every launch pool's terms are read from
|
|
16
|
+
* `PeddlesFeeHook` (`feeHookAbi`).
|
|
17
|
+
*/
|
|
18
|
+
export {
|
|
19
|
+
factoryAbi,
|
|
20
|
+
clogVaultAbi,
|
|
21
|
+
clogVaultFactoryAbi,
|
|
22
|
+
erc20Abi,
|
|
23
|
+
tokenV20Abi,
|
|
24
|
+
feeHookAbi,
|
|
25
|
+
stockLaunchpadAbi,
|
|
26
|
+
holderRewardsAbi,
|
|
27
|
+
nftFactoryAbi,
|
|
28
|
+
nftCollectionAbi,
|
|
29
|
+
nftFeeDistributorFactoryAbi,
|
|
30
|
+
nftFeeDistributorAbi,
|
|
31
|
+
nftBondingGraduationOrchestratorAbi,
|
|
32
|
+
nftStockGraduationOrchestratorAbi,
|
|
33
|
+
liquidityExecutorAbi,
|
|
34
|
+
routeSwapRouterAbi,
|
|
35
|
+
} from './abis.generated.js';
|
package/src/artDex.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { ReadClient } from './client.js';
|
|
2
|
+
import type { Address, Hex } from 'viem';
|
|
3
|
+
import { encodeAbiParameters, keccak256 } from 'viem';
|
|
4
|
+
import {
|
|
5
|
+
nftBondingGraduationOrchestratorAbi,
|
|
6
|
+
nftCollectionAbi,
|
|
7
|
+
nftFactoryAbi,
|
|
8
|
+
nftFeeDistributorAbi,
|
|
9
|
+
nftFeeDistributorFactoryAbi,
|
|
10
|
+
nftStockGraduationOrchestratorAbi,
|
|
11
|
+
} from './abis.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Art->DEX: a Peddles NFT collection launches ONE coin, paired to WETH or a tokenised stock, and may
|
|
15
|
+
* commit — permanently — a share of the creator's fee streams to its NFT holders.
|
|
16
|
+
*
|
|
17
|
+
* The commitment is the collection's write-once `holderRewards` binding to its canonical
|
|
18
|
+
* `PeddlesNftFeeDistributor` (made by `PeddlesNftFeeDistributorFactory`), plus the `holderShareBps`
|
|
19
|
+
* the creator passes at launch (0..10000). Nobody can change either afterwards.
|
|
20
|
+
*
|
|
21
|
+
* Addresses are parameters here rather than looked up from `DEPLOYMENTS`: the distributor factory ships
|
|
22
|
+
* with the Art->DEX deployment, and an address book that predates it has no entry to look up.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const ZERO = /^0x0{40}$/i;
|
|
26
|
+
const BPS = 10_000;
|
|
27
|
+
|
|
28
|
+
/** Bps bound shared by every holder-share argument. Throws rather than letting the chain revert. */
|
|
29
|
+
function assertBps(name: string, v: number): void {
|
|
30
|
+
if (!Number.isInteger(v) || v < 0 || v > BPS) throw new RangeError(`${name} must be an integer 0..10000, got ${v}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ------------------------------ relayed stock launch consent ------------------------------ */
|
|
34
|
+
|
|
35
|
+
export interface RelayParams {
|
|
36
|
+
readonly chainId: number;
|
|
37
|
+
/** The PeddlesNftStockGraduationOrchestrator the consent is for. */
|
|
38
|
+
readonly orchestrator: Address;
|
|
39
|
+
readonly collection: Address;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly symbol: string;
|
|
42
|
+
readonly quote: Address;
|
|
43
|
+
readonly holderShareBps: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `relayParamsHash` computed off chain — `keccak256(abi.encode(chainid, orchestrator, collection, name,
|
|
48
|
+
* symbol, quote, holderShareBps))`, exactly as the orchestrator hashes it. The collection owner signs
|
|
49
|
+
* `setStockGraduationOptIn(collection, hash)`; a relay whose parameters hash to anything else reverts.
|
|
50
|
+
* Chain id and orchestrator are part of it, so consent never carries across chains or deployments.
|
|
51
|
+
*/
|
|
52
|
+
export function relayParamsHash(p: RelayParams): Hex {
|
|
53
|
+
assertBps('holderShareBps', p.holderShareBps);
|
|
54
|
+
return keccak256(
|
|
55
|
+
encodeAbiParameters(
|
|
56
|
+
[{ type: 'uint256' }, { type: 'address' }, { type: 'address' }, { type: 'string' }, { type: 'string' }, { type: 'address' }, { type: 'uint16' }],
|
|
57
|
+
[BigInt(p.chainId), p.orchestrator, p.collection, p.name, p.symbol, p.quote, p.holderShareBps],
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Request for `setStockGraduationOptIn(collection, relayParamsHash(p))` (collection owner only). Zero revokes. */
|
|
63
|
+
export function stockGraduationOptInRequest(p: RelayParams) {
|
|
64
|
+
return {
|
|
65
|
+
address: p.orchestrator,
|
|
66
|
+
abi: nftStockGraduationOrchestratorAbi,
|
|
67
|
+
functionName: 'setStockGraduationOptIn',
|
|
68
|
+
args: [p.collection, relayParamsHash(p)],
|
|
69
|
+
} as const;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ------------------------------ launch requests ------------------------------ */
|
|
73
|
+
|
|
74
|
+
export interface StockTermsLaunch {
|
|
75
|
+
readonly orchestrator: Address;
|
|
76
|
+
readonly collection: Address;
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly symbol: string;
|
|
79
|
+
readonly quote: Address;
|
|
80
|
+
/** All-in pool tax, bps (100..1000). Permanent. */
|
|
81
|
+
readonly creatorTaxBps: number;
|
|
82
|
+
/** Creator's share of the tax above 1.00%, bps. Permanent. */
|
|
83
|
+
readonly excessToCreatorBps: number;
|
|
84
|
+
/** Share of the creator streams paid to NFT holders, bps. 0 unless the collection is committed. Permanent. */
|
|
85
|
+
readonly holderShareBps: number;
|
|
86
|
+
/** Quote base units for the in-launch dev buy (0 = none); pulled from the caller, so approve first. */
|
|
87
|
+
readonly devBuyQuoteIn: bigint;
|
|
88
|
+
readonly minTokensOut: bigint;
|
|
89
|
+
/** The launchpad's native launch fee, forwarded as msg.value. */
|
|
90
|
+
readonly launchFee: bigint;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Request for `graduateToStockWithTerms` (collection owner only). */
|
|
94
|
+
export function graduateToStockWithTermsRequest(l: StockTermsLaunch) {
|
|
95
|
+
assertBps('holderShareBps', l.holderShareBps);
|
|
96
|
+
assertBps('excessToCreatorBps', l.excessToCreatorBps);
|
|
97
|
+
return {
|
|
98
|
+
address: l.orchestrator,
|
|
99
|
+
abi: nftStockGraduationOrchestratorAbi,
|
|
100
|
+
functionName: 'graduateToStockWithTerms',
|
|
101
|
+
args: [
|
|
102
|
+
{
|
|
103
|
+
collection: l.collection,
|
|
104
|
+
name: l.name,
|
|
105
|
+
symbol: l.symbol,
|
|
106
|
+
quote: l.quote,
|
|
107
|
+
creatorTaxBps: l.creatorTaxBps,
|
|
108
|
+
excessToCreatorBps: l.excessToCreatorBps,
|
|
109
|
+
holderShareBps: l.holderShareBps,
|
|
110
|
+
devBuyQuoteIn: l.devBuyQuoteIn,
|
|
111
|
+
minTokensOut: l.minTokensOut,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
value: l.launchFee,
|
|
115
|
+
} as const;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Request for `graduateAndBuy(input, holderShareBps, devBuyValue, minTokensOut)` — the ONLY WETH-path
|
|
120
|
+
* entry since 94d6564 (`graduate` was removed). A plain launch is `holderShareBps = 0, devBuyValue = 0,
|
|
121
|
+
* minTokensOut = 0`. `value` must cover the orchestrator's `launchFee()` (read it live —
|
|
122
|
+
* `readOrchestratorLaunchFee` in `@peddles/sdk/launch`) `+ input.factoryValue + devBuyValue`; a relay of
|
|
123
|
+
* a committed graduation carries exactly `launchFee()` (`RELAY_FEE_ONLY`).
|
|
124
|
+
*
|
|
125
|
+
* `input` is the orchestrator's `GraduateInput` tuple, built by the launch kit exactly as for any V20
|
|
126
|
+
* launch; this helper only positions the Art->DEX arguments around it.
|
|
127
|
+
*/
|
|
128
|
+
export function graduateAndBuyRequest(p: {
|
|
129
|
+
readonly orchestrator: Address;
|
|
130
|
+
readonly input: unknown;
|
|
131
|
+
readonly holderShareBps: number;
|
|
132
|
+
readonly devBuyValue: bigint;
|
|
133
|
+
readonly minTokensOut: bigint;
|
|
134
|
+
readonly value: bigint;
|
|
135
|
+
}) {
|
|
136
|
+
assertBps('holderShareBps', p.holderShareBps);
|
|
137
|
+
if (p.devBuyValue > p.value) throw new RangeError('devBuyValue exceeds value');
|
|
138
|
+
return {
|
|
139
|
+
address: p.orchestrator,
|
|
140
|
+
abi: nftBondingGraduationOrchestratorAbi,
|
|
141
|
+
functionName: 'graduateAndBuy',
|
|
142
|
+
args: [p.input, p.holderShareBps, p.devBuyValue, p.minTokensOut],
|
|
143
|
+
value: p.value,
|
|
144
|
+
} as const;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ------------------------------ reads ------------------------------ */
|
|
148
|
+
|
|
149
|
+
/** The canonical distributor address for `collection` (CREATE2; exists or not). */
|
|
150
|
+
export async function predictDistributor(client: ReadClient, distributorFactory: Address, collection: Address): Promise<Address> {
|
|
151
|
+
return (await client.readContract({ address: distributorFactory, abi: nftFeeDistributorFactoryAbi, functionName: 'predict', args: [collection] })) as Address;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface HolderCommitment {
|
|
155
|
+
/** True when the collection is bound to its canonical Peddles distributor. */
|
|
156
|
+
readonly committed: boolean;
|
|
157
|
+
/** The bound `holderRewards` (zero = none). */
|
|
158
|
+
readonly holderRewards: Address;
|
|
159
|
+
/** True when the Peddles NFT factory made this collection. */
|
|
160
|
+
readonly peddlesCollection: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Whether a collection's creator streams are committed to its holders — the same three reads the
|
|
165
|
+
* orchestrators make: `holderRewards()`, `factory.isDistributor(d)`, `d.collection() == collection`.
|
|
166
|
+
* Also reports factory provenance, which the orchestrators do NOT check.
|
|
167
|
+
*/
|
|
168
|
+
export async function getHolderCommitment(
|
|
169
|
+
client: ReadClient,
|
|
170
|
+
a: { nftFactory: Address; distributorFactory: Address; collection: Address },
|
|
171
|
+
): Promise<HolderCommitment> {
|
|
172
|
+
const [holderRewards, peddlesCollection] = await Promise.all([
|
|
173
|
+
client.readContract({ address: a.collection, abi: nftCollectionAbi, functionName: 'holderRewards' }) as Promise<Address>,
|
|
174
|
+
client.readContract({ address: a.nftFactory, abi: nftFactoryAbi, functionName: 'isPeddlesCollection', args: [a.collection] }) as Promise<boolean>,
|
|
175
|
+
]);
|
|
176
|
+
if (ZERO.test(holderRewards)) return { committed: false, holderRewards, peddlesCollection };
|
|
177
|
+
const isDistributor = (await client.readContract({
|
|
178
|
+
address: a.distributorFactory,
|
|
179
|
+
abi: nftFeeDistributorFactoryAbi,
|
|
180
|
+
functionName: 'isDistributor',
|
|
181
|
+
args: [holderRewards],
|
|
182
|
+
})) as boolean;
|
|
183
|
+
if (!isDistributor) return { committed: false, holderRewards, peddlesCollection };
|
|
184
|
+
const bound = (await client.readContract({ address: holderRewards, abi: nftFeeDistributorAbi, functionName: 'collection' })) as Address;
|
|
185
|
+
return { committed: bound.toLowerCase() === a.collection.toLowerCase(), holderRewards, peddlesCollection };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface DistributorState {
|
|
189
|
+
readonly distributor: Address;
|
|
190
|
+
readonly collection: Address;
|
|
191
|
+
/** Zero until launch. */
|
|
192
|
+
readonly quote: Address;
|
|
193
|
+
readonly token: Address;
|
|
194
|
+
readonly holderShareBps: number;
|
|
195
|
+
readonly assetsInitialized: boolean;
|
|
196
|
+
readonly creatorShareOwed: { readonly quote: bigint; readonly token: bigint };
|
|
197
|
+
readonly owedToHolders: { readonly quote: bigint; readonly token: bigint };
|
|
198
|
+
/** False while minted pieces are unmirrored — holder distribution waits until they are synced. */
|
|
199
|
+
readonly mirrorComplete: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function getDistributorState(client: ReadClient, distributor: Address): Promise<DistributorState> {
|
|
203
|
+
const base = { address: distributor, abi: nftFeeDistributorAbi } as const;
|
|
204
|
+
const [collection, quote, token, holderShareBps, assetsInitialized, creatorShareOwed, owedToHolders, mirrorComplete] = await client.multicall({
|
|
205
|
+
allowFailure: false,
|
|
206
|
+
contracts: [
|
|
207
|
+
{ ...base, functionName: 'collection' },
|
|
208
|
+
{ ...base, functionName: 'quote' },
|
|
209
|
+
{ ...base, functionName: 'token' },
|
|
210
|
+
{ ...base, functionName: 'holderShareBps' },
|
|
211
|
+
{ ...base, functionName: 'assetsInitialized' },
|
|
212
|
+
{ ...base, functionName: 'creatorShareOwed' },
|
|
213
|
+
{ ...base, functionName: 'owedToHolders' },
|
|
214
|
+
{ ...base, functionName: 'mirrorComplete' },
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
const [cq, ct] = creatorShareOwed as readonly [bigint, bigint];
|
|
218
|
+
const [hq, ht] = owedToHolders as readonly [bigint, bigint];
|
|
219
|
+
return {
|
|
220
|
+
distributor,
|
|
221
|
+
collection: collection as Address,
|
|
222
|
+
quote: quote as Address,
|
|
223
|
+
token: token as Address,
|
|
224
|
+
holderShareBps: Number(holderShareBps),
|
|
225
|
+
assetsInitialized: assetsInitialized as boolean,
|
|
226
|
+
creatorShareOwed: { quote: cq, token: ct },
|
|
227
|
+
owedToHolders: { quote: hq, token: ht },
|
|
228
|
+
mirrorComplete: mirrorComplete as boolean,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface HolderClaimable {
|
|
233
|
+
readonly quote: bigint;
|
|
234
|
+
readonly token: bigint;
|
|
235
|
+
/** Shares the distributor has mirrored for the holder. */
|
|
236
|
+
readonly shares: bigint;
|
|
237
|
+
/** Pieces the holder holds on the collection. */
|
|
238
|
+
readonly pieces: bigint;
|
|
239
|
+
/** `shares != pieces`: claims under-accrue until `syncAccount(holder)` runs. */
|
|
240
|
+
readonly needsSync: boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function holderClaimable(client: ReadClient, distributor: Address, collection: Address, holder: Address): Promise<HolderClaimable> {
|
|
244
|
+
const [pending, shares, pieces] = await client.multicall({
|
|
245
|
+
allowFailure: false,
|
|
246
|
+
contracts: [
|
|
247
|
+
{ address: distributor, abi: nftFeeDistributorAbi, functionName: 'pending', args: [holder] },
|
|
248
|
+
{ address: distributor, abi: nftFeeDistributorAbi, functionName: 'sharesOf', args: [holder] },
|
|
249
|
+
{ address: collection, abi: nftCollectionAbi, functionName: 'balanceOf', args: [holder] },
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
const [quote, token] = pending as readonly [bigint, bigint];
|
|
253
|
+
return { quote, token, shares: shares as bigint, pieces: pieces as bigint, needsSync: (shares as bigint) !== (pieces as bigint) };
|
|
254
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PublicClient } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The client every SDK read takes: any viem client that can `readContract`, `multicall` and
|
|
5
|
+
* `getBalance`. Narrower than `PublicClient` on purpose — a `PublicClient` built for a chain with its
|
|
6
|
+
* own formatters (Base and every OP-stack chain add `deposit` transactions) is NOT assignable to the
|
|
7
|
+
* generic `PublicClient`, so typing parameters that way made `createPublicClient({ chain: base })`
|
|
8
|
+
* a compile error for every Base integrator. Only the three methods the SDK calls are required.
|
|
9
|
+
*/
|
|
10
|
+
export type ReadClient = Pick<PublicClient, 'readContract' | 'multicall' | 'getBalance'>;
|
package/src/clog.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ReadClient } from './client.js';
|
|
2
|
+
import type { Address } from 'viem';
|
|
3
|
+
import { clogVaultAbi, clogVaultFactoryAbi, erc20Abi, factoryAbi } from './abis.js';
|
|
4
|
+
import { addressOf } from './deployments.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The Clog launch type, read from the chain.
|
|
8
|
+
*
|
|
9
|
+
* A clog withholds a share of a launch's supply in a `PeddlesClogVault` and
|
|
10
|
+
* sells it into that launch's own pool a capped slice at a time. The proceeds
|
|
11
|
+
* (native) STAY IN THE VAULT until the creator calls `withdrawProceeds`, and a
|
|
12
|
+
* creator-set release floor bounds the price a release may sell at. It funds marketing without the creator putting up
|
|
13
|
+
* capital — and it only ever extracts from volume that actually arrived, since
|
|
14
|
+
* a sale into an empty book returns nothing and reverts.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** A launch type's declared economics. Write-once at variant registration. */
|
|
18
|
+
export interface VariantAllocation {
|
|
19
|
+
readonly liquidityBps: number;
|
|
20
|
+
readonly airdropBps: number;
|
|
21
|
+
readonly vestingBps: number;
|
|
22
|
+
readonly burnBps: number;
|
|
23
|
+
readonly clogBps: number;
|
|
24
|
+
/** Per-release cap, in bps OF TOTAL SUPPLY — not of the allocation. */
|
|
25
|
+
readonly clogSliceBps: number;
|
|
26
|
+
readonly clogMinIntervalSeconds: bigint;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** One clog vault's live state. */
|
|
30
|
+
export interface ClogState {
|
|
31
|
+
readonly vault: Address;
|
|
32
|
+
readonly token: Address;
|
|
33
|
+
readonly creator: Address;
|
|
34
|
+
readonly router: Address;
|
|
35
|
+
/** Raw units still held, i.e. not yet sold. */
|
|
36
|
+
readonly held: bigint;
|
|
37
|
+
/** Raw units sold to date, across every release. */
|
|
38
|
+
readonly totalSold: bigint;
|
|
39
|
+
/** Max raw units one release may sell. */
|
|
40
|
+
readonly sliceCap: bigint;
|
|
41
|
+
readonly minIntervalSeconds: bigint;
|
|
42
|
+
/** What `release` would sell right now. */
|
|
43
|
+
readonly nextSlice: bigint;
|
|
44
|
+
/** Unix seconds at which `release` stops reverting `TooSoon`. */
|
|
45
|
+
readonly readyAt: bigint;
|
|
46
|
+
/** Native proceeds held by the vault, owed to the creator (`withdrawProceeds`). */
|
|
47
|
+
readonly proceedsOwed: bigint;
|
|
48
|
+
/** The creator's minimum release price, 1e18-scaled quote per token; 0 = none. */
|
|
49
|
+
readonly releaseFloorX18: bigint;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read a launch type's allocation.
|
|
54
|
+
*
|
|
55
|
+
* `registerVariant` is write-once, so this is the deal a creator and every
|
|
56
|
+
* buyer of their token accepted — it cannot be changed under them afterwards.
|
|
57
|
+
* Read it rather than assuming: the shares differ per launch type, and a clog
|
|
58
|
+
* type is not 100% liquidity.
|
|
59
|
+
*/
|
|
60
|
+
export async function getVariantAllocation(
|
|
61
|
+
client: ReadClient,
|
|
62
|
+
chainId: number,
|
|
63
|
+
variant: number,
|
|
64
|
+
): Promise<VariantAllocation> {
|
|
65
|
+
const a = (await client.readContract({
|
|
66
|
+
address: addressOf(chainId, 'PeddlesFactoryV20'),
|
|
67
|
+
abi: factoryAbi,
|
|
68
|
+
functionName: 'variantAllocation',
|
|
69
|
+
args: [variant],
|
|
70
|
+
})) as {
|
|
71
|
+
liquidityBps: number;
|
|
72
|
+
airdropBps: number;
|
|
73
|
+
vestingBps: number;
|
|
74
|
+
burnBps: number;
|
|
75
|
+
clogBps: number;
|
|
76
|
+
clogSliceBps: number;
|
|
77
|
+
clogMinInterval: bigint;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
liquidityBps: a.liquidityBps,
|
|
82
|
+
airdropBps: a.airdropBps,
|
|
83
|
+
vestingBps: a.vestingBps,
|
|
84
|
+
burnBps: a.burnBps,
|
|
85
|
+
clogBps: a.clogBps,
|
|
86
|
+
clogSliceBps: a.clogSliceBps,
|
|
87
|
+
clogMinIntervalSeconds: a.clogMinInterval,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The clog vault a launch's orchestrator created for `token`, or null. */
|
|
92
|
+
export async function getClogVault(
|
|
93
|
+
client: ReadClient,
|
|
94
|
+
chainId: number,
|
|
95
|
+
token: Address,
|
|
96
|
+
): Promise<Address | null> {
|
|
97
|
+
const vault = (await client.readContract({
|
|
98
|
+
address: addressOf(chainId, 'PeddlesClogVaultFactory'),
|
|
99
|
+
abi: clogVaultFactoryAbi,
|
|
100
|
+
functionName: 'vaultOf',
|
|
101
|
+
args: [addressOf(chainId, 'PeddlesLaunchOrchestratorV20'), token],
|
|
102
|
+
})) as Address;
|
|
103
|
+
return /^0x0{40}$/i.test(vault) ? null : vault;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Everything about one clog vault, in a single multicall. */
|
|
107
|
+
export async function getClogState(client: ReadClient, vault: Address): Promise<ClogState> {
|
|
108
|
+
const base = { address: vault, abi: clogVaultAbi } as const;
|
|
109
|
+
const [token, creator, router, sliceCap, minInterval, totalSold, preview, releaseFloorX18] = await client.multicall({
|
|
110
|
+
allowFailure: false,
|
|
111
|
+
contracts: [
|
|
112
|
+
{ ...base, functionName: 'token' },
|
|
113
|
+
{ ...base, functionName: 'creator' },
|
|
114
|
+
{ ...base, functionName: 'router' },
|
|
115
|
+
{ ...base, functionName: 'sliceCap' },
|
|
116
|
+
{ ...base, functionName: 'minInterval' },
|
|
117
|
+
{ ...base, functionName: 'totalSold' },
|
|
118
|
+
{ ...base, functionName: 'previewRelease' },
|
|
119
|
+
{ ...base, functionName: 'releaseFloorX18' },
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const [held, proceedsOwed] = await Promise.all([
|
|
124
|
+
client.readContract({ address: token as Address, abi: erc20Abi, functionName: 'balanceOf', args: [vault] }) as Promise<bigint>,
|
|
125
|
+
client.getBalance({ address: vault }),
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const [nextSlice, readyAt] = preview as readonly [bigint, bigint];
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
vault,
|
|
132
|
+
token: token as Address,
|
|
133
|
+
creator: creator as Address,
|
|
134
|
+
router: router as Address,
|
|
135
|
+
held,
|
|
136
|
+
totalSold: totalSold as bigint,
|
|
137
|
+
sliceCap: sliceCap as bigint,
|
|
138
|
+
minIntervalSeconds: minInterval as bigint,
|
|
139
|
+
nextSlice,
|
|
140
|
+
readyAt,
|
|
141
|
+
proceedsOwed,
|
|
142
|
+
releaseFloorX18: releaseFloorX18 as bigint,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* What share of INFLOWS a clog routes to the creator, given its share of supply.
|
|
148
|
+
*
|
|
149
|
+
* THESE ARE NOT THE SAME NUMBER, and the second is the one that matters to a
|
|
150
|
+
* buyer. The allocation is a share of supply, but it is sold into the pool the
|
|
151
|
+
* buyer is buying from, so it captures a much larger share of the money coming
|
|
152
|
+
* in. Surface it wherever the allocation is shown: a launch page that says "5%"
|
|
153
|
+
* and stops has told the buyer the smaller of two true numbers.
|
|
154
|
+
*
|
|
155
|
+
* The model (exact, bigint, documented in `clogInflow.ts`) lives in its own
|
|
156
|
+
* import-free module and is re-exported here.
|
|
157
|
+
*/
|
|
158
|
+
export {
|
|
159
|
+
CLOG_MODEL_FLOW_MULTIPLE,
|
|
160
|
+
CLOG_TABLE_SLICE_BPS,
|
|
161
|
+
clogShareOfInflows,
|
|
162
|
+
clogShareOfInflowsFromBps,
|
|
163
|
+
clogInflowShareBps,
|
|
164
|
+
inflowShareBps,
|
|
165
|
+
} from './clogInflow.js';
|
|
166
|
+
export type { ShareOfInflows, ClogModelInput } from './clogInflow.js';
|
|
167
|
+
|
|
168
|
+
/** True when `release` would not revert `TooSoon`. */
|
|
169
|
+
export function isReleaseReady(state: ClogState, nowSeconds: bigint): boolean {
|
|
170
|
+
return nowSeconds >= state.readyAt;
|
|
171
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What share of the money buyers put in a clog routes to the creator.
|
|
3
|
+
*
|
|
4
|
+
* A clog allocation is a share of SUPPLY, but it is sold into the pool buyers are
|
|
5
|
+
* buying from, so it captures a far larger share of the money coming in. That
|
|
6
|
+
* second number is the one a buyer is accepting, and it must be stated wherever
|
|
7
|
+
* the allocation is. This module derives it; nothing here is a lookup table.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no imports, bigint only, no floating point at any step. Every step is
|
|
10
|
+
* exact rational arithmetic, so the result is a fraction `num / den`; the only
|
|
11
|
+
* rounding is in `clogInflowShareBps`, once, at the end.
|
|
12
|
+
*
|
|
13
|
+
* SINGLE SOURCE OF TRUTH. `apps/web/src/features/launch/clogInflow.ts` carries a
|
|
14
|
+
* copy (the web app does not depend on this package). `__tests__/clogInflow.test.ts`
|
|
15
|
+
* imports that copy and holds the two to identical fractions over a grid, so a
|
|
16
|
+
* change to one without the other fails the SDK's tests.
|
|
17
|
+
*
|
|
18
|
+
* ── THE MODEL ───────────────────────────────────────────────────────────────
|
|
19
|
+
* Pure buy flow — nobody but the clog ever sells. That is the most favourable
|
|
20
|
+
* case for the design: any real selling makes the clog sell into a weaker pool.
|
|
21
|
+
*
|
|
22
|
+
* The pool. A Peddles launch opens a single-sided position from the launch
|
|
23
|
+
* price upward holding the liquidity share `x0 = supply - clog`. For such a
|
|
24
|
+
* position, token reserve `x` and quote `y` satisfy
|
|
25
|
+
*
|
|
26
|
+
* x · (y + x0·p0) = x0² · p0 (a constant product, k)
|
|
27
|
+
*
|
|
28
|
+
* i.e. constant product against a virtual quote reserve `x0·p0`. Write
|
|
29
|
+
* `q = y + x0·p0`, so `q0 = x0·p0` and `k = x0·q0`.
|
|
30
|
+
*
|
|
31
|
+
* One round. Buyers spend `B` (a buy never changes k): q ← q + B
|
|
32
|
+
* then the clog sells `s = min(slice, remaining)` tokens:
|
|
33
|
+
*
|
|
34
|
+
* q ← k · q / (k + s · q) (x ← x + s, q = k / x)
|
|
35
|
+
*
|
|
36
|
+
* Rounds repeat until the clog is empty. After `n` rounds buyers have put in
|
|
37
|
+
* `n·B`, and the pool holds `q_n − q0` of it; everything else was paid out to
|
|
38
|
+
* the creator. So, exactly:
|
|
39
|
+
*
|
|
40
|
+
* share of inflows = 1 − (q_n − q0) / (n · B)
|
|
41
|
+
*
|
|
42
|
+
* Buy flow per round — THE ONE ASSUMPTION. `B = flowMultiple × slice × p0`:
|
|
43
|
+
* buyers spend between releases `flowMultiple` times what a slice was worth at
|
|
44
|
+
* the launch price. Default `CLOG_MODEL_FLOW_MULTIPLE` (100).
|
|
45
|
+
*
|
|
46
|
+
* p0 cancels. Measuring quote in units of `p0` makes every term an integer
|
|
47
|
+
* count of tokens (`q0 = x0`, `k = x0²`, `B = flowMultiple × slice`), so the
|
|
48
|
+
* result depends only on (supply, clog, slice) and the flow multiple — never
|
|
49
|
+
* on the launch price, which is why it can be stated before the pool exists.
|
|
50
|
+
*
|
|
51
|
+
* The floor. A single-sided position holds no quote below its launch price, so
|
|
52
|
+
* a release can never take the price under `p0`. If a round's sale would, the
|
|
53
|
+
* model does not apply and the functions return null rather than a number from
|
|
54
|
+
* a pool that cannot exist. At flowMultiple = 100 this cannot happen anywhere
|
|
55
|
+
* in the range the factory accepts (clog ≤ `PeddlesFactoryV20.CLOG_BPS_MAX`,
|
|
56
|
+
* 25%): a round starts at `q ≥ q0`, its buys take at least `x0·B / (x0 + B)`
|
|
57
|
+
* tokens out of the pool, and a sale `s ≤ clog` fits back under the floor
|
|
58
|
+
* whenever `99·x0 ≥ 100·s` — true for every `x0 ≥ 75%` and `s ≤ 25%`.
|
|
59
|
+
*
|
|
60
|
+
* ── REFERENCE ───────────────────────────────────────────────────────────────
|
|
61
|
+
* With slices of 1% of supply (`CLOG_TABLE_SLICE_BPS`) and flowMultiple = 100
|
|
62
|
+
* this reproduces the policy table in CLAUDE.md at its 0.1% resolution
|
|
63
|
+
* (5% → 16.5%, 10% → 37.1%, 15% → 54.6%, 25% → 75.0%). A launch type's real
|
|
64
|
+
* slice is smaller — the live Sepolia Clog type sells 5% in 0.25% slices, which
|
|
65
|
+
* is ≈14.2% — so pass the slice the chain reports, never the table's.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Buyer spend between two clog releases, as a multiple of what one slice was
|
|
70
|
+
* worth at the launch price. The one assumption in the model; see the header.
|
|
71
|
+
*/
|
|
72
|
+
export const CLOG_MODEL_FLOW_MULTIPLE = 100n;
|
|
73
|
+
|
|
74
|
+
/** The slice (1% of supply) at which the model reproduces the CLAUDE.md policy table. */
|
|
75
|
+
export const CLOG_TABLE_SLICE_BPS = 100n;
|
|
76
|
+
|
|
77
|
+
/** More rounds than any registrable launch type can need (25% in 1-unit slices of 10,000). */
|
|
78
|
+
const MAX_ROUNDS = 100_000n;
|
|
79
|
+
|
|
80
|
+
const BPS = 10_000n;
|
|
81
|
+
|
|
82
|
+
export interface ShareOfInflows {
|
|
83
|
+
/**
|
|
84
|
+
* Share of all buyer money paid out to the creator, as `num / den` (0 ≤ share < 1).
|
|
85
|
+
* Not reduced to lowest terms — compare two shares by cross-multiplying.
|
|
86
|
+
*/
|
|
87
|
+
readonly num: bigint;
|
|
88
|
+
readonly den: bigint;
|
|
89
|
+
/** How many releases it takes to sell the whole clog. */
|
|
90
|
+
readonly releases: bigint;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ClogModelInput {
|
|
94
|
+
/** Total supply, in any unit — basis points (10000) or raw token units. */
|
|
95
|
+
readonly supply: bigint;
|
|
96
|
+
/** The clog allocation, in the same unit. */
|
|
97
|
+
readonly clog: bigint;
|
|
98
|
+
/** The most one release sells, in the same unit. */
|
|
99
|
+
readonly slice: bigint;
|
|
100
|
+
/** Defaults to `CLOG_MODEL_FLOW_MULTIPLE`. */
|
|
101
|
+
readonly flowMultiple?: bigint;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The share of buyer money a clog routes to the creator, as an exact fraction,
|
|
106
|
+
* or null when the model does not apply (a malformed input, or a release that
|
|
107
|
+
* would breach the floor). A zero clog captures nothing:
|
|
108
|
+
* `{ num: 0n, den: 1n, releases: 0n }`.
|
|
109
|
+
*/
|
|
110
|
+
export function clogShareOfInflows(input: ClogModelInput): ShareOfInflows | null {
|
|
111
|
+
const { supply, clog, slice } = input;
|
|
112
|
+
const flowMultiple = input.flowMultiple ?? CLOG_MODEL_FLOW_MULTIPLE;
|
|
113
|
+
|
|
114
|
+
if (supply <= 0n || clog < 0n || clog >= supply) return null;
|
|
115
|
+
if (clog === 0n) return { num: 0n, den: 1n, releases: 0n };
|
|
116
|
+
if (slice <= 0n || flowMultiple <= 0n) return null;
|
|
117
|
+
|
|
118
|
+
const x0 = supply - clog;
|
|
119
|
+
const k = x0 * x0; // q0 = x0 in units of p0
|
|
120
|
+
const buy = flowMultiple * slice;
|
|
121
|
+
|
|
122
|
+
// q = qn / qd, kept as an unreduced exact fraction.
|
|
123
|
+
let qn = x0;
|
|
124
|
+
let qd = 1n;
|
|
125
|
+
let remaining = clog;
|
|
126
|
+
let rounds = 0n;
|
|
127
|
+
|
|
128
|
+
while (remaining > 0n) {
|
|
129
|
+
if (rounds >= MAX_ROUNDS) return null;
|
|
130
|
+
rounds += 1n;
|
|
131
|
+
|
|
132
|
+
// Buyers spend: q ← q + B.
|
|
133
|
+
qn += buy * qd;
|
|
134
|
+
|
|
135
|
+
// The clog sells s tokens: q ← k·q / (k + s·q).
|
|
136
|
+
const sold = remaining < slice ? remaining : slice;
|
|
137
|
+
const nextN = k * qn;
|
|
138
|
+
const nextD = k * qd + sold * qn;
|
|
139
|
+
|
|
140
|
+
// Floor: q must not fall below q0 (= x0), i.e. nextN / nextD ≥ x0.
|
|
141
|
+
if (nextN < x0 * nextD) return null;
|
|
142
|
+
|
|
143
|
+
// Left unreduced. The fraction almost never reduces, and a gcd on numbers
|
|
144
|
+
// that grow a few dozen bits per round is what makes a 2,500-round type slow.
|
|
145
|
+
qn = nextN;
|
|
146
|
+
qd = nextD;
|
|
147
|
+
remaining -= sold;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// share = 1 − (q − q0) / (n·B) = (n·B·qd − (qn − x0·qd)) / (n·B·qd)
|
|
151
|
+
const inflow = rounds * buy;
|
|
152
|
+
const den = inflow * qd;
|
|
153
|
+
const num = den - (qn - x0 * qd);
|
|
154
|
+
return { num, den, releases: rounds };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The same model, stated in basis points of supply — the unit a launch type declares. */
|
|
158
|
+
export function clogShareOfInflowsFromBps(
|
|
159
|
+
clogBps: bigint,
|
|
160
|
+
sliceBps: bigint,
|
|
161
|
+
flowMultiple: bigint = CLOG_MODEL_FLOW_MULTIPLE,
|
|
162
|
+
): ShareOfInflows | null {
|
|
163
|
+
return clogShareOfInflows({ supply: BPS, clog: clogBps, slice: sliceBps, flowMultiple });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** `num / den` scaled by `unit`, rounded half up. `num ≥ 0`, `den > 0`. */
|
|
167
|
+
function roundHalfUp(num: bigint, den: bigint, unit: bigint): bigint {
|
|
168
|
+
return (num * unit * 2n + den) / (den * 2n);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Share of inflows, in basis points, for a clog of `clogBps` of supply sold in
|
|
173
|
+
* slices of `sliceBps` of supply.
|
|
174
|
+
*
|
|
175
|
+
* Pass the launch type's own `clogSliceBps` (from `getVariantAllocation`), not
|
|
176
|
+
* the policy table's 1%: the slice moves the answer (5% in 1% slices is 1649 bps;
|
|
177
|
+
* in the live 0.25% slices it is 1417 bps).
|
|
178
|
+
*
|
|
179
|
+
* @param clogBps Clog allocation, bps of total supply.
|
|
180
|
+
* @param sliceBps Per-release cap, bps of total supply.
|
|
181
|
+
* @param flowMultiple Buyer spend between releases, in slices at the launch
|
|
182
|
+
* price. The model's one assumption; defaults to
|
|
183
|
+
* `CLOG_MODEL_FLOW_MULTIPLE` (100).
|
|
184
|
+
* @returns Nearest basis point, rounded half up — the only rounding in the model
|
|
185
|
+
* (use `clogShareOfInflows` for the exact fraction) — or null when the
|
|
186
|
+
* model does not apply.
|
|
187
|
+
*/
|
|
188
|
+
export function clogInflowShareBps(
|
|
189
|
+
clogBps: bigint,
|
|
190
|
+
sliceBps: bigint,
|
|
191
|
+
flowMultiple: bigint = CLOG_MODEL_FLOW_MULTIPLE,
|
|
192
|
+
): bigint | null {
|
|
193
|
+
const share = clogShareOfInflowsFromBps(clogBps, sliceBps, flowMultiple);
|
|
194
|
+
return share === null ? null : roundHalfUp(share.num, share.den, BPS);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* What share of INFLOWS a clog routes to the creator, given its share of supply —
|
|
199
|
+
* AT THE POLICY TABLE'S 1% SLICE AND 0.1% RESOLUTION.
|
|
200
|
+
*
|
|
201
|
+
* @deprecated Kept for existing callers. It answers for the CLAUDE.md table's
|
|
202
|
+
* assumed slice, not for any real launch type's, so it overstates the live Sepolia
|
|
203
|
+
* Clog type (5%: 1650 here vs 1417 at its real 0.25% slice). Use
|
|
204
|
+
* `clogInflowShareBps(clogBps, allocation.clogSliceBps)`.
|
|
205
|
+
*
|
|
206
|
+
* Output is unchanged for the four table rows (500 → 1650, 1000 → 3710,
|
|
207
|
+
* 1500 → 5460, 2500 → 7500). It is now derived from the model rather than looked
|
|
208
|
+
* up, so any integer clog in [0, 10000) returns a value (a multiple of 10) where
|
|
209
|
+
* it used to return null; non-integer or out-of-range input still returns null.
|
|
210
|
+
*/
|
|
211
|
+
export function inflowShareBps(clogBps: number): number | null {
|
|
212
|
+
if (!Number.isSafeInteger(clogBps)) return null;
|
|
213
|
+
const share = clogShareOfInflowsFromBps(BigInt(clogBps), CLOG_TABLE_SLICE_BPS);
|
|
214
|
+
if (share === null) return null;
|
|
215
|
+
// Table resolution is 0.1% = 10 bps: round once at that resolution, then restate in bps.
|
|
216
|
+
return Number(roundHalfUp(share.num, share.den, BPS / 10n) * 10n);
|
|
217
|
+
}
|