Skip to content

Commit cd1a07d

Browse files
committed
feat: add mint builder
1 parent 06d6311 commit cd1a07d

File tree

14 files changed

+1030
-298
lines changed

14 files changed

+1030
-298
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
import { afterAll, beforeAll, describe, expect, it } from "@effect/vitest"
2+
import * as Cluster from "@evolution-sdk/devnet/Cluster"
3+
import * as Config from "@evolution-sdk/devnet/Config"
4+
import * as Genesis from "@evolution-sdk/devnet/Genesis"
5+
import { Core } from "@evolution-sdk/evolution"
6+
import * as CoreAddress from "@evolution-sdk/evolution/core/Address"
7+
import * as AssetName from "@evolution-sdk/evolution/core/AssetName"
8+
import * as NativeScripts from "@evolution-sdk/evolution/core/NativeScripts"
9+
import * as PolicyId from "@evolution-sdk/evolution/core/PolicyId"
10+
import * as ScriptHash from "@evolution-sdk/evolution/core/ScriptHash"
11+
import * as Text from "@evolution-sdk/evolution/core/Text"
12+
import { createClient } from "@evolution-sdk/evolution/sdk/client/ClientImpl"
13+
14+
const CoreAssets = Core.Assets
15+
16+
describe("TxBuilder Minting (Devnet Submit)", () => {
17+
// ============================================================================
18+
// Devnet Setup
19+
// ============================================================================
20+
21+
let devnetCluster: Cluster.Cluster | undefined
22+
let genesisConfig: Config.ShelleyGenesis
23+
let genesisUtxos: ReadonlyArray<Core.UTxO.UTxO> = []
24+
let nativeScript: NativeScripts.NativeScript
25+
let policyId: string
26+
27+
const TEST_MNEMONIC = "test test test test test test test test test test test test test test test test test test test test test test test sauce"
28+
const ASSET_NAME = "TestToken"
29+
30+
const createTestClient = () =>
31+
createClient({
32+
network: 0,
33+
provider: {
34+
type: "kupmios",
35+
kupoUrl: "http://localhost:1443",
36+
ogmiosUrl: "http://localhost:1338"
37+
},
38+
wallet: {
39+
type: "seed",
40+
mnemonic: TEST_MNEMONIC,
41+
accountIndex: 0
42+
}
43+
})
44+
45+
beforeAll(async () => {
46+
const testClient = createClient({
47+
network: 0,
48+
wallet: { type: "seed", mnemonic: TEST_MNEMONIC, accountIndex: 0 }
49+
})
50+
51+
const testAddress = await testClient.address()
52+
const testAddressHex = CoreAddress.toHex(testAddress)
53+
54+
// Get payment key hash from client's address for native script
55+
const paymentKeyHash = testAddress.paymentCredential.hash
56+
57+
// Create native script requiring signature from payment key
58+
nativeScript = NativeScripts.makeScriptPubKey(paymentKeyHash)
59+
60+
// Calculate policy ID from script hash using core module
61+
const scriptHash = ScriptHash.fromScript(nativeScript)
62+
policyId = ScriptHash.toHex(scriptHash)
63+
64+
genesisConfig = {
65+
...Config.DEFAULT_SHELLEY_GENESIS,
66+
slotLength: 0.02,
67+
epochLength: 50,
68+
activeSlotsCoeff: 1.0,
69+
initialFunds: { [testAddressHex]: 900_000_000_000 }
70+
}
71+
72+
// Pre-calculate genesis UTxOs (same pattern as Client.Devnet.test.ts)
73+
genesisUtxos = await Genesis.calculateUtxosFromConfig(genesisConfig)
74+
75+
devnetCluster = await Cluster.make({
76+
clusterName: "client-minting-test",
77+
ports: { node: 6001, submit: 9002 },
78+
shelleyGenesis: genesisConfig,
79+
kupo: { enabled: true, port: 1443, logLevel: "Info" },
80+
ogmios: { enabled: true, port: 1338, logLevel: "info" }
81+
})
82+
83+
await Cluster.start(devnetCluster)
84+
await new Promise((resolve) => setTimeout(resolve, 3_000))
85+
}, 180_000)
86+
87+
afterAll(async () => {
88+
if (devnetCluster) {
89+
await Cluster.stop(devnetCluster)
90+
await Cluster.remove(devnetCluster)
91+
}
92+
}, 60_000)
93+
94+
// ============================================================================
95+
// Submit Tests
96+
// ============================================================================
97+
98+
it("should mint, submit and find asset in UTxO", { timeout: 30_000 }, async () => {
99+
if (genesisUtxos.length === 0) {
100+
throw new Error("Genesis UTxOs not calculated")
101+
}
102+
103+
const client = createTestClient()
104+
const address = await client.address()
105+
106+
// Use pre-calculated genesis UTxOs (Kupo may not have synced yet)
107+
const genesisUtxo = genesisUtxos.find((u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(address))
108+
if (!genesisUtxo) {
109+
throw new Error("Genesis UTxO not found for wallet address")
110+
}
111+
112+
const assetNameHex = Text.toHex("IntegrationToken")
113+
const unit = policyId + assetNameHex
114+
115+
// Build, sign, and submit transaction with minting using native script
116+
const signBuilder = await client
117+
.newTx()
118+
.attachScript({ script: nativeScript })
119+
.mintAssets({
120+
assets: CoreAssets.fromRecord({ [unit]: 5000n })
121+
})
122+
.payToAddress({
123+
address,
124+
assets: CoreAssets.fromRecord({
125+
lovelace: 3_000_000n,
126+
[unit]: 5000n
127+
})
128+
})
129+
.build({ availableUtxos: [genesisUtxo] })
130+
131+
const tx = await signBuilder.toTransaction()
132+
expect(tx.body.mint).toBeDefined()
133+
134+
const submitBuilder = await signBuilder.sign()
135+
const txHash = await submitBuilder.submit()
136+
expect(txHash.length).toBe(64)
137+
138+
const confirmed = await client.awaitTx(txHash, 1000)
139+
expect(confirmed).toBe(true)
140+
141+
// Query wallet UTxOs and verify minted asset
142+
const utxos = await client.getWalletUtxos()
143+
let foundMintedAsset = false
144+
let mintedAmount = 0n
145+
146+
for (const utxo of utxos) {
147+
if (!utxo.assets.multiAsset) continue
148+
149+
for (const [policyIdKey, assetMap] of utxo.assets.multiAsset.map.entries()) {
150+
if (PolicyId.toHex(policyIdKey) === policyId) {
151+
for (const [assetName, amount] of assetMap.entries()) {
152+
if (AssetName.toHex(assetName) === assetNameHex) {
153+
foundMintedAsset = true
154+
mintedAmount = amount
155+
}
156+
}
157+
}
158+
}
159+
}
160+
161+
expect(foundMintedAsset).toBe(true)
162+
expect(mintedAmount).toBe(5000n)
163+
})
164+
165+
it("should handle burning (negative amounts) with submit", { timeout: 60_000 }, async () => {
166+
if (genesisUtxos.length === 0) {
167+
throw new Error("Genesis UTxOs not calculated")
168+
}
169+
170+
const client = createTestClient()
171+
const address = await client.address()
172+
173+
const assetNameHex = Text.toHex(ASSET_NAME)
174+
const unit = policyId + assetNameHex
175+
176+
// Use pre-calculated genesis UTxOs or wallet UTxOs (from prior test)
177+
let availableUtxos = await client.getWalletUtxos()
178+
if (availableUtxos.length === 0) {
179+
// Fall back to genesis UTxOs if Kupo hasn't synced
180+
const genesisUtxo = genesisUtxos.find((u) => CoreAddress.toBech32(u.address) === CoreAddress.toBech32(address))
181+
if (!genesisUtxo) {
182+
throw new Error("Genesis UTxO not found for wallet address")
183+
}
184+
availableUtxos = [genesisUtxo]
185+
}
186+
187+
// Step 1: First mint tokens
188+
const mintBuilder = await client
189+
.newTx()
190+
.attachScript({ script: nativeScript })
191+
.mintAssets({
192+
assets: CoreAssets.fromRecord({ [unit]: 1000n })
193+
})
194+
.payToAddress({
195+
address,
196+
assets: CoreAssets.fromRecord({
197+
lovelace: 3_000_000n,
198+
[unit]: 1000n
199+
})
200+
})
201+
.build({ availableUtxos })
202+
203+
const mintTx = await mintBuilder.toTransaction()
204+
expect(mintTx.body.mint).toBeDefined()
205+
206+
// Submit and wait for confirmation
207+
const mintSubmitBuilder = await mintBuilder.sign()
208+
const mintTxHash = await mintSubmitBuilder.submit()
209+
const mintConfirmed = await client.awaitTx(mintTxHash, 1000)
210+
expect(mintConfirmed).toBe(true)
211+
212+
// Step 2: Get the UTxO with minted tokens
213+
const utxos = await client.getWalletUtxos()
214+
const utxoWithTokens = utxos.find((u) => {
215+
const hasToken = CoreAssets.getByUnit(u.assets, unit) > 0n
216+
return hasToken
217+
})
218+
219+
if (!utxoWithTokens) {
220+
throw new Error("UTxO with minted tokens not found")
221+
}
222+
223+
// Step 3: Now burn some of those tokens
224+
const burnBuilder = await client
225+
.newTx()
226+
.attachScript({ script: nativeScript })
227+
.collectFrom({ inputs: [utxoWithTokens] })
228+
.mintAssets({
229+
assets: CoreAssets.fromRecord({ [unit]: -500n })
230+
})
231+
.payToAddress({
232+
address,
233+
assets: CoreAssets.fromRecord({
234+
lovelace: 1_500_000n,
235+
[unit]: 500n
236+
})
237+
})
238+
.build({ availableUtxos: [] })
239+
240+
const burnTx = await burnBuilder.toTransaction()
241+
expect(burnTx.body.mint).toBeDefined()
242+
243+
// Verify the mint shows negative (burning)
244+
const mint = burnTx.body.mint!
245+
let foundBurn = false
246+
for (const [policyIdKey, assetMap] of mint.map.entries()) {
247+
if (PolicyId.toHex(policyIdKey) === policyId) {
248+
for (const [assetName, amount] of assetMap.entries()) {
249+
if (AssetName.toHex(assetName) === assetNameHex && amount === -500n) {
250+
foundBurn = true
251+
}
252+
}
253+
}
254+
}
255+
expect(foundBurn).toBe(true)
256+
257+
// Submit burn transaction and verify
258+
const burnSubmitBuilder = await burnBuilder.sign()
259+
const burnTxHash = await burnSubmitBuilder.submit()
260+
expect(burnTxHash.length).toBe(64)
261+
262+
const burnConfirmed = await client.awaitTx(burnTxHash, 1000)
263+
expect(burnConfirmed).toBe(true)
264+
265+
// Verify the remaining tokens in wallet
266+
const utxosAfterBurn = await client.getWalletUtxos()
267+
let remainingTokenAmount = 0n
268+
for (const utxo of utxosAfterBurn) {
269+
remainingTokenAmount += CoreAssets.getByUnit(utxo.assets, unit)
270+
}
271+
expect(remainingTokenAmount).toBe(500n)
272+
})
273+
})

0 commit comments

Comments
 (0)