@pbgtoken/rwa-contract 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +2 -0
- package/dist/CardanoWalletProvider.d.ts +27 -0
- package/dist/PricesProvider.d.ts +13 -0
- package/dist/RWADatumProvider.d.ts +51 -0
- package/dist/TokenizedAccountProvider.d.ts +21 -0
- package/dist/TransferID.d.ts +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +473 -0
- package/dist/retryExpBackoff.d.ts +13 -0
- package/dist/tokens.d.ts +2 -0
- package/dist/validators/index.d.ts +203 -0
- package/package.json +42 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Address } from "@helios-lang/ledger";
|
|
2
|
+
import { type BlockfrostV0Client } from "@helios-lang/tx-utils";
|
|
3
|
+
import { type PricesProvider } from "./PricesProvider";
|
|
4
|
+
import { type TokenizedAccountProvider } from "./TokenizedAccountProvider";
|
|
5
|
+
/**
|
|
6
|
+
* Constructs a CardanoWallet-specific implementation of TokenizedAccountClient.
|
|
7
|
+
*
|
|
8
|
+
* @param provider
|
|
9
|
+
* Must be a mainnet provider!
|
|
10
|
+
* TODO: generalize to allow any Cardano provider
|
|
11
|
+
*
|
|
12
|
+
* @param address
|
|
13
|
+
* Must be a Shelley-era address on mainnet
|
|
14
|
+
*
|
|
15
|
+
* @param prices
|
|
16
|
+
* A provider for USD/ADA prices etc.
|
|
17
|
+
*
|
|
18
|
+
* @throws
|
|
19
|
+
* If the `provider` isn't for mainnet.
|
|
20
|
+
*
|
|
21
|
+
* @throws
|
|
22
|
+
* If the `address` isn't a Shelley-era address.
|
|
23
|
+
*
|
|
24
|
+
* @returns
|
|
25
|
+
* A CardanoWallet-specific implementation of TokenizedAccountClient.
|
|
26
|
+
*/
|
|
27
|
+
export declare function makeCardanoWalletProvider(provider: BlockfrostV0Client, address: Address, prices: PricesProvider): TokenizedAccountProvider;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StrictType } from "@helios-lang/contract-utils";
|
|
2
|
+
import { MintingPolicyHash } from "@helios-lang/ledger";
|
|
3
|
+
import { ReadonlyCardanoClient } from "@helios-lang/tx-utils";
|
|
4
|
+
declare const castDatum: (config: import("@helios-lang/contract-utils").CastConfig) => import("@helios-lang/contract-utils").Cast<{
|
|
5
|
+
current_supply: bigint;
|
|
6
|
+
supply_after_last_mint: bigint;
|
|
7
|
+
transfer_id_before_last_mint: number[];
|
|
8
|
+
type: string;
|
|
9
|
+
account: number[];
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
decimals: bigint;
|
|
13
|
+
ticker: string;
|
|
14
|
+
url: string;
|
|
15
|
+
logo: string;
|
|
16
|
+
}, {
|
|
17
|
+
current_supply: import("@helios-lang/codec-utils").IntLike;
|
|
18
|
+
supply_after_last_mint: import("@helios-lang/codec-utils").IntLike;
|
|
19
|
+
transfer_id_before_last_mint: number[];
|
|
20
|
+
type: string;
|
|
21
|
+
account: number[];
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
decimals: import("@helios-lang/codec-utils").IntLike;
|
|
25
|
+
ticker: string;
|
|
26
|
+
url: string;
|
|
27
|
+
logo: string;
|
|
28
|
+
}>;
|
|
29
|
+
export type RWADatum = StrictType<typeof castDatum>;
|
|
30
|
+
/**
|
|
31
|
+
* Export the interface, not the class itself.
|
|
32
|
+
*
|
|
33
|
+
* This approach allows as to add/remove/rename private properties without breaking compatibiliy.
|
|
34
|
+
* This also prevents the use of `instanceof`, which is an evil operator.
|
|
35
|
+
*/
|
|
36
|
+
export interface RWADatumProvider {
|
|
37
|
+
fetch(policy: MintingPolicyHash): Promise<RWADatum>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Constructs a RWADatumProvider instance.
|
|
41
|
+
*
|
|
42
|
+
* @param client
|
|
43
|
+
* The Cardano provider, initialized to the correct network
|
|
44
|
+
*
|
|
45
|
+
* TODO: generalize to be able to use any Cardano provider
|
|
46
|
+
*
|
|
47
|
+
* @returns
|
|
48
|
+
* A RWADatumProvider instance.
|
|
49
|
+
*/
|
|
50
|
+
export declare function makeRWADatumProvider(provider: ReadonlyCardanoClient): RWADatumProvider;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type TransferID } from "./TransferID";
|
|
2
|
+
export interface TokenizedAccountProvider {
|
|
3
|
+
/**
|
|
4
|
+
* @returns
|
|
5
|
+
* USD value of account holdings
|
|
6
|
+
*/
|
|
7
|
+
balance: Promise<number>;
|
|
8
|
+
/**
|
|
9
|
+
* @returns
|
|
10
|
+
* All historical transfer IDs, sorted from earliest to latest
|
|
11
|
+
*/
|
|
12
|
+
transferHistory: Promise<TransferID[]>;
|
|
13
|
+
/**
|
|
14
|
+
* @param transfers
|
|
15
|
+
* Only aggregate the deposits/withdrawals of these transfers (so not of all transfers)
|
|
16
|
+
*
|
|
17
|
+
* @returns
|
|
18
|
+
* USD value aggregate of deposits (positive) and withdrawals (negative)
|
|
19
|
+
*/
|
|
20
|
+
deposits(transfers: TransferID[]): Promise<number>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex encoded transfer ID bytes
|
|
3
|
+
*
|
|
4
|
+
* A hex string is easier to work with than a list of numbers
|
|
5
|
+
*/
|
|
6
|
+
export type TransferID = string;
|
|
7
|
+
/**
|
|
8
|
+
* @param transfers
|
|
9
|
+
* List of sorted transfers, earliest first
|
|
10
|
+
*
|
|
11
|
+
* @param after
|
|
12
|
+
* Not including this transfer
|
|
13
|
+
*
|
|
14
|
+
* @returns
|
|
15
|
+
* Filtered list of transfers
|
|
16
|
+
*/
|
|
17
|
+
export declare function filterTransfersAfter(transfers: TransferID[], after: TransferID): TransferID[];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { makeCardanoWalletProvider } from "./CardanoWalletProvider";
|
|
2
|
+
export { type PricesProvider } from "./PricesProvider";
|
|
3
|
+
export { makeRWADatumProvider, type RWADatumProvider } from "./RWADatumProvider";
|
|
4
|
+
export { type TokenizedAccountProvider } from "./TokenizedAccountProvider";
|
|
5
|
+
export { filterTransfersAfter, type TransferID } from "./TransferID";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
// src/CardanoWalletProvider.ts
|
|
2
|
+
import { addValues, makeTxId, makeValue } from "@helios-lang/ledger";
|
|
3
|
+
import { getAssetClassInfo } from "@helios-lang/tx-utils";
|
|
4
|
+
|
|
5
|
+
// src/retryExpBackoff.ts
|
|
6
|
+
async function retryExpBackoff(callback, config) {
|
|
7
|
+
let attempt = 1;
|
|
8
|
+
let lastError = void 0;
|
|
9
|
+
while (attempt <= config.maxRetries) {
|
|
10
|
+
try {
|
|
11
|
+
const res = await callback();
|
|
12
|
+
return res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
lastError = e;
|
|
15
|
+
}
|
|
16
|
+
attempt++;
|
|
17
|
+
if (attempt <= config.maxRetries) {
|
|
18
|
+
await new Promise((resolve) => {
|
|
19
|
+
setTimeout(resolve, config.delay * Math.pow(2, attempt - 1));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw lastError;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/CardanoWalletProvider.ts
|
|
27
|
+
function makeCardanoWalletProvider(provider, address, prices) {
|
|
28
|
+
if (!provider.isMainnet()) {
|
|
29
|
+
throw new Error("not a mainnet Cardano provider");
|
|
30
|
+
}
|
|
31
|
+
if (address.era != "Shelley") {
|
|
32
|
+
throw new Error("not a Shelley era address");
|
|
33
|
+
}
|
|
34
|
+
if (!address.mainnet) {
|
|
35
|
+
throw new Error("not a mainnet Cardano wallet address");
|
|
36
|
+
}
|
|
37
|
+
return new CardanoWalletProviderImpl(provider, address, prices);
|
|
38
|
+
}
|
|
39
|
+
var CardanoWalletProviderImpl = class {
|
|
40
|
+
provider;
|
|
41
|
+
address;
|
|
42
|
+
prices;
|
|
43
|
+
/**
|
|
44
|
+
* @param provider
|
|
45
|
+
* The Cardano client
|
|
46
|
+
*
|
|
47
|
+
* @param address
|
|
48
|
+
* The mainnet address for which to fetch the balance and transactions.
|
|
49
|
+
*
|
|
50
|
+
* @param prices
|
|
51
|
+
*/
|
|
52
|
+
constructor(provider, address, prices) {
|
|
53
|
+
this.provider = provider;
|
|
54
|
+
this.address = address;
|
|
55
|
+
this.prices = prices;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @returns
|
|
59
|
+
* The USD total value of the Cardano wallet
|
|
60
|
+
*/
|
|
61
|
+
get balance() {
|
|
62
|
+
return (async () => {
|
|
63
|
+
const utxos = await this.provider.getUtxos(this.address);
|
|
64
|
+
const v = addValues(utxos);
|
|
65
|
+
const ada = await this.aggregateValue(v);
|
|
66
|
+
const adaPrice = await this.prices.getSpotPrice("ADA", "USD");
|
|
67
|
+
return ada * adaPrice;
|
|
68
|
+
})();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @returns
|
|
72
|
+
* A list of hex encoded transaction IDs
|
|
73
|
+
*/
|
|
74
|
+
get transferHistory() {
|
|
75
|
+
return (async () => {
|
|
76
|
+
const txInfos = await this.provider.getAddressTxs(this.address);
|
|
77
|
+
txInfos.sort((a, b) => {
|
|
78
|
+
return a.blockTime - b.blockTime;
|
|
79
|
+
});
|
|
80
|
+
return txInfos.map((txInfo) => txInfo.id.toHex());
|
|
81
|
+
})();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @param transfers
|
|
85
|
+
* Sum over the given transfers only
|
|
86
|
+
*
|
|
87
|
+
* @returns
|
|
88
|
+
* USD value of deposits (positive) and withdrawals (negative)
|
|
89
|
+
*/
|
|
90
|
+
async deposits(transfers) {
|
|
91
|
+
let txs = [];
|
|
92
|
+
for (let i = 0; i < transfers.length; i++) {
|
|
93
|
+
const id = transfers[i];
|
|
94
|
+
const tx = await retryExpBackoff(
|
|
95
|
+
async () => {
|
|
96
|
+
return await this.provider.getTxInfo(makeTxId(id));
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
delay: 1e3,
|
|
100
|
+
maxRetries: 3
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
txs.push(tx);
|
|
104
|
+
}
|
|
105
|
+
const depositValue = txs.reduce((value, tx) => {
|
|
106
|
+
value = tx.inputs.reduce((v, input) => {
|
|
107
|
+
if (input.address.isEqual(this.address)) {
|
|
108
|
+
return v.subtract(input.value);
|
|
109
|
+
} else {
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
}, value);
|
|
113
|
+
value = tx.outputs.reduce((v, output) => {
|
|
114
|
+
if (output.address.isEqual(this.address)) {
|
|
115
|
+
return v.add(output.value);
|
|
116
|
+
} else {
|
|
117
|
+
return v;
|
|
118
|
+
}
|
|
119
|
+
}, value);
|
|
120
|
+
return value;
|
|
121
|
+
}, makeValue(0n));
|
|
122
|
+
return await this.aggregateValue(depositValue);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* CNTs are Cardano Native Tokens
|
|
126
|
+
*
|
|
127
|
+
* @param value
|
|
128
|
+
* Lovelace + CNTs
|
|
129
|
+
*
|
|
130
|
+
* @returns
|
|
131
|
+
* A number of USD
|
|
132
|
+
*/
|
|
133
|
+
async aggregateValue(value) {
|
|
134
|
+
let ada = Number(value.lovelace) / 1e6;
|
|
135
|
+
for (let ac of value.assets.assetClasses) {
|
|
136
|
+
try {
|
|
137
|
+
const { ticker, decimals } = await getAssetClassInfo(
|
|
138
|
+
this.provider,
|
|
139
|
+
ac
|
|
140
|
+
);
|
|
141
|
+
try {
|
|
142
|
+
const tickerPrice = await this.prices.getSpotPrice(
|
|
143
|
+
ticker,
|
|
144
|
+
"ADA"
|
|
145
|
+
);
|
|
146
|
+
const qty = value.assets.getAssetClassQuantity(ac);
|
|
147
|
+
ada += Number(qty) / Math.pow(10, decimals) * tickerPrice;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error(
|
|
150
|
+
`${ticker} price not yet available (${e.message})`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.error(
|
|
155
|
+
`${ac.toString()} doesn't have CIP26 metadata, ignoring (${e.message})`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const adaPrice = await this.prices.getSpotPrice("ADA", "USD");
|
|
160
|
+
return ada * adaPrice;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/RWADatumProvider.ts
|
|
165
|
+
import { bytesToHex } from "@helios-lang/codec-utils";
|
|
166
|
+
import { makeShelleyAddress, makeValidatorHash } from "@helios-lang/ledger";
|
|
167
|
+
import { expectDefined } from "@helios-lang/type-utils";
|
|
168
|
+
|
|
169
|
+
// src/validators/index.ts
|
|
170
|
+
import { makeCast, makeUserFunc } from "@helios-lang/contract-utils";
|
|
171
|
+
var tokenized_account = {
|
|
172
|
+
$name: "tokenized_account",
|
|
173
|
+
$purpose: "mixed",
|
|
174
|
+
$currentScriptIndex: 0,
|
|
175
|
+
$sourceCode: `mixed tokenized_account
|
|
176
|
+
|
|
177
|
+
import { get_current_input, tx } from ScriptContext
|
|
178
|
+
|
|
179
|
+
struct State {
|
|
180
|
+
current_supply: Int "current_supply" // smaller or equal to supply_after_last_mint
|
|
181
|
+
supply_after_last_mint: Int "supply_after_last_mint"
|
|
182
|
+
transfer_id_before_last_mint: ByteArray "transfer_id_before_last_mint" // this is hash, because it might contain data that shouldn't be public
|
|
183
|
+
|
|
184
|
+
type: String "type" // eg. CardanoWallet or Private
|
|
185
|
+
account: ByteArray "account" // if type==Private -> this is a hash of the actual account type + address
|
|
186
|
+
|
|
187
|
+
name: String "name"
|
|
188
|
+
description: String "description"
|
|
189
|
+
decimals: Int "decimals" // can't change
|
|
190
|
+
ticker: String "ticker" // can't change
|
|
191
|
+
url: String "url"
|
|
192
|
+
logo: String "logo"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
enum Cip68Extra {
|
|
196
|
+
Unused
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
enum Metadata {
|
|
200
|
+
Cip68 {
|
|
201
|
+
state: State
|
|
202
|
+
version: Int
|
|
203
|
+
extra: Cip68Extra
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func state(self) -> State {
|
|
207
|
+
self.switch{
|
|
208
|
+
x: Cip68 => x.state
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
struct Redeemer {
|
|
214
|
+
total_reserves: Real // usually a USD value, 6 decimal places is enough
|
|
215
|
+
reserves_change: Real
|
|
216
|
+
latest_transfer_id: ByteArray
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const SEED_ID = TxOutputId::new(TxId::new(#), 0)
|
|
220
|
+
const ORACLE_KEYS = []PubKeyHash{}
|
|
221
|
+
const INITIAL_PRICE: Real = 0.001 // 1000 USD per token -> 0.001 USD per microtoken
|
|
222
|
+
const TYPE = "Private"
|
|
223
|
+
const ACCOUNT = #
|
|
224
|
+
const TICKER = "USDT"
|
|
225
|
+
const NAME = TICKER + " RWA"
|
|
226
|
+
const DESCRIPTION = TICKER + " RWA operated by PBG"
|
|
227
|
+
const DECIMALS = 6
|
|
228
|
+
const URL = "https://www.pbg.io"
|
|
229
|
+
const LOGO = "https://assets.pbg.io/usdt_bridge.png"
|
|
230
|
+
|
|
231
|
+
const ticker_bytes = TICKER.encode_utf8()
|
|
232
|
+
const user_token_name = Cip67::fungible_token_label + ticker_bytes
|
|
233
|
+
const ref_token_name = Cip67::reference_token_label + ticker_bytes
|
|
234
|
+
|
|
235
|
+
const own_hash = Scripts::tokenized_account
|
|
236
|
+
const own_mph = MintingPolicyHash::from_script_hash(own_hash)
|
|
237
|
+
const own_address = Address::new(
|
|
238
|
+
SpendingCredential::new_validator(
|
|
239
|
+
ValidatorHash::from_script_hash(own_hash)
|
|
240
|
+
),
|
|
241
|
+
Option[StakingCredential]::None
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const ref_token_asset_class = AssetClass::new(own_mph, ref_token_name)
|
|
245
|
+
const user_token_asset_class = AssetClass::new(own_mph, user_token_name)
|
|
246
|
+
|
|
247
|
+
func validate_initialization() -> () {
|
|
248
|
+
assert(tx.inputs.any((input: TxInput) -> {
|
|
249
|
+
input.output_id == SEED_ID
|
|
250
|
+
}), "seed UTXO not spent")
|
|
251
|
+
|
|
252
|
+
ref_utxo = tx.outputs.find((output: TxOutput) -> {
|
|
253
|
+
output.address == own_address
|
|
254
|
+
&& output.value.get_safe(ref_token_asset_class) == 1
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
metadata = ref_utxo.datum.inline.as_strictly[Metadata]
|
|
258
|
+
|
|
259
|
+
assert(metadata == Metadata::Cip68{
|
|
260
|
+
State{
|
|
261
|
+
current_supply: 0,
|
|
262
|
+
supply_after_last_mint: 0,
|
|
263
|
+
transfer_id_before_last_mint: #,
|
|
264
|
+
|
|
265
|
+
type: TYPE,
|
|
266
|
+
account: ACCOUNT,
|
|
267
|
+
|
|
268
|
+
name: NAME,
|
|
269
|
+
description: DESCRIPTION,
|
|
270
|
+
decimals: DECIMALS,
|
|
271
|
+
ticker: TICKER,
|
|
272
|
+
url: URL,
|
|
273
|
+
logo: LOGO
|
|
274
|
+
},
|
|
275
|
+
1,
|
|
276
|
+
Cip68Extra::Unused
|
|
277
|
+
}, "metadata not initialized correctly")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
func signed_by_quorum() -> Bool {
|
|
281
|
+
n_signers = ORACLE_KEYS.fold((n_signers: Int, key: PubKeyHash) -> {
|
|
282
|
+
n_signers + tx.is_signed_by(key).to_int()
|
|
283
|
+
}, 0)
|
|
284
|
+
|
|
285
|
+
n_signers > (ORACLE_KEYS.length/2)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
func calc_token_price(R: Real, Delta: Real, current_supply: Int, supply_after_last_mint: Int) -> Real {
|
|
289
|
+
if (current_supply == 0 || supply_after_last_mint == 0) {
|
|
290
|
+
INITIAL_PRICE
|
|
291
|
+
} else {
|
|
292
|
+
p_last = (R - Delta)/supply_after_last_mint
|
|
293
|
+
p_curr = R / current_supply
|
|
294
|
+
|
|
295
|
+
if (p_last < p_curr) {
|
|
296
|
+
p_last
|
|
297
|
+
} else {
|
|
298
|
+
p_curr
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
func validate_state_change(redeemer: Redeemer, input: TxInput) -> () {
|
|
304
|
+
state0 = input.datum.inline.as[Metadata].state()
|
|
305
|
+
|
|
306
|
+
output = tx.outputs.find((output: TxOutput) -> {
|
|
307
|
+
output.address == own_address
|
|
308
|
+
&& output.value.get_safe(ref_token_asset_class) > 0
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
state1 = output.datum.inline.as_strictly[Metadata].state()
|
|
312
|
+
|
|
313
|
+
// ensure constant metadata fields don't change
|
|
314
|
+
assert(state1.type == state0.type, "type not constant")
|
|
315
|
+
assert(state1.account == state0.account, "account not constant")
|
|
316
|
+
assert(state1.ticker == state0.ticker, "metadata ticker not constant")
|
|
317
|
+
assert(state1.decimals == state0.decimals, "metadata decimals not constant")
|
|
318
|
+
|
|
319
|
+
n = tx.minted.get_safe(user_token_asset_class)
|
|
320
|
+
N0 = state0.current_supply
|
|
321
|
+
N1 = state1.current_supply
|
|
322
|
+
|
|
323
|
+
assert((N1 - N0) == n, "current token supply not updated correctly")
|
|
324
|
+
|
|
325
|
+
if (n > 0) {
|
|
326
|
+
Redeemer{R, Delta, latest_transfer_id} = redeemer
|
|
327
|
+
|
|
328
|
+
p = calc_token_price(R, Delta, N0, state0.supply_after_last_mint)
|
|
329
|
+
|
|
330
|
+
assert(N1*p <= R, "too many tokens minted")
|
|
331
|
+
assert(state1.transfer_id_before_last_mint == latest_transfer_id, "transfer_id_before_last_mint not updated correctly")
|
|
332
|
+
assert(state1.supply_after_last_mint == N1, "supply_after_last_mint not updated correctly")
|
|
333
|
+
assert(signed_by_quorum(), "not signed by simple quorum of oracles")
|
|
334
|
+
} else {
|
|
335
|
+
assert(state1.supply_after_last_mint == state0.supply_after_last_mint, "supply_after_last_mint can't change during burn")
|
|
336
|
+
assert(state1.transfer_id_before_last_mint == state0.transfer_id_before_last_mint, "transfer_id_before_last_mint can't change during burn")
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
func main(args: MixedArgs) -> () {
|
|
341
|
+
args.switch{
|
|
342
|
+
s: Spending => {
|
|
343
|
+
redeemer = s.redeemer.as[Redeemer]
|
|
344
|
+
|
|
345
|
+
utxo = get_current_input()
|
|
346
|
+
|
|
347
|
+
tokens = utxo.value.get_policy(own_mph)
|
|
348
|
+
|
|
349
|
+
if (tokens.is_empty()) {
|
|
350
|
+
// UTXOs that don't contain any tokens from current policy can always be spent.
|
|
351
|
+
// This can be used to remove garbage.
|
|
352
|
+
()
|
|
353
|
+
} else {
|
|
354
|
+
(name, _) = tokens.head
|
|
355
|
+
|
|
356
|
+
if (name == ref_token_name) {
|
|
357
|
+
validate_state_change(redeemer, utxo)
|
|
358
|
+
} else {
|
|
359
|
+
error("unexpected token name")
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
Other => {
|
|
364
|
+
tokens = tx.minted.get_policy(own_mph)
|
|
365
|
+
|
|
366
|
+
(name, qty) = tokens.head
|
|
367
|
+
tail = tokens.tail
|
|
368
|
+
|
|
369
|
+
assert(tail.is_empty(), "only one token kind can be minted or burned")
|
|
370
|
+
|
|
371
|
+
if (name == user_token_name && qty != 0) {
|
|
372
|
+
// metadata token must be spent, which triggers Spending witness
|
|
373
|
+
assert(tx.inputs.any((input: TxInput) -> {
|
|
374
|
+
input.address == own_address
|
|
375
|
+
&& input.value.get_safe(ref_token_asset_class) > 0
|
|
376
|
+
}), "ref token not spent")
|
|
377
|
+
} else if (qty == 1 && (name == ref_token_name)) {
|
|
378
|
+
validate_initialization()
|
|
379
|
+
} else {
|
|
380
|
+
error("invalid minted tokens")
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}`,
|
|
385
|
+
$dependencies: [],
|
|
386
|
+
$hashDependencies: [],
|
|
387
|
+
$dependsOnOwnHash: true,
|
|
388
|
+
$Redeemer: (config) => makeCast({ "kind": "enum", "id": "__helios__mixedargs", "name": "MixedArgs", "variantTypes": [{ "kind": "variant", "name": "Other", "id": "__helios__mixedargs__other", "tag": 0, "fieldTypes": [{ "name": "redeemer", "type": { "kind": "internal", "name": "Data" } }] }, { "kind": "variant", "name": "Spending", "id": "__helios__mixedargs__spending", "tag": 1, "fieldTypes": [{ "name": "redeemer", "type": { "kind": "internal", "name": "Data" } }] }] }, config),
|
|
389
|
+
$Datum: (config) => makeCast({ "kind": "internal", "name": "Data" }, config),
|
|
390
|
+
$types: {
|
|
391
|
+
State: (config) => makeCast({ "kind": "struct", "format": "map", "id": "__module__tokenized_account__State[]", "name": "State", "fieldTypes": [{ "name": "current_supply", "type": { "kind": "internal", "name": "Int" }, "key": "current_supply" }, { "name": "supply_after_last_mint", "type": { "kind": "internal", "name": "Int" }, "key": "supply_after_last_mint" }, { "name": "transfer_id_before_last_mint", "type": { "kind": "internal", "name": "ByteArray" }, "key": "transfer_id_before_last_mint" }, { "name": "type", "type": { "kind": "internal", "name": "String" }, "key": "type" }, { "name": "account", "type": { "kind": "internal", "name": "ByteArray" }, "key": "account" }, { "name": "name", "type": { "kind": "internal", "name": "String" }, "key": "name" }, { "name": "description", "type": { "kind": "internal", "name": "String" }, "key": "description" }, { "name": "decimals", "type": { "kind": "internal", "name": "Int" }, "key": "decimals" }, { "name": "ticker", "type": { "kind": "internal", "name": "String" }, "key": "ticker" }, { "name": "url", "type": { "kind": "internal", "name": "String" }, "key": "url" }, { "name": "logo", "type": { "kind": "internal", "name": "String" }, "key": "logo" }] }, config),
|
|
392
|
+
Cip68Extra: (config) => makeCast({ "kind": "enum", "name": "Cip68Extra", "id": "__module__tokenized_account__Cip68Extra[]", "variantTypes": [{ "kind": "variant", "tag": 0, "id": "__module__tokenized_account__Cip68Extra[]__Unused", "name": "Unused", "fieldTypes": [] }] }, config),
|
|
393
|
+
Metadata: (config) => makeCast({ "kind": "enum", "name": "Metadata", "id": "__module__tokenized_account__Metadata[]", "variantTypes": [{ "kind": "variant", "tag": 0, "id": "__module__tokenized_account__Metadata[]__Cip68", "name": "Cip68", "fieldTypes": [{ "name": "state", "type": { "kind": "struct", "format": "map", "id": "__module__tokenized_account__State[]", "name": "State", "fieldTypes": [{ "name": "current_supply", "type": { "kind": "internal", "name": "Int" }, "key": "current_supply" }, { "name": "supply_after_last_mint", "type": { "kind": "internal", "name": "Int" }, "key": "supply_after_last_mint" }, { "name": "transfer_id_before_last_mint", "type": { "kind": "internal", "name": "ByteArray" }, "key": "transfer_id_before_last_mint" }, { "name": "type", "type": { "kind": "internal", "name": "String" }, "key": "type" }, { "name": "account", "type": { "kind": "internal", "name": "ByteArray" }, "key": "account" }, { "name": "name", "type": { "kind": "internal", "name": "String" }, "key": "name" }, { "name": "description", "type": { "kind": "internal", "name": "String" }, "key": "description" }, { "name": "decimals", "type": { "kind": "internal", "name": "Int" }, "key": "decimals" }, { "name": "ticker", "type": { "kind": "internal", "name": "String" }, "key": "ticker" }, { "name": "url", "type": { "kind": "internal", "name": "String" }, "key": "url" }, { "name": "logo", "type": { "kind": "internal", "name": "String" }, "key": "logo" }] } }, { "name": "version", "type": { "kind": "internal", "name": "Int" } }, { "name": "extra", "type": { "kind": "enum", "name": "Cip68Extra", "id": "__module__tokenized_account__Cip68Extra[]", "variantTypes": [{ "kind": "variant", "tag": 0, "id": "__module__tokenized_account__Cip68Extra[]__Unused", "name": "Unused", "fieldTypes": [] }] } }] }] }, config),
|
|
394
|
+
Redeemer: (config) => makeCast({ "kind": "struct", "format": "list", "id": "__module__tokenized_account__Redeemer[]", "name": "Redeemer", "fieldTypes": [{ "name": "total_reserves", "type": { "kind": "internal", "name": "Real" } }, { "name": "reserves_change", "type": { "kind": "internal", "name": "Real" } }, { "name": "latest_transfer_id", "type": { "kind": "internal", "name": "ByteArray" } }] }, config)
|
|
395
|
+
},
|
|
396
|
+
$functions: {
|
|
397
|
+
"Metadata::state": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "Metadata::state", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [{ "name": "self", "isOptional": false, "type": { "kind": "enum", "name": "Metadata", "id": "__module__tokenized_account__Metadata[]", "variantTypes": [{ "kind": "variant", "tag": 0, "id": "__module__tokenized_account__Metadata[]__Cip68", "name": "Cip68", "fieldTypes": [{ "name": "state", "type": { "kind": "struct", "format": "map", "id": "__module__tokenized_account__State[]", "name": "State", "fieldTypes": [{ "name": "current_supply", "type": { "kind": "internal", "name": "Int" }, "key": "current_supply" }, { "name": "supply_after_last_mint", "type": { "kind": "internal", "name": "Int" }, "key": "supply_after_last_mint" }, { "name": "transfer_id_before_last_mint", "type": { "kind": "internal", "name": "ByteArray" }, "key": "transfer_id_before_last_mint" }, { "name": "type", "type": { "kind": "internal", "name": "String" }, "key": "type" }, { "name": "account", "type": { "kind": "internal", "name": "ByteArray" }, "key": "account" }, { "name": "name", "type": { "kind": "internal", "name": "String" }, "key": "name" }, { "name": "description", "type": { "kind": "internal", "name": "String" }, "key": "description" }, { "name": "decimals", "type": { "kind": "internal", "name": "Int" }, "key": "decimals" }, { "name": "ticker", "type": { "kind": "internal", "name": "String" }, "key": "ticker" }, { "name": "url", "type": { "kind": "internal", "name": "String" }, "key": "url" }, { "name": "logo", "type": { "kind": "internal", "name": "String" }, "key": "logo" }] } }, { "name": "version", "type": { "kind": "internal", "name": "Int" } }, { "name": "extra", "type": { "kind": "enum", "name": "Cip68Extra", "id": "__module__tokenized_account__Cip68Extra[]", "variantTypes": [{ "kind": "variant", "tag": 0, "id": "__module__tokenized_account__Cip68Extra[]__Unused", "name": "Unused", "fieldTypes": [] }] } }] }] } }], "returns": { "kind": "struct", "format": "map", "id": "__module__tokenized_account__State[]", "name": "State", "fieldTypes": [{ "name": "current_supply", "type": { "kind": "internal", "name": "Int" }, "key": "current_supply" }, { "name": "supply_after_last_mint", "type": { "kind": "internal", "name": "Int" }, "key": "supply_after_last_mint" }, { "name": "transfer_id_before_last_mint", "type": { "kind": "internal", "name": "ByteArray" }, "key": "transfer_id_before_last_mint" }, { "name": "type", "type": { "kind": "internal", "name": "String" }, "key": "type" }, { "name": "account", "type": { "kind": "internal", "name": "ByteArray" }, "key": "account" }, { "name": "name", "type": { "kind": "internal", "name": "String" }, "key": "name" }, { "name": "description", "type": { "kind": "internal", "name": "String" }, "key": "description" }, { "name": "decimals", "type": { "kind": "internal", "name": "Int" }, "key": "decimals" }, { "name": "ticker", "type": { "kind": "internal", "name": "String" }, "key": "ticker" }, { "name": "url", "type": { "kind": "internal", "name": "String" }, "key": "url" }, { "name": "logo", "type": { "kind": "internal", "name": "String" }, "key": "logo" }] } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
398
|
+
"SEED_ID": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "SEED_ID", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "TxOutputId" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
399
|
+
"ORACLE_KEYS": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "ORACLE_KEYS", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "list", "itemType": { "kind": "internal", "name": "PubKeyHash" } } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
400
|
+
"INITIAL_PRICE": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "INITIAL_PRICE", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "Real" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
401
|
+
"TYPE": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "TYPE", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
402
|
+
"ACCOUNT": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "ACCOUNT", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "ByteArray" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
403
|
+
"TICKER": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "TICKER", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
404
|
+
"NAME": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "NAME", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
405
|
+
"DESCRIPTION": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "DESCRIPTION", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
406
|
+
"DECIMALS": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "DECIMALS", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "Int" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
407
|
+
"URL": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "URL", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
408
|
+
"LOGO": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "LOGO", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "String" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
409
|
+
"ticker_bytes": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "ticker_bytes", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "ByteArray" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
410
|
+
"user_token_name": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "user_token_name", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "ByteArray" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
411
|
+
"ref_token_name": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "ref_token_name", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "ByteArray" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
412
|
+
"own_hash": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "own_hash", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "ScriptHash" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
413
|
+
"own_mph": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "own_mph", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "MintingPolicyHash" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
414
|
+
"own_address": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "own_address", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "Address" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
415
|
+
"ref_token_asset_class": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "ref_token_asset_class", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "AssetClass" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
416
|
+
"user_token_asset_class": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "user_token_asset_class", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [], "returns": { "kind": "internal", "name": "AssetClass" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
417
|
+
"validate_initialization": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "validate_initialization", "requiresCurrentScript": false, "requiresScriptContext": true, "arguments": [] }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
418
|
+
"signed_by_quorum": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "signed_by_quorum", "requiresCurrentScript": false, "requiresScriptContext": true, "arguments": [], "returns": { "kind": "internal", "name": "Bool" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
419
|
+
"calc_token_price": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "calc_token_price", "requiresCurrentScript": false, "requiresScriptContext": false, "arguments": [{ "name": "R", "isOptional": false, "type": { "kind": "internal", "name": "Real" } }, { "name": "Delta", "isOptional": false, "type": { "kind": "internal", "name": "Real" } }, { "name": "current_supply", "isOptional": false, "type": { "kind": "internal", "name": "Int" } }, { "name": "supply_after_last_mint", "isOptional": false, "type": { "kind": "internal", "name": "Int" } }], "returns": { "kind": "internal", "name": "Real" } }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
420
|
+
"validate_state_change": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "validate_state_change", "requiresCurrentScript": false, "requiresScriptContext": true, "arguments": [{ "name": "redeemer", "isOptional": false, "type": { "kind": "struct", "format": "list", "id": "__module__tokenized_account__Redeemer[]", "name": "Redeemer", "fieldTypes": [{ "name": "total_reserves", "type": { "kind": "internal", "name": "Real" } }, { "name": "reserves_change", "type": { "kind": "internal", "name": "Real" } }, { "name": "latest_transfer_id", "type": { "kind": "internal", "name": "ByteArray" } }] } }, { "name": "input", "isOptional": false, "type": { "kind": "internal", "name": "TxInput" } }] }, castConfig: config, validatorIndices: __validatorIndices }),
|
|
421
|
+
"main": (uplc, config) => makeUserFunc(uplc, { ...{ "name": "main", "arguments": [{ "name": "$datum", "isOptional": true, "type": { "kind": "internal", "name": "Data" } }, { "name": "args", "isOptional": false, "type": { "kind": "enum", "id": "__helios__mixedargs", "name": "MixedArgs", "variantTypes": [{ "kind": "variant", "name": "Other", "id": "__helios__mixedargs__other", "tag": 0, "fieldTypes": [{ "name": "redeemer", "type": { "kind": "internal", "name": "Data" } }] }, { "kind": "variant", "name": "Spending", "id": "__helios__mixedargs__spending", "tag": 1, "fieldTypes": [{ "name": "redeemer", "type": { "kind": "internal", "name": "Data" } }] }] } }], "requiresCurrentScript": false, "requiresScriptContext": true }, castConfig: config, validatorIndices: __validatorIndices })
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var __validatorIndices = { "tokenized_account": 0 };
|
|
425
|
+
|
|
426
|
+
// src/RWADatumProvider.ts
|
|
427
|
+
var castMetadata = tokenized_account.$types.Metadata;
|
|
428
|
+
var castDatum = tokenized_account.$types.State;
|
|
429
|
+
function makeRWADatumProvider(provider) {
|
|
430
|
+
return new RWADatumProviderImpl(provider);
|
|
431
|
+
}
|
|
432
|
+
var RWADatumProviderImpl = class {
|
|
433
|
+
provider;
|
|
434
|
+
constructor(provider) {
|
|
435
|
+
this.provider = provider;
|
|
436
|
+
}
|
|
437
|
+
async fetch(policy) {
|
|
438
|
+
const utxo = await this.fetchMetadataUTXO(policy);
|
|
439
|
+
const metadata = castMetadata({ isMainnet: this.isMainnet }).fromUplcData(expectDefined(utxo.datum?.data, "metadata UTXO doesn't have datum"));
|
|
440
|
+
return metadata.Cip68.state;
|
|
441
|
+
}
|
|
442
|
+
get isMainnet() {
|
|
443
|
+
return this.provider.isMainnet();
|
|
444
|
+
}
|
|
445
|
+
async fetchMetadataUTXO(policy) {
|
|
446
|
+
const vh = makeValidatorHash(policy.bytes);
|
|
447
|
+
const addr = makeShelleyAddress(this.isMainnet, vh);
|
|
448
|
+
let utxos = await this.provider.getUtxos(addr);
|
|
449
|
+
utxos = utxos.filter(
|
|
450
|
+
(utxo) => utxo.value.assets.assets.some(
|
|
451
|
+
([mph, tokens]) => mph.isEqual(policy) && tokens.some(
|
|
452
|
+
([tokenName, qty]) => bytesToHex(tokenName.slice(0, 4)) == "000643b0" && qty == 1n
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
);
|
|
456
|
+
return expectDefined(utxos[0], "expected 1 metadata UTXO");
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/TransferID.ts
|
|
461
|
+
function filterTransfersAfter(transfers, after) {
|
|
462
|
+
const cutOff = transfers.findIndex((id) => id == after);
|
|
463
|
+
if (cutOff != -1) {
|
|
464
|
+
return transfers.slice(cutOff + 1);
|
|
465
|
+
} else {
|
|
466
|
+
return transfers;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
export {
|
|
470
|
+
filterTransfersAfter,
|
|
471
|
+
makeCardanoWalletProvider,
|
|
472
|
+
makeRWADatumProvider
|
|
473
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helper for retrying network requests
|
|
3
|
+
*
|
|
4
|
+
* @param callback
|
|
5
|
+
* @param config
|
|
6
|
+
* delay is a number in milliseconds
|
|
7
|
+
*
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export declare function retryExpBackoff<T>(callback: () => Promise<T>, config: {
|
|
11
|
+
delay: number;
|
|
12
|
+
maxRetries: number;
|
|
13
|
+
}): Promise<T>;
|
package/dist/tokens.d.ts
ADDED