@silvana-one/token 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 +1 -0
- package/dist/node/BondingCurveAdmin.d.ts +690 -0
- package/dist/node/BondingCurveAdmin.js +504 -0
- package/dist/node/BondingCurveAdmin.js.map +1 -0
- package/dist/node/FungibleToken.d.ts +414 -0
- package/dist/node/FungibleToken.js +7 -0
- package/dist/node/FungibleToken.js.map +1 -0
- package/dist/node/FungibleTokenAdvancedAdmin.d.ts +124 -0
- package/dist/node/FungibleTokenAdvancedAdmin.js +226 -0
- package/dist/node/FungibleTokenAdvancedAdmin.js.map +1 -0
- package/dist/node/FungibleTokenContract.d.ts +526 -0
- package/dist/node/FungibleTokenContract.js +295 -0
- package/dist/node/FungibleTokenContract.js.map +1 -0
- package/dist/node/FungibleTokenStandardAdmin.d.ts +27 -0
- package/dist/node/FungibleTokenStandardAdmin.js +101 -0
- package/dist/node/FungibleTokenStandardAdmin.js.map +1 -0
- package/dist/node/bid.d.ts +86 -0
- package/dist/node/bid.js +168 -0
- package/dist/node/bid.js.map +1 -0
- package/dist/node/claim.d.ts +89 -0
- package/dist/node/claim.js +156 -0
- package/dist/node/claim.js.map +1 -0
- package/dist/node/index.cjs +1576 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +9 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/offer.d.ts +87 -0
- package/dist/node/offer.js +169 -0
- package/dist/node/offer.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsconfig.web.tsbuildinfo +1 -0
- package/dist/web/BondingCurveAdmin.d.ts +690 -0
- package/dist/web/BondingCurveAdmin.js +504 -0
- package/dist/web/BondingCurveAdmin.js.map +1 -0
- package/dist/web/FungibleToken.d.ts +414 -0
- package/dist/web/FungibleToken.js +7 -0
- package/dist/web/FungibleToken.js.map +1 -0
- package/dist/web/FungibleTokenAdvancedAdmin.d.ts +124 -0
- package/dist/web/FungibleTokenAdvancedAdmin.js +226 -0
- package/dist/web/FungibleTokenAdvancedAdmin.js.map +1 -0
- package/dist/web/FungibleTokenContract.d.ts +526 -0
- package/dist/web/FungibleTokenContract.js +295 -0
- package/dist/web/FungibleTokenContract.js.map +1 -0
- package/dist/web/FungibleTokenStandardAdmin.d.ts +27 -0
- package/dist/web/FungibleTokenStandardAdmin.js +101 -0
- package/dist/web/FungibleTokenStandardAdmin.js.map +1 -0
- package/dist/web/bid.d.ts +86 -0
- package/dist/web/bid.js +168 -0
- package/dist/web/bid.js.map +1 -0
- package/dist/web/claim.d.ts +89 -0
- package/dist/web/claim.js +156 -0
- package/dist/web/claim.js.map +1 -0
- package/dist/web/index.d.ts +8 -0
- package/dist/web/index.js +9 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/offer.d.ts +87 -0
- package/dist/web/offer.js +169 -0
- package/dist/web/offer.js.map +1 -0
- package/package.json +64 -0
- package/src/BondingCurveAdmin.ts +590 -0
- package/src/FungibleToken.ts +11 -0
- package/src/FungibleTokenAdvancedAdmin.ts +260 -0
- package/src/FungibleTokenContract.ts +337 -0
- package/src/FungibleTokenStandardAdmin.ts +95 -0
- package/src/bid.ts +170 -0
- package/src/claim.ts +151 -0
- package/src/index.ts +8 -0
- package/src/offer.ts +164 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountUpdate,
|
|
3
|
+
Bool,
|
|
4
|
+
DeployArgs,
|
|
5
|
+
method,
|
|
6
|
+
Permissions,
|
|
7
|
+
Provable,
|
|
8
|
+
PublicKey,
|
|
9
|
+
TokenContract,
|
|
10
|
+
State,
|
|
11
|
+
state,
|
|
12
|
+
UInt64,
|
|
13
|
+
VerificationKey,
|
|
14
|
+
Field,
|
|
15
|
+
Struct,
|
|
16
|
+
AccountUpdateForest,
|
|
17
|
+
Int64,
|
|
18
|
+
UInt32,
|
|
19
|
+
fetchAccount,
|
|
20
|
+
Mina,
|
|
21
|
+
} from "o1js";
|
|
22
|
+
import {
|
|
23
|
+
FungibleTokenAdminBase,
|
|
24
|
+
FungibleTokenContract,
|
|
25
|
+
} from "@silvana-one/token";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default bonding curve:
|
|
29
|
+
* Initial price: 1 MINA per 100,000 tokens (0.00001 MINA per token), or 10_000 / 10 ** 9
|
|
30
|
+
* Curve K: 1 MINA per 100,000 tokens (0.00001 MINA per token), or 10_000 / 10 ** 9 MINA
|
|
31
|
+
* Owner fee: 10% in 0.001 % units(10 * 1000 = 10_000)
|
|
32
|
+
* Price formula: price = startPrice + curveK * totalSupply
|
|
33
|
+
* Example:
|
|
34
|
+
* If supply is 200,000 tokens, price = 1 MINA + 1 MINA * 200_000 / 100_000 = 3 MINA per 100,000 tokens
|
|
35
|
+
* or per token in MINA/1e9
|
|
36
|
+
* 10000 + 10000 * 200_000 / 100_000 = 30000 per token,
|
|
37
|
+
* or 30_000 * 100_000 = 3_000_000_000, or 3 MINA per 100,000 tokens
|
|
38
|
+
*
|
|
39
|
+
* To calculate the max supply for the given price for 100,000 tokens:
|
|
40
|
+
* price = startPrice + curveK * totalSupply
|
|
41
|
+
* price - startPrice = curveK * totalSupply
|
|
42
|
+
* (price - startPrice) / curveK = totalSupply
|
|
43
|
+
* (3 MINA - 1 MINA) / 1 MINA = 2 * 100_000 = 200_000 tokens
|
|
44
|
+
* (30_000 - 10_000) / 10_000 = 2 * 100_000 = 200_000 tokens
|
|
45
|
+
* or, in 1e9 units:
|
|
46
|
+
* (30_000 - 10_000) * 1e9 * 100_000 / 10_000 = 200_000_000_000_000, or to avoid overflow,
|
|
47
|
+
* (30_000 - 10_000) * 1e9 / 10_000 * 100_000 = 200_000_000_000_000
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
export class BondingCurveParams extends Struct({
|
|
51
|
+
startPrice: UInt64,
|
|
52
|
+
curveK: UInt64,
|
|
53
|
+
fee: UInt32, // 1000 = 1%
|
|
54
|
+
mintingIsAllowed: Bool,
|
|
55
|
+
}) {
|
|
56
|
+
pack() {
|
|
57
|
+
return Field.fromBits([
|
|
58
|
+
...this.startPrice.value.toBits(64),
|
|
59
|
+
...this.curveK.value.toBits(64),
|
|
60
|
+
...this.fee.value.toBits(32),
|
|
61
|
+
this.mintingIsAllowed,
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
static unpack(field: Field): BondingCurveParams {
|
|
65
|
+
const bits = field.toBits(64 + 64 + 32 + 1);
|
|
66
|
+
const startPrice = UInt64.Unsafe.fromField(
|
|
67
|
+
Field.fromBits(bits.slice(0, 64))
|
|
68
|
+
);
|
|
69
|
+
const curveK = UInt64.Unsafe.fromField(
|
|
70
|
+
Field.fromBits(bits.slice(64, 64 + 64))
|
|
71
|
+
);
|
|
72
|
+
const fee = UInt32.Unsafe.fromField(
|
|
73
|
+
Field.fromBits(bits.slice(64 + 64, 64 + 64 + 32))
|
|
74
|
+
);
|
|
75
|
+
const mintingIsAllowed = bits[64 + 64 + 32];
|
|
76
|
+
return new BondingCurveParams({
|
|
77
|
+
startPrice,
|
|
78
|
+
curveK,
|
|
79
|
+
fee,
|
|
80
|
+
mintingIsAllowed,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class BondingMintEvent extends Struct({
|
|
86
|
+
to: PublicKey,
|
|
87
|
+
amount: UInt64,
|
|
88
|
+
price: UInt64,
|
|
89
|
+
payment: UInt64,
|
|
90
|
+
fee: UInt64,
|
|
91
|
+
}) {}
|
|
92
|
+
|
|
93
|
+
export class BondingRedeemEvent extends Struct({
|
|
94
|
+
seller: PublicKey,
|
|
95
|
+
amount: UInt64,
|
|
96
|
+
payment: UInt64,
|
|
97
|
+
minBalance: UInt64,
|
|
98
|
+
maxSupply: UInt64,
|
|
99
|
+
fee: UInt64,
|
|
100
|
+
}) {}
|
|
101
|
+
|
|
102
|
+
export class BondingCurveAdminInitializeProps extends Struct({
|
|
103
|
+
tokenAddress: PublicKey,
|
|
104
|
+
startPrice: UInt64,
|
|
105
|
+
curveK: UInt64,
|
|
106
|
+
feeMaster: PublicKey,
|
|
107
|
+
fee: UInt32, // 1000 = 1%
|
|
108
|
+
launchFee: UInt64,
|
|
109
|
+
numberOfNewAccounts: UInt64,
|
|
110
|
+
}) {}
|
|
111
|
+
|
|
112
|
+
export class FungibleTokenBondingCurveAdmin
|
|
113
|
+
extends TokenContract
|
|
114
|
+
implements FungibleTokenAdminBase
|
|
115
|
+
{
|
|
116
|
+
@state(PublicKey) owner = State<PublicKey>(PublicKey.empty());
|
|
117
|
+
@state(PublicKey) token = State<PublicKey>(PublicKey.empty());
|
|
118
|
+
@state(PublicKey) feeMaster = State<PublicKey>(PublicKey.empty());
|
|
119
|
+
@state(Field) curve = State<Field>();
|
|
120
|
+
@state(Bool) insideMint = State<Bool>(Bool(false));
|
|
121
|
+
|
|
122
|
+
events = {
|
|
123
|
+
mint: BondingMintEvent,
|
|
124
|
+
redeem: BondingRedeemEvent,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
async deploy(props: DeployArgs) {
|
|
128
|
+
await super.deploy(props);
|
|
129
|
+
this.curve.set(
|
|
130
|
+
new BondingCurveParams({
|
|
131
|
+
startPrice: UInt64.from(10_000),
|
|
132
|
+
curveK: UInt64.from(10_000),
|
|
133
|
+
fee: UInt32.from(1000),
|
|
134
|
+
mintingIsAllowed: Bool(false),
|
|
135
|
+
}).pack()
|
|
136
|
+
);
|
|
137
|
+
this.account.permissions.set({
|
|
138
|
+
...Permissions.default(),
|
|
139
|
+
setVerificationKey:
|
|
140
|
+
Permissions.VerificationKey.impossibleDuringCurrentVersion(),
|
|
141
|
+
setPermissions: Permissions.impossible(),
|
|
142
|
+
send: Permissions.proof(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async approveBase(forest: AccountUpdateForest) {
|
|
147
|
+
throw Error("Transfer not allowed");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@method
|
|
151
|
+
async initialize(props: BondingCurveAdminInitializeProps) {
|
|
152
|
+
const {
|
|
153
|
+
tokenAddress,
|
|
154
|
+
feeMaster,
|
|
155
|
+
startPrice,
|
|
156
|
+
curveK,
|
|
157
|
+
fee,
|
|
158
|
+
launchFee,
|
|
159
|
+
numberOfNewAccounts,
|
|
160
|
+
} = props;
|
|
161
|
+
this.account.provedState.requireEquals(Bool(false));
|
|
162
|
+
this.token.set(tokenAddress);
|
|
163
|
+
this.feeMaster.set(feeMaster);
|
|
164
|
+
this.curve.set(
|
|
165
|
+
new BondingCurveParams({
|
|
166
|
+
startPrice,
|
|
167
|
+
curveK,
|
|
168
|
+
fee,
|
|
169
|
+
mintingIsAllowed: Bool(false),
|
|
170
|
+
}).pack()
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
/*
|
|
174
|
+
We cannot constrain the circulating supply of the token,
|
|
175
|
+
as the error Can't transfer to/from the circulation account
|
|
176
|
+
is thrown for ANY AccountUpdate for the circulation tracking account
|
|
177
|
+
so we keep the copy if the circulation amount here and sync sometimes
|
|
178
|
+
in the case the user burns tokens without calling the redeem method
|
|
179
|
+
*/
|
|
180
|
+
const supplyUpdate = AccountUpdate.createSigned(
|
|
181
|
+
this.address,
|
|
182
|
+
this.deriveTokenId()
|
|
183
|
+
);
|
|
184
|
+
let permissions = Permissions.default();
|
|
185
|
+
permissions.send = Permissions.none();
|
|
186
|
+
permissions.setPermissions = Permissions.impossible();
|
|
187
|
+
supplyUpdate.account.permissions.set(permissions);
|
|
188
|
+
|
|
189
|
+
const payment = launchFee.add(
|
|
190
|
+
numberOfNewAccounts.mul(UInt64.from(1_000_000_000))
|
|
191
|
+
);
|
|
192
|
+
const owner = this.sender.getUnconstrained();
|
|
193
|
+
const ownerUpdate = AccountUpdate.create(owner);
|
|
194
|
+
ownerUpdate.requireSignature();
|
|
195
|
+
this.owner.set(owner);
|
|
196
|
+
ownerUpdate.body.useFullCommitment = Bool(true);
|
|
197
|
+
ownerUpdate.account.balance.requireBetween(payment, UInt64.MAXINT());
|
|
198
|
+
ownerUpdate.balance.subInPlace(payment);
|
|
199
|
+
const feeUpdate = AccountUpdate.create(feeMaster);
|
|
200
|
+
feeUpdate.balance.addInPlace(launchFee);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@method async mint(to: PublicKey, amount: UInt64, price: UInt64) {
|
|
204
|
+
this.insideMint.getAndRequireEquals().assertEquals(Bool(false));
|
|
205
|
+
this.insideMint.set(Bool(true));
|
|
206
|
+
const tokenAddress = this.token.getAndRequireEquals();
|
|
207
|
+
const token = new BondingCurveFungibleToken(tokenAddress);
|
|
208
|
+
const { startPrice, curveK, fee, mintingIsAllowed } =
|
|
209
|
+
BondingCurveParams.unpack(this.curve.getAndRequireEquals());
|
|
210
|
+
const buyer = this.sender.getUnconstrained();
|
|
211
|
+
const buyerUpdate = AccountUpdate.create(buyer);
|
|
212
|
+
buyerUpdate.requireSignature();
|
|
213
|
+
buyerUpdate.body.useFullCommitment = Bool(true);
|
|
214
|
+
const owner = this.owner.getAndRequireEquals();
|
|
215
|
+
const isOwner = owner.equals(buyer);
|
|
216
|
+
const canMint = isOwner.or(mintingIsAllowed);
|
|
217
|
+
canMint.assertTrue("Minting is disabled for this token");
|
|
218
|
+
this.curve.set(
|
|
219
|
+
new BondingCurveParams({
|
|
220
|
+
startPrice,
|
|
221
|
+
curveK,
|
|
222
|
+
fee,
|
|
223
|
+
mintingIsAllowed: Bool(true),
|
|
224
|
+
}).pack()
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* (price - startPrice) / curveK = totalSupply
|
|
229
|
+
* (3 MINA - 1 MINA) / 1 MINA = 2 * 100_000 = 200_000 tokens
|
|
230
|
+
* (30_000 - 10_000) / 10_000 = 2 * 100_000 = 200_000 tokens
|
|
231
|
+
* or, in 1e9 units:
|
|
232
|
+
* (30_000 - 10_000) * 1e9 * 100_000 / 10_000 = 200_000_000_000_000, or to avoid overflow,
|
|
233
|
+
* (30_000 - 10_000) * 1e9 / 10_000 * 100_000 = 200_000_000_000_000
|
|
234
|
+
* (price - startPrice) * 1e9 / curveK * 100_000 = maximumSupply
|
|
235
|
+
* maximumSupply * curveK = (price - startPrice) * 1e14
|
|
236
|
+
* maximumSupply * curveK + startPrice * 1e14 = price * 1e14
|
|
237
|
+
*
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
const maximumSupplyField = Provable.witness(Field, () => {
|
|
241
|
+
if (price.toBigInt() < startPrice.toBigInt()) {
|
|
242
|
+
throw Error("Price must be greater than or equal to the start price");
|
|
243
|
+
}
|
|
244
|
+
return Field.from(
|
|
245
|
+
((price.toBigInt() - startPrice.toBigInt()) * 10n ** 14n) /
|
|
246
|
+
curveK.toBigInt()
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
maximumSupplyField
|
|
251
|
+
.mul(curveK.value)
|
|
252
|
+
.add(startPrice.value.mul(Field.from(10 ** 14)))
|
|
253
|
+
.assertLessThanOrEqual(price.value.mul(Field.from(10 ** 14)));
|
|
254
|
+
|
|
255
|
+
maximumSupplyField.assertLessThan(UInt64.MAXINT().value);
|
|
256
|
+
const maximumSupply = UInt64.Unsafe.fromField(maximumSupplyField);
|
|
257
|
+
const supplyUpdate = AccountUpdate.create(
|
|
258
|
+
this.address,
|
|
259
|
+
this.deriveTokenId()
|
|
260
|
+
);
|
|
261
|
+
supplyUpdate.account.balance.requireBetween(UInt64.zero, maximumSupply);
|
|
262
|
+
amount.assertLessThanOrEqual(UInt64.from(2n ** 62n));
|
|
263
|
+
supplyUpdate.balanceChange = Int64.fromUnsigned(amount);
|
|
264
|
+
const paymentField = Provable.witness(Field, () => {
|
|
265
|
+
let payment = (price.toBigInt() * amount.toBigInt()) / 10n ** 9n;
|
|
266
|
+
if (payment * 10n ** 9n !== price.toBigInt() * amount.toBigInt()) {
|
|
267
|
+
payment++;
|
|
268
|
+
}
|
|
269
|
+
if (payment * 10n ** 9n < price.toBigInt() * amount.toBigInt()) {
|
|
270
|
+
throw Error("Payment calculation failed");
|
|
271
|
+
}
|
|
272
|
+
return Field.from(payment);
|
|
273
|
+
});
|
|
274
|
+
paymentField
|
|
275
|
+
.mul(Field.from(10 ** 9))
|
|
276
|
+
.assertGreaterThanOrEqual(price.value.mul(amount.value));
|
|
277
|
+
paymentField.assertLessThan(UInt64.MAXINT().value);
|
|
278
|
+
const payment = UInt64.Unsafe.fromField(paymentField);
|
|
279
|
+
|
|
280
|
+
const feePaymentField = Provable.witness(Field, () => {
|
|
281
|
+
let feePayment = (payment.toBigInt() * fee.toBigint()) / 100_000n;
|
|
282
|
+
if (feePayment * 100_000n !== payment.toBigInt() * fee.toBigint()) {
|
|
283
|
+
feePayment++;
|
|
284
|
+
}
|
|
285
|
+
if (feePayment * 100_000n < payment.toBigInt() * fee.toBigint()) {
|
|
286
|
+
throw Error("Fee calculation failed");
|
|
287
|
+
}
|
|
288
|
+
return Field.from(feePayment);
|
|
289
|
+
});
|
|
290
|
+
feePaymentField
|
|
291
|
+
.mul(Field.from(100_000))
|
|
292
|
+
.assertGreaterThanOrEqual(payment.value.mul(fee.value));
|
|
293
|
+
feePaymentField.assertLessThan(UInt64.MAXINT().value);
|
|
294
|
+
let feePayment = UInt64.Unsafe.fromField(feePaymentField);
|
|
295
|
+
feePayment = Provable.if(
|
|
296
|
+
feePayment.lessThan(UInt64.from(100_000_000)),
|
|
297
|
+
UInt64.from(100_000_000),
|
|
298
|
+
feePayment
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const tokenUpdate = await token.mint(to, amount);
|
|
302
|
+
const isNew = tokenUpdate.account.isNew.get();
|
|
303
|
+
const totalPayment = payment
|
|
304
|
+
.add(feePayment)
|
|
305
|
+
.add(Provable.if(isNew, UInt64.from(1_000_000_000), UInt64.zero));
|
|
306
|
+
buyerUpdate.account.balance.requireBetween(totalPayment, UInt64.MAXINT());
|
|
307
|
+
buyerUpdate.balance.subInPlace(totalPayment);
|
|
308
|
+
|
|
309
|
+
this.balance.addInPlace(payment);
|
|
310
|
+
const feeUpdate = AccountUpdate.create(
|
|
311
|
+
this.feeMaster.getAndRequireEquals()
|
|
312
|
+
);
|
|
313
|
+
feeUpdate.body.useFullCommitment = Bool(true);
|
|
314
|
+
feeUpdate.balance.addInPlace(feePayment);
|
|
315
|
+
this.emitEvent(
|
|
316
|
+
"mint",
|
|
317
|
+
new BondingMintEvent({
|
|
318
|
+
to,
|
|
319
|
+
amount,
|
|
320
|
+
price,
|
|
321
|
+
payment,
|
|
322
|
+
fee: feePayment,
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
In case of other txs being included in the same block, the balance and supply may change.
|
|
329
|
+
We need to ensure that the balance is at least minBalance and the supply is at most maxSupply.
|
|
330
|
+
It is recommended to put 5% buffer for minBalance and 5% buffer for maxSupply for tx to succeed.
|
|
331
|
+
*/
|
|
332
|
+
@method async redeem(amount: UInt64, minPrice: UInt64, slippage: UInt32) {
|
|
333
|
+
const tokenAddress = this.token.getAndRequireEquals();
|
|
334
|
+
const token = new BondingCurveFungibleToken(tokenAddress);
|
|
335
|
+
|
|
336
|
+
const balance = await Provable.witnessAsync(UInt64, async () => {
|
|
337
|
+
await fetchAccount({
|
|
338
|
+
publicKey: this.address,
|
|
339
|
+
tokenId: this.tokenId,
|
|
340
|
+
});
|
|
341
|
+
const balance = Mina.getAccount(this.address, this.tokenId).balance;
|
|
342
|
+
console.log("redeem: balance", balance.toBigInt());
|
|
343
|
+
return balance;
|
|
344
|
+
});
|
|
345
|
+
slippage.assertLessThan(UInt32.from(1000));
|
|
346
|
+
const minBalanceField = Provable.witness(Field, () => {
|
|
347
|
+
let minBalance =
|
|
348
|
+
(balance.toBigInt() * (1000n - slippage.toBigint())) / 1000n;
|
|
349
|
+
if (
|
|
350
|
+
minBalance * 1000n !==
|
|
351
|
+
balance.toBigInt() * (1000n - slippage.toBigint())
|
|
352
|
+
) {
|
|
353
|
+
minBalance++;
|
|
354
|
+
}
|
|
355
|
+
if (
|
|
356
|
+
minBalance * 1000n <
|
|
357
|
+
balance.toBigInt() * (1000n - slippage.toBigint())
|
|
358
|
+
) {
|
|
359
|
+
throw Error("Min balance calculation failed");
|
|
360
|
+
}
|
|
361
|
+
return Field.from(minBalance);
|
|
362
|
+
});
|
|
363
|
+
minBalanceField
|
|
364
|
+
.mul(Field.from(1000))
|
|
365
|
+
.add(balance.value.mul(slippage.value))
|
|
366
|
+
.assertGreaterThanOrEqual(balance.value.mul(Field.from(1000)));
|
|
367
|
+
|
|
368
|
+
minBalanceField.assertLessThan(UInt64.MAXINT().value);
|
|
369
|
+
const minBalance = UInt64.Unsafe.fromField(minBalanceField);
|
|
370
|
+
this.account.balance.requireBetween(minBalance, UInt64.MAXINT());
|
|
371
|
+
|
|
372
|
+
const supply = await Provable.witnessAsync(UInt64, async () => {
|
|
373
|
+
await fetchAccount({
|
|
374
|
+
publicKey: this.address,
|
|
375
|
+
tokenId: this.deriveTokenId(),
|
|
376
|
+
});
|
|
377
|
+
const balance = Mina.getAccount(
|
|
378
|
+
this.address,
|
|
379
|
+
this.deriveTokenId()
|
|
380
|
+
).balance;
|
|
381
|
+
console.log("redeem: supply", balance.toBigInt());
|
|
382
|
+
return balance;
|
|
383
|
+
});
|
|
384
|
+
supply.assertGreaterThanOrEqual(amount);
|
|
385
|
+
supply.assertGreaterThan(UInt64.zero);
|
|
386
|
+
const maxSupplyField = Provable.witness(Field, () =>
|
|
387
|
+
Field.from((supply.toBigInt() * (1000n + slippage.toBigint())) / 1000n)
|
|
388
|
+
);
|
|
389
|
+
maxSupplyField
|
|
390
|
+
.mul(Field.from(1000))
|
|
391
|
+
.assertLessThanOrEqual(
|
|
392
|
+
supply.value.mul(Field.from(1000).add(slippage.value))
|
|
393
|
+
);
|
|
394
|
+
maxSupplyField.assertLessThan(UInt64.MAXINT().value);
|
|
395
|
+
const maxSupply = UInt64.Unsafe.fromField(maxSupplyField);
|
|
396
|
+
const supplyUpdate = AccountUpdate.create(
|
|
397
|
+
this.address,
|
|
398
|
+
this.deriveTokenId()
|
|
399
|
+
);
|
|
400
|
+
supplyUpdate.account.balance.requireBetween(UInt64.zero, maxSupply);
|
|
401
|
+
supplyUpdate.balanceChange = Int64.fromUnsigned(amount).neg();
|
|
402
|
+
|
|
403
|
+
// We avoid modular division that is not working in case there is remainder
|
|
404
|
+
const paymentField = Provable.witness(Field, () =>
|
|
405
|
+
Field.from(
|
|
406
|
+
(minBalance.toBigInt() * amount.toBigInt()) / maxSupply.toBigInt()
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
// We use assertLessThanOrEqual to handle the case when there is remainder
|
|
410
|
+
paymentField
|
|
411
|
+
.mul(maxSupply.value)
|
|
412
|
+
.assertLessThanOrEqual(minBalance.value.mul(amount.value));
|
|
413
|
+
paymentField.assertLessThan(Field.from(2n ** 62n));
|
|
414
|
+
amount.value
|
|
415
|
+
.mul(minPrice.value)
|
|
416
|
+
.assertLessThanOrEqual(paymentField.mul(Field.from(10 ** 9)));
|
|
417
|
+
const payment = UInt64.Unsafe.fromField(paymentField);
|
|
418
|
+
const { fee, mintingIsAllowed } = BondingCurveParams.unpack(
|
|
419
|
+
this.curve.getAndRequireEquals()
|
|
420
|
+
);
|
|
421
|
+
mintingIsAllowed.assertTrue(
|
|
422
|
+
"Minting is disabled for this token, nothing to redeem"
|
|
423
|
+
);
|
|
424
|
+
let feePayment = Provable.witness(UInt64, () => {
|
|
425
|
+
let feePayment = (payment.toBigInt() * fee.toBigint()) / 100_000n;
|
|
426
|
+
if (feePayment * 100_000n !== payment.toBigInt() * fee.toBigint()) {
|
|
427
|
+
feePayment++;
|
|
428
|
+
}
|
|
429
|
+
if (feePayment * 100_000n < payment.toBigInt() * fee.toBigint()) {
|
|
430
|
+
throw Error("Fee calculation failed");
|
|
431
|
+
}
|
|
432
|
+
return UInt64.from(feePayment);
|
|
433
|
+
});
|
|
434
|
+
feePayment = Provable.if(
|
|
435
|
+
feePayment.lessThan(UInt64.from(100_000_000)),
|
|
436
|
+
UInt64.from(100_000_000),
|
|
437
|
+
feePayment
|
|
438
|
+
);
|
|
439
|
+
const seller = this.sender.getUnconstrained();
|
|
440
|
+
const sellerUpdate = AccountUpdate.create(seller);
|
|
441
|
+
const isNew = await Provable.witnessAsync(Bool, async () => {
|
|
442
|
+
const sellerAccount = await fetchAccount({
|
|
443
|
+
publicKey: seller,
|
|
444
|
+
});
|
|
445
|
+
return Bool(sellerAccount.account === undefined);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
sellerUpdate.account.isNew.requireEquals(isNew);
|
|
449
|
+
const accountCreationFee = Provable.if(
|
|
450
|
+
isNew,
|
|
451
|
+
UInt64.from(1_000_000_000),
|
|
452
|
+
UInt64.zero
|
|
453
|
+
);
|
|
454
|
+
payment.assertGreaterThan(feePayment.add(accountCreationFee));
|
|
455
|
+
sellerUpdate.requireSignature();
|
|
456
|
+
sellerUpdate.body.useFullCommitment = Bool(true);
|
|
457
|
+
const totalPayment = Provable.witness(UInt64, () => {
|
|
458
|
+
if (
|
|
459
|
+
payment.toBigInt() <
|
|
460
|
+
feePayment.toBigInt() + accountCreationFee.toBigInt()
|
|
461
|
+
) {
|
|
462
|
+
console.error(
|
|
463
|
+
`The redeem amount ${
|
|
464
|
+
Number(payment.toBigInt() / 10n ** 6n) / 1000
|
|
465
|
+
} MINA is too low to cover the fee ${
|
|
466
|
+
Number(feePayment.toBigInt() / 10n ** 6n) / 1000
|
|
467
|
+
} MINA and account creation fee ${
|
|
468
|
+
Number(accountCreationFee.toBigInt() / 10n ** 6n) / 1000
|
|
469
|
+
} MINA for the account ${seller.toBase58()}`,
|
|
470
|
+
{
|
|
471
|
+
payment: payment.toBigInt(),
|
|
472
|
+
feePayment: feePayment.toBigInt(),
|
|
473
|
+
accountCreationFee: accountCreationFee.toBigInt(),
|
|
474
|
+
minBalance: minBalance.toBigInt(),
|
|
475
|
+
maxSupply: maxSupply.toBigInt(),
|
|
476
|
+
supply: supply.toBigInt(),
|
|
477
|
+
amount: amount.toBigInt(),
|
|
478
|
+
balance: balance.toBigInt(),
|
|
479
|
+
tokenAddress: tokenAddress.toBase58(),
|
|
480
|
+
seller: seller.toBase58(),
|
|
481
|
+
isNew: isNew.toBoolean(),
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
throw Error(
|
|
485
|
+
`The redeem amount ${
|
|
486
|
+
Number(payment.toBigInt() / 10n ** 6n) / 1000
|
|
487
|
+
} MINA is too low to cover the fee ${
|
|
488
|
+
Number(feePayment.toBigInt() / 10n ** 6n) / 1000
|
|
489
|
+
} MINA and account creation fee ${
|
|
490
|
+
Number(accountCreationFee.toBigInt() / 10n ** 6n) / 1000
|
|
491
|
+
} MINA for the account ${seller.toBase58()}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return UInt64.from(
|
|
495
|
+
payment.toBigInt() -
|
|
496
|
+
feePayment.toBigInt() -
|
|
497
|
+
accountCreationFee.toBigInt()
|
|
498
|
+
);
|
|
499
|
+
});
|
|
500
|
+
totalPayment.add(feePayment).add(accountCreationFee).assertEquals(payment);
|
|
501
|
+
sellerUpdate.balance.addInPlace(totalPayment);
|
|
502
|
+
const feeUpdate = AccountUpdate.create(
|
|
503
|
+
this.feeMaster.getAndRequireEquals()
|
|
504
|
+
);
|
|
505
|
+
feeUpdate.body.useFullCommitment = Bool(true);
|
|
506
|
+
feeUpdate.balance.addInPlace(feePayment);
|
|
507
|
+
this.balance.subInPlace(payment);
|
|
508
|
+
await token.burn(seller, amount);
|
|
509
|
+
this.emitEvent(
|
|
510
|
+
"redeem",
|
|
511
|
+
new BondingRedeemEvent({
|
|
512
|
+
seller,
|
|
513
|
+
amount,
|
|
514
|
+
payment,
|
|
515
|
+
minBalance,
|
|
516
|
+
maxSupply,
|
|
517
|
+
fee: feePayment,
|
|
518
|
+
})
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* In case the user burned tokens without calling the redeem method,
|
|
524
|
+
* we need to sync the supply to the actual circulated supply
|
|
525
|
+
*/
|
|
526
|
+
|
|
527
|
+
@method async sync() {
|
|
528
|
+
const tokenAddress = this.token.getAndRequireEquals();
|
|
529
|
+
const token = new BondingCurveFungibleToken(tokenAddress);
|
|
530
|
+
const supplyUpdate = AccountUpdate.create(
|
|
531
|
+
this.address,
|
|
532
|
+
this.deriveTokenId()
|
|
533
|
+
);
|
|
534
|
+
const circulatingSupply = await token.getBalanceOf(tokenAddress);
|
|
535
|
+
const totalSupply = supplyUpdate.account.balance.getAndRequireEquals();
|
|
536
|
+
supplyUpdate.balanceChange = Int64.fromUnsigned(
|
|
537
|
+
totalSupply.sub(circulatingSupply)
|
|
538
|
+
).neg();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Update the verification key.
|
|
542
|
+
* Note that because we have set the permissions for setting the verification key to `impossibleDuringCurrentVersion()`, this will only be possible in case of a protocol update that requires an update.
|
|
543
|
+
*/
|
|
544
|
+
@method
|
|
545
|
+
async updateVerificationKey(vk: VerificationKey) {
|
|
546
|
+
this.account.verificationKey.set(vk);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
ensureOwnerSignature() {
|
|
550
|
+
const owner = this.owner.getAndRequireEquals();
|
|
551
|
+
const update = AccountUpdate.createSigned(owner);
|
|
552
|
+
update.body.useFullCommitment = Bool(true);
|
|
553
|
+
return update;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
@method.returns(Bool)
|
|
557
|
+
public async canMint(_accountUpdate: AccountUpdate) {
|
|
558
|
+
this.insideMint.requireEquals(Bool(true));
|
|
559
|
+
this.insideMint.set(Bool(false));
|
|
560
|
+
return Bool(true);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@method.returns(Bool)
|
|
564
|
+
public async canChangeAdmin(_admin: PublicKey) {
|
|
565
|
+
this.ensureOwnerSignature();
|
|
566
|
+
return Bool(true);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
@method.returns(Bool)
|
|
570
|
+
public async canPause(): Promise<Bool> {
|
|
571
|
+
this.ensureOwnerSignature();
|
|
572
|
+
return Bool(true);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
@method.returns(Bool)
|
|
576
|
+
public async canResume(): Promise<Bool> {
|
|
577
|
+
this.ensureOwnerSignature();
|
|
578
|
+
return Bool(true);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@method.returns(Bool)
|
|
582
|
+
public async canChangeVerificationKey(_vk: VerificationKey): Promise<Bool> {
|
|
583
|
+
this.ensureOwnerSignature();
|
|
584
|
+
return Bool(true);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export const BondingCurveFungibleToken = FungibleTokenContract(
|
|
589
|
+
FungibleTokenBondingCurveAdmin
|
|
590
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FungibleTokenContract,
|
|
3
|
+
FungibleTokenAdminBase,
|
|
4
|
+
} from "./FungibleTokenContract.js";
|
|
5
|
+
import { FungibleTokenAdmin } from "./FungibleTokenStandardAdmin.js";
|
|
6
|
+
import { FungibleTokenAdvancedAdmin } from "./FungibleTokenAdvancedAdmin.js";
|
|
7
|
+
|
|
8
|
+
export { FungibleToken, AdvancedFungibleToken };
|
|
9
|
+
|
|
10
|
+
const FungibleToken = FungibleTokenContract(FungibleTokenAdmin);
|
|
11
|
+
const AdvancedFungibleToken = FungibleTokenContract(FungibleTokenAdvancedAdmin);
|