@interest-protocol/blast-core 0.1.0 → 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/dist/aborts.d.ts +13 -0
- package/dist/aborts.d.ts.map +1 -0
- package/dist/aborts.js +32 -0
- package/dist/aborts.js.map +1 -0
- package/dist/bcs.d.ts +226 -7
- package/dist/bcs.d.ts.map +1 -1
- package/dist/bcs.js +227 -1
- package/dist/bcs.js.map +1 -1
- package/dist/bps.d.ts +25 -0
- package/dist/bps.d.ts.map +1 -0
- package/dist/bps.js +64 -0
- package/dist/bps.js.map +1 -0
- package/dist/bytecode-template.d.ts +14 -3
- package/dist/bytecode-template.d.ts.map +1 -1
- package/dist/bytecode-template.js +61 -6
- package/dist/bytecode-template.js.map +1 -1
- package/dist/deployment.d.ts +59 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +172 -0
- package/dist/deployment.js.map +1 -0
- package/dist/effects.d.ts +69 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +143 -0
- package/dist/effects.js.map +1 -0
- package/dist/fee-split.d.ts +55 -0
- package/dist/fee-split.d.ts.map +1 -0
- package/dist/fee-split.js +69 -0
- package/dist/fee-split.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/integers.d.ts +2 -0
- package/dist/integers.d.ts.map +1 -1
- package/dist/integers.js +8 -0
- package/dist/integers.js.map +1 -1
- package/dist/merkle.d.ts +6 -1
- package/dist/merkle.d.ts.map +1 -1
- package/dist/merkle.js +8 -1
- package/dist/merkle.js.map +1 -1
- package/dist/migration-math.d.ts +13 -2
- package/dist/migration-math.d.ts.map +1 -1
- package/dist/migration-math.js +34 -6
- package/dist/migration-math.js.map +1 -1
- package/dist/migration.d.ts +25 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +43 -0
- package/dist/migration.js.map +1 -0
- package/dist/payout.d.ts +21 -0
- package/dist/payout.d.ts.map +1 -0
- package/dist/payout.js +36 -0
- package/dist/payout.js.map +1 -0
- package/dist/protocol-fees.d.ts +63 -0
- package/dist/protocol-fees.d.ts.map +1 -0
- package/dist/protocol-fees.js +117 -0
- package/dist/protocol-fees.js.map +1 -0
- package/dist/rewards/allocations.d.ts +16 -17
- package/dist/rewards/allocations.d.ts.map +1 -1
- package/dist/rewards/allocations.js +47 -43
- package/dist/rewards/allocations.js.map +1 -1
- package/dist/roles.d.ts +17 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +32 -0
- package/dist/roles.js.map +1 -0
- package/dist/treasury.d.ts +58 -0
- package/dist/treasury.d.ts.map +1 -0
- package/dist/treasury.js +61 -0
- package/dist/treasury.js.map +1 -0
- package/package.json +39 -11
- package/src/aborts.ts +38 -0
- package/src/bcs.ts +374 -1
- package/src/bps.ts +92 -0
- package/src/bytecode-template.ts +78 -6
- package/src/deployment.ts +230 -0
- package/src/effects.ts +247 -0
- package/src/fee-split.ts +103 -0
- package/src/index.ts +10 -1
- package/src/integers.ts +15 -0
- package/src/merkle.ts +11 -1
- package/src/migration-math.ts +38 -6
- package/src/migration.ts +69 -0
- package/src/payout.ts +57 -0
- package/src/protocol-fees.ts +174 -0
- package/src/rewards/allocations.ts +52 -67
- package/src/roles.ts +39 -0
- package/src/treasury.ts +94 -0
- package/dist/rewards/keeper.d.ts +0 -17
- package/dist/rewards/keeper.d.ts.map +0 -1
- package/dist/rewards/keeper.js +0 -21
- package/dist/rewards/keeper.js.map +0 -1
- package/dist/rewards/state.d.ts +0 -55
- package/dist/rewards/state.d.ts.map +0 -1
- package/dist/rewards/state.js +0 -80
- package/dist/rewards/state.js.map +0 -1
- package/dist/timelock.d.ts +0 -15
- package/dist/timelock.d.ts.map +0 -1
- package/dist/timelock.js +0 -30
- package/dist/timelock.js.map +0 -1
- package/src/rewards/keeper.ts +0 -45
- package/src/rewards/state.ts +0 -113
- package/src/timelock.ts +0 -47
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Transaction,
|
|
3
|
+
TransactionObjectInput,
|
|
4
|
+
TransactionResult,
|
|
5
|
+
} from "@mysten/sui/transactions";
|
|
6
|
+
import { normalizeStructTag, normalizeSuiObjectId } from "@mysten/sui/utils";
|
|
7
|
+
import { MAX_VESTING_DURATION_MS, requireBps } from "./bps.ts";
|
|
8
|
+
import type { Destination, FeeSplit } from "./fee-split.ts";
|
|
9
|
+
import { normalizeFeeSplit } from "./fee-split.ts";
|
|
10
|
+
import { requireU64 } from "./integers.ts";
|
|
11
|
+
import type { VestingTerms } from "./payout.ts";
|
|
12
|
+
import type { SharedObjectIdentity } from "./sui.ts";
|
|
13
|
+
import { normalizeSharedObjectIdentity } from "./sui.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `blast_core` package and its shared `blast_treasury::Treasury`. Calls
|
|
17
|
+
* target `packageId`; types and roles name `originalPackageId`.
|
|
18
|
+
*/
|
|
19
|
+
export type CoreDeployment = {
|
|
20
|
+
packageId: string;
|
|
21
|
+
originalPackageId: string;
|
|
22
|
+
treasury: SharedObjectIdentity;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function normalizeCoreDeployment(
|
|
26
|
+
deployment: CoreDeployment,
|
|
27
|
+
): CoreDeployment {
|
|
28
|
+
return {
|
|
29
|
+
packageId: normalizeSuiObjectId(deployment.packageId),
|
|
30
|
+
originalPackageId: normalizeSuiObjectId(deployment.originalPackageId),
|
|
31
|
+
treasury: normalizeSharedObjectIdentity(
|
|
32
|
+
"core.treasury",
|
|
33
|
+
deployment.treasury,
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `<core original>::<module>::<name>`, normalized. */
|
|
39
|
+
export function coreType(
|
|
40
|
+
core: Pick<CoreDeployment, "originalPackageId">,
|
|
41
|
+
module: string,
|
|
42
|
+
name: string,
|
|
43
|
+
): string {
|
|
44
|
+
return normalizeStructTag(
|
|
45
|
+
`${normalizeSuiObjectId(core.originalPackageId)}::${module}::${name}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `blast_core` value constructors and treasury calls. Each method returns a
|
|
51
|
+
* PTB step; the constructors return the value as a transaction result that
|
|
52
|
+
* the product or vault call consuming it takes by value.
|
|
53
|
+
*/
|
|
54
|
+
export class CoreCalls {
|
|
55
|
+
readonly deployment: CoreDeployment;
|
|
56
|
+
|
|
57
|
+
constructor(deployment: CoreDeployment) {
|
|
58
|
+
this.deployment = normalizeCoreDeployment(deployment);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `bps::new`: aborts above 10,000. */
|
|
62
|
+
bps(value: bigint) {
|
|
63
|
+
const bps = requireBps(value);
|
|
64
|
+
return (tx: Transaction): TransactionResult =>
|
|
65
|
+
tx.moveCall({
|
|
66
|
+
target: this.#target("bps", "new"),
|
|
67
|
+
arguments: [tx.pure.u64(bps)],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `fee_split::new` over one `fee_split::route` per route, each built from
|
|
73
|
+
* its `*_destination` constructor and `bps::new`, in the given order.
|
|
74
|
+
*/
|
|
75
|
+
feeSplit(split: FeeSplit) {
|
|
76
|
+
const { routes } = normalizeFeeSplit(split);
|
|
77
|
+
return (tx: Transaction): TransactionResult => {
|
|
78
|
+
const elements = routes.map(({ destination, bps }) =>
|
|
79
|
+
tx.moveCall({
|
|
80
|
+
target: this.#target("fee_split", "route"),
|
|
81
|
+
arguments: [this.#destination(tx, destination), this.bps(bps)(tx)],
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
return tx.moveCall({
|
|
85
|
+
target: this.#target("fee_split", "new"),
|
|
86
|
+
arguments: [
|
|
87
|
+
tx.makeMoveVec({
|
|
88
|
+
type: coreType(this.deployment, "fee_split", "Route"),
|
|
89
|
+
elements,
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `payout::vesting_terms`; rejects terms the contract aborts on: positive
|
|
98
|
+
* steps whose total fits `MAX_VESTING_DURATION_MS` and a cliff within it.
|
|
99
|
+
*/
|
|
100
|
+
vestingTerms(terms: VestingTerms) {
|
|
101
|
+
const cliff = requireU64("cliffDelayMs", terms.cliffDelayMs);
|
|
102
|
+
const period = requireU64("periodDurationMs", terms.periodDurationMs);
|
|
103
|
+
const periods = requireU64("periods", terms.periods);
|
|
104
|
+
if (
|
|
105
|
+
period === 0n ||
|
|
106
|
+
periods === 0n ||
|
|
107
|
+
period > MAX_VESTING_DURATION_MS / periods ||
|
|
108
|
+
cliff > period * periods
|
|
109
|
+
) {
|
|
110
|
+
throw new RangeError(
|
|
111
|
+
"vesting needs positive steps whose total and cliff fit the bound",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return (tx: Transaction): TransactionResult =>
|
|
115
|
+
tx.moveCall({
|
|
116
|
+
target: this.#target("payout", "vesting_terms"),
|
|
117
|
+
arguments: [
|
|
118
|
+
tx.pure.u64(cliff),
|
|
119
|
+
tx.pure.u64(period),
|
|
120
|
+
tx.pure.u64(periods),
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** `payout::recipient`: paid outright when `vestingTerms` is null. */
|
|
126
|
+
recipient(options: {
|
|
127
|
+
destination: string;
|
|
128
|
+
vestingTerms: VestingTerms | null;
|
|
129
|
+
}) {
|
|
130
|
+
const terms = options.vestingTerms
|
|
131
|
+
? this.vestingTerms(options.vestingTerms)
|
|
132
|
+
: null;
|
|
133
|
+
const termsType = coreType(this.deployment, "payout", "VestingTerms");
|
|
134
|
+
return (tx: Transaction): TransactionResult =>
|
|
135
|
+
tx.moveCall({
|
|
136
|
+
target: this.#target("payout", "recipient"),
|
|
137
|
+
arguments: [
|
|
138
|
+
tx.pure.address(options.destination),
|
|
139
|
+
terms
|
|
140
|
+
? tx.moveCall({
|
|
141
|
+
target: "0x1::option::some",
|
|
142
|
+
typeArguments: [termsType],
|
|
143
|
+
arguments: [terms(tx)],
|
|
144
|
+
})
|
|
145
|
+
: tx.moveCall({
|
|
146
|
+
target: "0x1::option::none",
|
|
147
|
+
typeArguments: [termsType],
|
|
148
|
+
}),
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* `blast_treasury::schedule_destination`: a `TreasuryAdmin` witness
|
|
155
|
+
* schedules `destination` and the next change's `delayMs`.
|
|
156
|
+
*/
|
|
157
|
+
scheduleDestination(options: {
|
|
158
|
+
adminWitness: TransactionObjectInput;
|
|
159
|
+
destination: string;
|
|
160
|
+
delayMs: bigint;
|
|
161
|
+
}) {
|
|
162
|
+
const delayMs = requireU64("delayMs", options.delayMs);
|
|
163
|
+
return (tx: Transaction): void => {
|
|
164
|
+
tx.moveCall({
|
|
165
|
+
target: this.#target("blast_treasury", "schedule_destination"),
|
|
166
|
+
arguments: [
|
|
167
|
+
this.#treasury(tx, true),
|
|
168
|
+
tx.object(options.adminWitness),
|
|
169
|
+
tx.pure.address(options.destination),
|
|
170
|
+
tx.pure.u64(delayMs),
|
|
171
|
+
tx.object.clock(),
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** `blast_treasury::apply_destination`: anyone, once ready. */
|
|
178
|
+
applyDestination() {
|
|
179
|
+
return (tx: Transaction): void => {
|
|
180
|
+
tx.moveCall({
|
|
181
|
+
target: this.#target("blast_treasury", "apply_destination"),
|
|
182
|
+
arguments: [this.#treasury(tx, true), tx.object.clock()],
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** `blast_treasury::cancel_destination` with a `CancelAdmin` witness. */
|
|
188
|
+
cancelDestination(options: { adminWitness: TransactionObjectInput }) {
|
|
189
|
+
return (tx: Transaction): void => {
|
|
190
|
+
tx.moveCall({
|
|
191
|
+
target: this.#target("blast_treasury", "cancel_destination"),
|
|
192
|
+
arguments: [this.#treasury(tx, true), tx.object(options.adminWitness)],
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#destination(tx: Transaction, destination: Destination) {
|
|
198
|
+
switch (destination.kind) {
|
|
199
|
+
case "Address":
|
|
200
|
+
return tx.moveCall({
|
|
201
|
+
target: this.#target("fee_split", "address_destination"),
|
|
202
|
+
arguments: [tx.pure.address(destination.address)],
|
|
203
|
+
});
|
|
204
|
+
case "Owner":
|
|
205
|
+
return tx.moveCall({
|
|
206
|
+
target: this.#target("fee_split", "owner_destination"),
|
|
207
|
+
});
|
|
208
|
+
case "AutoLp":
|
|
209
|
+
return tx.moveCall({
|
|
210
|
+
target: this.#target("fee_split", "auto_lp_destination"),
|
|
211
|
+
});
|
|
212
|
+
case "HolderRewards":
|
|
213
|
+
return tx.moveCall({
|
|
214
|
+
target: this.#target("fee_split", "holder_rewards_destination"),
|
|
215
|
+
});
|
|
216
|
+
case "Burn":
|
|
217
|
+
return tx.moveCall({
|
|
218
|
+
target: this.#target("fee_split", "burn_destination"),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#target(module: string, functionName: string): string {
|
|
224
|
+
return `${this.deployment.packageId}::${module}::${functionName}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#treasury(tx: Transaction, mutable: boolean) {
|
|
228
|
+
return tx.sharedObjectRef({ ...this.deployment.treasury, mutable });
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/effects.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { SuiClientTypes } from "@mysten/sui/client";
|
|
2
|
+
import type { Transaction, TransactionResult } from "@mysten/sui/transactions";
|
|
3
|
+
import {
|
|
4
|
+
normalizeStructTag,
|
|
5
|
+
normalizeSuiAddress,
|
|
6
|
+
normalizeSuiObjectId,
|
|
7
|
+
parseStructTag,
|
|
8
|
+
SUI_FRAMEWORK_ADDRESS,
|
|
9
|
+
} from "@mysten/sui/utils";
|
|
10
|
+
import type { SharedObjectIdentity, SharedObjectReference } from "./sui.ts";
|
|
11
|
+
import { normalizeSharedObjectIdentity } from "./sui.ts";
|
|
12
|
+
|
|
13
|
+
// Effects readers shared by every generated-package publication: a product's
|
|
14
|
+
// coin package creates its currency through the product, which seals the
|
|
15
|
+
// `TreasuryCap` under `blast_core::sealed_treasury_cap`, and sends the
|
|
16
|
+
// `Currency` to the Coin Registry. Effects are the authority for created
|
|
17
|
+
// objects, owners and versions; events never are.
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The parts of one executed transaction these readers need: its effects and
|
|
21
|
+
* the types of the objects they touched (Core API `include: { effects: true,
|
|
22
|
+
* objectTypes: true }`).
|
|
23
|
+
*/
|
|
24
|
+
export type TransactionEffectsInput = {
|
|
25
|
+
effects: SuiClientTypes.TransactionEffects;
|
|
26
|
+
objectTypes: Readonly<Record<string, string>>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** An owned or receiving object at one version. */
|
|
30
|
+
export type ObjectVersionReference = {
|
|
31
|
+
objectId: string;
|
|
32
|
+
version: string;
|
|
33
|
+
digest: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** `sui::object::sui_coin_registry_address()`. */
|
|
37
|
+
export const COIN_REGISTRY_ID = normalizeSuiObjectId("0xc");
|
|
38
|
+
|
|
39
|
+
/** The objects a successful transaction created. */
|
|
40
|
+
export function createdObjects(
|
|
41
|
+
result: TransactionEffectsInput,
|
|
42
|
+
): SuiClientTypes.ChangedObject[] {
|
|
43
|
+
if (!result.effects.status.success) {
|
|
44
|
+
throw new TypeError("transaction did not succeed");
|
|
45
|
+
}
|
|
46
|
+
return result.effects.changedObjects.filter(
|
|
47
|
+
(object) => object.idOperation === "Created",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The one package a publication created. */
|
|
52
|
+
export function readPublishedPackage(
|
|
53
|
+
created: readonly SuiClientTypes.ChangedObject[],
|
|
54
|
+
): string {
|
|
55
|
+
const packages = created.filter(
|
|
56
|
+
(object) => object.outputState === "PackageWrite",
|
|
57
|
+
);
|
|
58
|
+
if (packages.length !== 1) {
|
|
59
|
+
throw new TypeError("publication must create exactly one package");
|
|
60
|
+
}
|
|
61
|
+
return normalizeSuiObjectId(packages[0]!.objectId);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The parsed type of a touched object, or undefined for a package. */
|
|
65
|
+
export function typeOf(
|
|
66
|
+
result: TransactionEffectsInput,
|
|
67
|
+
objectId: string,
|
|
68
|
+
): ReturnType<typeof parseStructTag> | undefined {
|
|
69
|
+
const type =
|
|
70
|
+
result.objectTypes[normalizeSuiObjectId(objectId)] ??
|
|
71
|
+
result.objectTypes[objectId];
|
|
72
|
+
return type === undefined || type === "package"
|
|
73
|
+
? undefined
|
|
74
|
+
: parseStructTag(type);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Whether the object is `0x2::coin_registry::Currency<coinType>`. */
|
|
78
|
+
export function isCurrencyOf(
|
|
79
|
+
result: TransactionEffectsInput,
|
|
80
|
+
objectId: string,
|
|
81
|
+
coinType: string,
|
|
82
|
+
): boolean {
|
|
83
|
+
const tag = typeOf(result, objectId);
|
|
84
|
+
const parameter = tag?.typeParams[0];
|
|
85
|
+
return (
|
|
86
|
+
tag?.address === normalizeSuiAddress(SUI_FRAMEWORK_ADDRESS) &&
|
|
87
|
+
tag.module === "coin_registry" &&
|
|
88
|
+
tag.name === "Currency" &&
|
|
89
|
+
tag.typeParams.length === 1 &&
|
|
90
|
+
parameter !== undefined &&
|
|
91
|
+
normalizeStructTag(parameter) === coinType
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The `<core>::sealed_treasury_cap::SealedTreasuryCap<Coin>` objects a
|
|
97
|
+
* publication created, each with its sealed coin type, in effects order.
|
|
98
|
+
*/
|
|
99
|
+
export function sealedTreasuryCaps(
|
|
100
|
+
result: TransactionEffectsInput,
|
|
101
|
+
created: readonly SuiClientTypes.ChangedObject[],
|
|
102
|
+
coreOriginalPackageId: string,
|
|
103
|
+
): { object: SuiClientTypes.ChangedObject; coinType: string }[] {
|
|
104
|
+
const core = normalizeSuiAddress(coreOriginalPackageId);
|
|
105
|
+
return created.flatMap((object) => {
|
|
106
|
+
const tag = typeOf(result, object.objectId);
|
|
107
|
+
const parameter = tag?.typeParams[0];
|
|
108
|
+
return tag?.address === core &&
|
|
109
|
+
tag.module === "sealed_treasury_cap" &&
|
|
110
|
+
tag.name === "SealedTreasuryCap" &&
|
|
111
|
+
parameter !== undefined &&
|
|
112
|
+
typeof parameter !== "string"
|
|
113
|
+
? [{ object, coinType: normalizeStructTag(parameter) }]
|
|
114
|
+
: [];
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The one sealed `TreasuryCap<coinType>` a publication sent to `owner`;
|
|
120
|
+
* `where` names the expectation in the error.
|
|
121
|
+
*/
|
|
122
|
+
export function readSealedTreasuryCap(
|
|
123
|
+
result: TransactionEffectsInput,
|
|
124
|
+
created: readonly SuiClientTypes.ChangedObject[],
|
|
125
|
+
coreOriginalPackageId: string,
|
|
126
|
+
coinType: string,
|
|
127
|
+
owner: string,
|
|
128
|
+
where: string,
|
|
129
|
+
): ObjectVersionReference & { owner: string } {
|
|
130
|
+
const caps = sealedTreasuryCaps(result, created, coreOriginalPackageId)
|
|
131
|
+
.filter(
|
|
132
|
+
(cap) =>
|
|
133
|
+
cap.coinType === coinType &&
|
|
134
|
+
cap.object.outputOwner?.$kind === "AddressOwner" &&
|
|
135
|
+
normalizeSuiAddress(cap.object.outputOwner.AddressOwner) === owner,
|
|
136
|
+
)
|
|
137
|
+
.map(({ object }) => object);
|
|
138
|
+
const [cap] = caps;
|
|
139
|
+
if (caps.length !== 1 || !cap) {
|
|
140
|
+
throw new TypeError(
|
|
141
|
+
`publication must send exactly one SealedTreasuryCap<Coin> ${where}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return { ...versionReference(cap), owner };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The one `Currency<coinType>` a publication sent to the Coin Registry address. */
|
|
148
|
+
export function readPendingCurrency(
|
|
149
|
+
result: TransactionEffectsInput,
|
|
150
|
+
created: readonly SuiClientTypes.ChangedObject[],
|
|
151
|
+
coinType: string,
|
|
152
|
+
): ObjectVersionReference {
|
|
153
|
+
const currencies = created.filter(
|
|
154
|
+
(object) =>
|
|
155
|
+
isCurrencyOf(result, object.objectId, coinType) &&
|
|
156
|
+
object.outputOwner?.$kind === "AddressOwner" &&
|
|
157
|
+
normalizeSuiAddress(object.outputOwner.AddressOwner) === COIN_REGISTRY_ID,
|
|
158
|
+
);
|
|
159
|
+
if (currencies.length !== 1) {
|
|
160
|
+
throw new TypeError(
|
|
161
|
+
"publication must send exactly one Currency<Coin> to the Coin Registry",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return versionReference(currencies[0]!);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Reads the shared `Currency<coinType>` that
|
|
169
|
+
* `coin_registry::finalize_registration` created at its derived address;
|
|
170
|
+
* mutable for the product's `finalize`.
|
|
171
|
+
*/
|
|
172
|
+
export function readCurrencyRegistration(
|
|
173
|
+
result: TransactionEffectsInput,
|
|
174
|
+
coinType: string,
|
|
175
|
+
): SharedObjectReference<true> {
|
|
176
|
+
const coin = normalizeStructTag(coinType);
|
|
177
|
+
const currencies = createdObjects(result).filter(
|
|
178
|
+
(object) =>
|
|
179
|
+
object.outputOwner?.$kind === "Shared" &&
|
|
180
|
+
isCurrencyOf(result, object.objectId, coin),
|
|
181
|
+
);
|
|
182
|
+
if (currencies.length !== 1) {
|
|
183
|
+
throw new TypeError("registration must share exactly one Currency<Coin>");
|
|
184
|
+
}
|
|
185
|
+
return sharedReference(currencies[0]!);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `0x2::coin_registry::finalize_registration<Coin>(registry, currency)`:
|
|
190
|
+
* shares a published coin's pending `Currency` from the Coin Registry
|
|
191
|
+
* address (`readPendingCurrency`). Anyone may run it; `coinRegistry` is the
|
|
192
|
+
* shared registry `0xc` with its initial shared version, so it builds
|
|
193
|
+
* offline.
|
|
194
|
+
*/
|
|
195
|
+
export function finalizeCurrencyRegistration(options: {
|
|
196
|
+
coinType: string;
|
|
197
|
+
currency: ObjectVersionReference;
|
|
198
|
+
coinRegistry: SharedObjectIdentity;
|
|
199
|
+
}): (tx: Transaction) => TransactionResult {
|
|
200
|
+
const coinType = normalizeStructTag(options.coinType);
|
|
201
|
+
const currency = {
|
|
202
|
+
objectId: normalizeSuiObjectId(options.currency.objectId),
|
|
203
|
+
version: options.currency.version,
|
|
204
|
+
digest: options.currency.digest,
|
|
205
|
+
};
|
|
206
|
+
const coinRegistry = normalizeSharedObjectIdentity(
|
|
207
|
+
"coinRegistry",
|
|
208
|
+
options.coinRegistry,
|
|
209
|
+
);
|
|
210
|
+
return (tx) =>
|
|
211
|
+
tx.moveCall({
|
|
212
|
+
target: `${SUI_FRAMEWORK_ADDRESS}::coin_registry::finalize_registration`,
|
|
213
|
+
typeArguments: [coinType],
|
|
214
|
+
arguments: [
|
|
215
|
+
tx.sharedObjectRef({ ...coinRegistry, mutable: true }),
|
|
216
|
+
tx.receivingRef(currency),
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** An owned object's ID, output version and digest. */
|
|
222
|
+
export function versionReference(
|
|
223
|
+
object: SuiClientTypes.ChangedObject,
|
|
224
|
+
): ObjectVersionReference {
|
|
225
|
+
if (object.outputVersion === null || object.outputDigest === null) {
|
|
226
|
+
throw new TypeError(`object ${object.objectId} has no output version`);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
objectId: normalizeSuiObjectId(object.objectId),
|
|
230
|
+
version: object.outputVersion,
|
|
231
|
+
digest: object.outputDigest,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** A shared object's identity, borrowed mutably. */
|
|
236
|
+
export function sharedReference(
|
|
237
|
+
object: SuiClientTypes.ChangedObject,
|
|
238
|
+
): SharedObjectReference<true> {
|
|
239
|
+
if (object.outputOwner?.$kind !== "Shared") {
|
|
240
|
+
throw new TypeError(`object ${object.objectId} is not shared`);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
objectId: normalizeSuiObjectId(object.objectId),
|
|
244
|
+
initialSharedVersion: object.outputOwner.Shared.initialSharedVersion,
|
|
245
|
+
mutable: true,
|
|
246
|
+
};
|
|
247
|
+
}
|
package/src/fee-split.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
2
|
+
import { normalizeSuiAddress } from "@mysten/sui/utils";
|
|
3
|
+
import { addressBcs } from "./bcs.ts";
|
|
4
|
+
import { apportionBps, bpsBcs, MAX_ROUTES, requireBps } from "./bps.ts";
|
|
5
|
+
import { BASIS_POINTS_DENOMINATOR } from "./integers.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `blast_core::fee_split::Destination`: where one route's part of each
|
|
9
|
+
* LP-fee collection goes. The vault decides what each special kind does.
|
|
10
|
+
*/
|
|
11
|
+
export type Destination =
|
|
12
|
+
| { kind: "Address"; address: string }
|
|
13
|
+
| { kind: "Owner" }
|
|
14
|
+
| { kind: "AutoLp" }
|
|
15
|
+
| { kind: "HolderRewards" }
|
|
16
|
+
| { kind: "Burn" };
|
|
17
|
+
|
|
18
|
+
/** `blast_core::fee_split::Route`: one destination and its positive share. */
|
|
19
|
+
export type Route = { destination: Destination; bps: bigint };
|
|
20
|
+
|
|
21
|
+
/** `blast_core::fee_split::FeeSplit`: routes summing to exactly 10,000. */
|
|
22
|
+
export type FeeSplit = { routes: Route[] };
|
|
23
|
+
|
|
24
|
+
/** BCS of `Destination`, in Move variant order. */
|
|
25
|
+
export const destinationBcs = bcs
|
|
26
|
+
.enum("Destination", {
|
|
27
|
+
Address: addressBcs,
|
|
28
|
+
Owner: null,
|
|
29
|
+
AutoLp: null,
|
|
30
|
+
HolderRewards: null,
|
|
31
|
+
Burn: null,
|
|
32
|
+
})
|
|
33
|
+
.transform({
|
|
34
|
+
input: (destination: Destination) =>
|
|
35
|
+
destination.kind === "Address"
|
|
36
|
+
? { Address: destination.address }
|
|
37
|
+
: ({ [destination.kind]: true } as
|
|
38
|
+
| { Owner: true }
|
|
39
|
+
| { AutoLp: true }
|
|
40
|
+
| { HolderRewards: true }
|
|
41
|
+
| { Burn: true }),
|
|
42
|
+
output: (destination): Destination =>
|
|
43
|
+
destination.$kind === "Address"
|
|
44
|
+
? { kind: "Address", address: destination.Address }
|
|
45
|
+
: { kind: destination.$kind },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const routeBcs = bcs.struct("Route", {
|
|
49
|
+
destination: destinationBcs,
|
|
50
|
+
bps: bpsBcs,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const feeSplitBcs = bcs.struct("FeeSplit", {
|
|
54
|
+
routes: bcs.vector(routeBcs),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Checks `split` as far as `fee_split::new` would reject it: at most 20
|
|
59
|
+
* routes, each positive (and an address route nonzero), no destination
|
|
60
|
+
* twice, summing to exactly 10,000. Returns the routes with normalized
|
|
61
|
+
* addresses, in the given order (the order `apportion` pays them).
|
|
62
|
+
*/
|
|
63
|
+
export function normalizeFeeSplit(split: FeeSplit): FeeSplit {
|
|
64
|
+
if (split.routes.length > MAX_ROUTES) {
|
|
65
|
+
throw new RangeError(`a fee split holds at most ${MAX_ROUTES} routes`);
|
|
66
|
+
}
|
|
67
|
+
const routes = split.routes.map(({ destination, bps }): Route => {
|
|
68
|
+
if (requireBps(bps) === 0n) {
|
|
69
|
+
throw new RangeError("a fee-split route needs a positive share");
|
|
70
|
+
}
|
|
71
|
+
if (destination.kind !== "Address") return { destination, bps };
|
|
72
|
+
const address = normalizeSuiAddress(destination.address);
|
|
73
|
+
if (address === normalizeSuiAddress("0x0")) {
|
|
74
|
+
throw new RangeError("a fee-split route cannot pay the zero address");
|
|
75
|
+
}
|
|
76
|
+
return { destination: { kind: "Address", address }, bps };
|
|
77
|
+
});
|
|
78
|
+
const keys = routes.map(({ destination }) =>
|
|
79
|
+
destination.kind === "Address" ? destination.address : destination.kind,
|
|
80
|
+
);
|
|
81
|
+
if (new Set(keys).size !== keys.length) {
|
|
82
|
+
throw new RangeError("a fee split names each destination at most once");
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
routes.reduce((sum, route) => sum + route.bps, 0n) !==
|
|
86
|
+
BASIS_POINTS_DENOMINATOR
|
|
87
|
+
) {
|
|
88
|
+
throw new RangeError("fee-split routes must sum to 10,000 basis points");
|
|
89
|
+
}
|
|
90
|
+
return { routes };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mirrors `fee_split::apportion`: `total` atoms split across the routes
|
|
95
|
+
* exactly, in route order, by `bps::apportion` with no rounded-up part.
|
|
96
|
+
*/
|
|
97
|
+
export function apportionFeeSplit(split: FeeSplit, total: bigint): bigint[] {
|
|
98
|
+
return apportionBps(
|
|
99
|
+
split.routes.map(({ bps }) => bps),
|
|
100
|
+
total,
|
|
101
|
+
null,
|
|
102
|
+
);
|
|
103
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
export * from "./aborts.ts";
|
|
1
2
|
export * from "./arrays.ts";
|
|
2
3
|
export * from "./bcs.ts";
|
|
4
|
+
export * from "./bps.ts";
|
|
3
5
|
export * from "./bytecode-template.ts";
|
|
4
6
|
export * from "./cetus.ts";
|
|
5
7
|
export * from "./coins.ts";
|
|
8
|
+
export * from "./deployment.ts";
|
|
9
|
+
export * from "./effects.ts";
|
|
6
10
|
export * from "./events.ts";
|
|
11
|
+
export * from "./fee-split.ts";
|
|
7
12
|
export * from "./gas.ts";
|
|
8
13
|
export * from "./integers.ts";
|
|
9
14
|
export * from "./merkle.ts";
|
|
15
|
+
export * from "./migration.ts";
|
|
10
16
|
export * from "./migration-math.ts";
|
|
17
|
+
export * from "./payout.ts";
|
|
18
|
+
export * from "./protocol-fees.ts";
|
|
11
19
|
export * from "./ptb.ts";
|
|
20
|
+
export * from "./roles.ts";
|
|
12
21
|
export * from "./sui.ts";
|
|
13
|
-
export * from "./
|
|
22
|
+
export * from "./treasury.ts";
|
package/src/integers.ts
CHANGED
|
@@ -45,6 +45,21 @@ export function requireBasisPoints(name: string, value: bigint): BasisPoints {
|
|
|
45
45
|
return value as BasisPoints;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Requires a basis-point value no greater than a contract's compiled maximum. */
|
|
49
|
+
export function requireBasisPointsAtMost(
|
|
50
|
+
name: string,
|
|
51
|
+
value: bigint,
|
|
52
|
+
maximum: bigint,
|
|
53
|
+
): BasisPoints {
|
|
54
|
+
const bps = requireBasisPoints(name, value);
|
|
55
|
+
if (bps > maximum) {
|
|
56
|
+
throw new RangeError(
|
|
57
|
+
`${name} exceeds ${maximum.toLocaleString("en-US")} basis points`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return bps;
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
/** Adds two values and requires the result to remain a Move `u64`. */
|
|
49
64
|
export function checkedAddU64(name: string, left: bigint, right: bigint): U64 {
|
|
50
65
|
return requireU64(name, left + right);
|
package/src/merkle.ts
CHANGED
|
@@ -4,7 +4,17 @@ export function blake2b256(bytes: Uint8Array): Uint8Array {
|
|
|
4
4
|
return blake2b(bytes, { dkLen: 32 });
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors `blast_core::merkle::leaf`: `blake2b256(0x00 || domain || payload)`,
|
|
9
|
+
* `domain` naming the tree's purpose (UTF-8) and `payload` the leaf's BCS.
|
|
10
|
+
*/
|
|
11
|
+
export function merkleLeaf(domain: string, payload: Uint8Array): Uint8Array {
|
|
12
|
+
return blake2b256(
|
|
13
|
+
new Uint8Array([0, ...new TextEncoder().encode(domain), ...payload]),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Mirrors `blast_core::merkle::node`: `blake2b256(0x01 || left || right)`. */
|
|
8
18
|
export function merkleNode(left: Uint8Array, right: Uint8Array): Uint8Array {
|
|
9
19
|
return blake2b256(new Uint8Array([1, ...left, ...right]));
|
|
10
20
|
}
|