@meddleware/token-deployer-ui 0.0.16 → 0.0.17

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.
@@ -1,182 +0,0 @@
1
- // PTB builders for deploying a coin.
2
- //
3
- // Two transactions, because the OTW `init()` creates the TreasuryCap/MetadataCap
4
- // and sends them to the sender — they are NOT results of `tx.publish`, so they
5
- // can only be used after the publish executes:
6
- //
7
- // 1. buildPublishTransaction — publish the patched module, take the trivial
8
- // fee, and apply the UpgradeCap policy (make immutable, or keep). The
9
- // TreasuryCap + MetadataCap land with the sender automatically.
10
- // 2. buildFinalizeTransaction — (optional) mint the initial supply, apply the
11
- // supply + metadata policies, promote the currency in the registry, and move
12
- // the caps/supply to the recipient. Only needed when any of those differ
13
- // from the defaults.
14
-
15
- import { Transaction } from '@mysten/sui/transactions'
16
- import { toBase64 } from '@mysten/sui/utils'
17
- import type { Network, PublishResult, TokenConfig } from './types.js'
18
-
19
- const SUI_FRAMEWORK = '0x2'
20
- const MOVE_STDLIB = '0x1'
21
-
22
- export interface BuildPublishArgs {
23
- moduleBytes: Uint8Array
24
- sender: string
25
- feeMist: bigint
26
- feeRecipient: string
27
- gasBudget: bigint
28
- packagePolicy: TokenConfig['packagePolicy']
29
- }
30
-
31
- /**
32
- * Publish PTB: publish -> (make_immutable | transfer UpgradeCap) + fee split.
33
- * The user signs this; they pay gas and the fee.
34
- */
35
- export function buildPublishTransaction(args: BuildPublishArgs): Transaction {
36
- const tx = new Transaction()
37
- tx.setSender(args.sender)
38
-
39
- const [upgradeCap] = tx.publish({
40
- modules: [toBase64(args.moduleBytes)],
41
- dependencies: [MOVE_STDLIB, SUI_FRAMEWORK],
42
- })
43
-
44
- if (args.packagePolicy === 'immutable') {
45
- tx.moveCall({ target: `${SUI_FRAMEWORK}::package::make_immutable`, arguments: [upgradeCap] })
46
- } else {
47
- tx.transferObjects([upgradeCap], args.sender)
48
- }
49
-
50
- // Trivial fee: split from the gas coin into the operator treasury.
51
- if (args.feeMist > 0n) {
52
- const [fee] = tx.splitCoins(tx.gas, [args.feeMist])
53
- tx.transferObjects([fee], args.feeRecipient)
54
- }
55
-
56
- tx.setGasBudget(args.gasBudget)
57
- return tx
58
- }
59
-
60
- export interface BuildFinalizeArgs {
61
- config: TokenConfig
62
- coinType: string
63
- treasuryCapId: string
64
- metadataCapId: string
65
- /**
66
- * Full object reference for the pending Currency<T> object (at address 0xc).
67
- * When provided, `finalize_registration` is called first in the PTB, promoting the
68
- * pending Currency to a shared, RPC-discoverable object so wallets can read the
69
- * coin's decimal metadata via suix_getCoinMetadata.
70
- */
71
- currencyRef?: { objectId: string; version: string; digest: string }
72
- sender: string
73
- gasBudget: bigint
74
- }
75
-
76
- /**
77
- * Finalize PTB: register the coin in the registry, mint initial supply, apply
78
- * supply/metadata policy, and route caps to the recipient.
79
- * Built after the publish effects reveal the cap object IDs.
80
- */
81
- export function buildFinalizeTransaction(args: BuildFinalizeArgs): Transaction {
82
- const { config, coinType } = args
83
- const tx = new Transaction()
84
- tx.setSender(args.sender)
85
- const recipient = config.recipient || args.sender
86
-
87
- // Always call finalize_registration when we have the full object ref for the pending
88
- // Currency<T>. This promotes it from a pending owned object (at @0xc) to a shared,
89
- // RPC-discoverable object so wallets can read decimals via suix_getCoinMetadata.
90
- // Without this step wallets fall back to 0 decimals and display raw base-unit counts.
91
- if (args.currencyRef) {
92
- tx.moveCall({
93
- target: `${SUI_FRAMEWORK}::coin_registry::finalize_registration`,
94
- typeArguments: [coinType],
95
- arguments: [
96
- tx.object('0xc'), // &mut CoinRegistry (shared object at well-known address)
97
- tx.receivingRef(args.currencyRef), // Receiving<Currency<T>> — pending object at @0xc
98
- ],
99
- })
100
- }
101
-
102
- // Mint the initial supply (scaled by decimals) to the recipient.
103
- if (config.initialSupply > 0n) {
104
- const amount = config.initialSupply * 10n ** BigInt(config.decimals)
105
- const minted = tx.moveCall({
106
- target: `${SUI_FRAMEWORK}::coin::mint`,
107
- typeArguments: [coinType],
108
- arguments: [tx.object(args.treasuryCapId), tx.pure.u64(amount)],
109
- })
110
- tx.transferObjects([minted], recipient)
111
- }
112
-
113
- // Supply policy: fixed => freeze the TreasuryCap (no further minting possible);
114
- // mintable => keep it with the recipient.
115
- if (config.supplyPolicy === 'fixed') {
116
- tx.moveCall({
117
- target: `${SUI_FRAMEWORK}::transfer::public_freeze_object`,
118
- typeArguments: [`${SUI_FRAMEWORK}::coin::TreasuryCap<${coinType}>`],
119
- arguments: [tx.object(args.treasuryCapId)],
120
- })
121
- } else if (recipient.toLowerCase() !== args.sender.toLowerCase()) {
122
- tx.transferObjects([tx.object(args.treasuryCapId)], recipient)
123
- }
124
-
125
- // Metadata policy: frozen => freeze the MetadataCap (no further metadata edits);
126
- // updatable => keep it with the recipient.
127
- if (config.metadataPolicy === 'frozen') {
128
- tx.moveCall({
129
- target: `${SUI_FRAMEWORK}::transfer::public_freeze_object`,
130
- typeArguments: [`${SUI_FRAMEWORK}::coin_registry::MetadataCap<${coinType}>`],
131
- arguments: [tx.object(args.metadataCapId)],
132
- })
133
- } else if (recipient.toLowerCase() !== args.sender.toLowerCase()) {
134
- tx.transferObjects([tx.object(args.metadataCapId)], recipient)
135
- }
136
-
137
- tx.setGasBudget(args.gasBudget)
138
- return tx
139
- }
140
-
141
- interface ObjectChange {
142
- type: string
143
- objectType?: string
144
- objectId?: string
145
- packageId?: string
146
- /** Version and digest are present for created/mutated objects in the full RPC response. */
147
- version?: string
148
- digest?: string
149
- }
150
-
151
- /** Pull package id, coin type and cap ids out of publish `objectChanges`. */
152
- export function extractPublishResult(
153
- objectChanges: ObjectChange[],
154
- ctx: { network: Network; digest: string; feeRecipient: string; feeMist: bigint },
155
- ): PublishResult {
156
- const published = objectChanges.find((c) => c.type === 'published')
157
- const packageId = published?.packageId ?? ''
158
-
159
- const createdOfType = (needle: string) =>
160
- objectChanges.find((c) => c.type === 'created' && (c.objectType ?? '').includes(needle))
161
-
162
- const treasury = createdOfType('coin::TreasuryCap')
163
- const coinTypeMatch = treasury?.objectType?.match(/TreasuryCap<(.+)>/)
164
- const coinType = coinTypeMatch?.[1] ?? ''
165
-
166
- const currency = createdOfType('coin_registry::Currency')
167
-
168
- return {
169
- network: ctx.network,
170
- packageId,
171
- coinType,
172
- treasuryCapId: treasury?.objectId,
173
- metadataCapId: createdOfType('coin_registry::MetadataCap')?.objectId,
174
- currencyId: currency?.objectId,
175
- currencyVersion: currency?.version,
176
- currencyDigest: currency?.digest,
177
- upgradeCapId: createdOfType('package::UpgradeCap')?.objectId,
178
- digest: ctx.digest,
179
- feeRecipient: ctx.feeRecipient,
180
- feeMist: ctx.feeMist.toString(),
181
- }
182
- }
package/src/lib/deploy.ts DELETED
@@ -1,210 +0,0 @@
1
- // Deployment orchestration: patch -> publish -> wait -> (finalize) -> result.
2
- // Decoupled from the wallet via an `Executor` so it is unit-testable.
3
-
4
- import type { Transaction } from '@mysten/sui/transactions'
5
- import type { SuiClientTypes } from '@mysten/sui/client'
6
- import { patchTemplateModule } from './template.js'
7
- import {
8
- buildPublishTransaction,
9
- buildFinalizeTransaction,
10
- extractPublishResult,
11
- } from './buildPublishTx.js'
12
- import { deriveStructName } from './validation.js'
13
- import type { Network, PublishResult, TokenConfig } from './types.js'
14
-
15
- export interface SuiTxResult {
16
- digest: string
17
- objectChanges?: {
18
- type: string
19
- objectType?: string
20
- objectId?: string
21
- packageId?: string
22
- /** Present for created/mutated objects in the full Sui RPC response. */
23
- version?: string
24
- digest?: string
25
- }[]
26
- effects?: { status?: { status?: string; error?: string } }
27
- }
28
-
29
- /** The core-API execution result {@link toSuiTxResult} consumes (effects + object types). */
30
- export type CoreExecutionResult = SuiClientTypes.TransactionResult<{
31
- effects: true
32
- objectTypes: true
33
- }>
34
-
35
- /**
36
- * Map a core-API (gRPC) execution result onto {@link SuiTxResult}: created/mutated objects
37
- * come from `effects.changedObjects` (types from the `objectTypes` map), and a newly written
38
- * package becomes the `published` entry. Failed transactions are returned with a failure
39
- * status (not thrown) so {@link deployToken}'s `assertSuccess` reports them uniformly.
40
- */
41
- export function toSuiTxResult(res: CoreExecutionResult): SuiTxResult {
42
- const tx = res.Transaction ?? res.FailedTransaction
43
- const types = tx.objectTypes ?? {}
44
- const objectChanges: NonNullable<SuiTxResult['objectChanges']> = []
45
- for (const c of tx.effects?.changedObjects ?? []) {
46
- if (c.outputState === 'PackageWrite') {
47
- if (c.idOperation === 'Created') objectChanges.push({ type: 'published', packageId: c.objectId })
48
- } else if (c.outputState === 'ObjectWrite') {
49
- objectChanges.push({
50
- type: c.idOperation === 'Created' ? 'created' : 'mutated',
51
- objectId: c.objectId,
52
- objectType: types[c.objectId],
53
- version: c.outputVersion ?? undefined,
54
- digest: c.outputDigest ?? undefined,
55
- })
56
- }
57
- }
58
- const status = tx.status.success
59
- ? { status: 'success' }
60
- : { status: 'failure', error: tx.status.error.message }
61
- return { digest: tx.digest, objectChanges, effects: { status } }
62
- }
63
-
64
- export interface Executor {
65
- /** Sign with the wallet and execute; must request object changes + effects. */
66
- signAndExecute(tx: Transaction): Promise<SuiTxResult>
67
- /** Block until the digest is indexed (so new objects resolve). */
68
- waitForTransaction(digest: string): Promise<void>
69
- }
70
-
71
- export type DeployStep =
72
- | 'patching'
73
- | 'publishing'
74
- | 'confirming'
75
- | 'finalizing'
76
- | 'confirming-finalize'
77
- | 'done'
78
-
79
- export interface DeployArgs {
80
- config: TokenConfig
81
- network: Network
82
- sender: string
83
- feeMist: bigint
84
- feeTreasury: string
85
- gasBudget: bigint
86
- executor: Executor
87
- onStep?: (step: DeployStep) => void
88
- }
89
-
90
- /**
91
- * Only an explicit `success` effects status counts: a missing status (an executor that did not
92
- * return effects) is treated as failure, never as success.
93
- */
94
- function assertSuccess(res: SuiTxResult, what: string): void {
95
- const status = res.effects?.status?.status
96
- if (status !== 'success') {
97
- throw new Error(
98
- `${what} failed: ${res.effects?.status?.error ?? status ?? 'no effects status returned'}`,
99
- )
100
- }
101
- }
102
-
103
- /**
104
- * Deploy a token end-to-end. Returns the publish result once any required
105
- * finalize transaction has also confirmed.
106
- */
107
- export async function deployToken(args: DeployArgs): Promise<PublishResult> {
108
- const { config, executor, onStep } = args
109
- const structName = deriveStructName(config.moduleName)
110
- const normalizedConfig: TokenConfig = { ...config, structName }
111
- const recipient = normalizedConfig.recipient || args.sender
112
- const isE2e = import.meta.env.MODE === 'e2e' && import.meta.env.VITE_E2E === '1'
113
-
114
- onStep?.('patching')
115
- const moduleBytes = await patchTemplateModule({
116
- moduleName: normalizedConfig.moduleName,
117
- structName,
118
- symbol: normalizedConfig.symbol,
119
- name: normalizedConfig.name,
120
- description: normalizedConfig.description,
121
- iconUrl: normalizedConfig.iconUrl,
122
- decimals: normalizedConfig.decimals,
123
- })
124
-
125
- onStep?.('publishing')
126
- const publishTx = buildPublishTransaction({
127
- moduleBytes,
128
- sender: args.sender,
129
- feeMist: args.feeMist,
130
- feeRecipient: args.feeTreasury,
131
- gasBudget: args.gasBudget,
132
- packagePolicy: normalizedConfig.packagePolicy,
133
- })
134
- const pub = await (async () => {
135
- try {
136
- const result = await executor.signAndExecute(publishTx)
137
- if (isE2e) console.log('[E2E Deploy]', 'Publish TX result:', result)
138
- assertSuccess(result, 'Publish')
139
-
140
- onStep?.('confirming')
141
- try {
142
- if (isE2e) console.log('[E2E Deploy]', 'Waiting for transaction digest:', result.digest)
143
- await executor.waitForTransaction(result.digest)
144
- if (isE2e) console.log('[E2E Deploy]', 'Transaction confirmed')
145
- } catch (e) {
146
- if (isE2e) console.error('[E2E Deploy]', 'waitForTransaction error:', e)
147
- throw e
148
- }
149
-
150
- return result
151
- } catch (e) {
152
- if (isE2e) console.error('[E2E Deploy]', 'Publish TX error:', e)
153
- throw e
154
- }
155
- })()
156
-
157
- const result = extractPublishResult(pub.objectChanges ?? [], {
158
- network: args.network,
159
- digest: pub.digest,
160
- feeRecipient: args.feeTreasury,
161
- feeMist: args.feeMist,
162
- })
163
-
164
- // A successful publish must surface the package id + coin type in its object
165
- // changes; if not, fail loudly rather than returning an empty result panel.
166
- if (!result.packageId || !result.coinType) {
167
- throw new Error(
168
- 'Publish succeeded but the package id / coin type could not be read from the transaction effects.',
169
- )
170
- }
171
-
172
- // Build the currency receiving ref for finalize_registration (requires version + digest
173
- // from the publish objectChanges, present in production Sui RPC responses).
174
- const currencyChange = (pub.objectChanges ?? []).find(
175
- (c) => c.type === 'created' && (c.objectType ?? '').includes('coin_registry::Currency'),
176
- )
177
- const currencyRef =
178
- currencyChange?.objectId && currencyChange?.version && currencyChange?.digest
179
- ? {
180
- objectId: currencyChange.objectId,
181
- version: currencyChange.version,
182
- digest: currencyChange.digest,
183
- }
184
- : undefined
185
-
186
- // Always finalize: the finalize PTB calls coin_registry::finalize_registration,
187
- // which is required to promote the pending Currency<T> to a shared, wallet-discoverable
188
- // object. Without it, suix_getCoinMetadata returns null and wallets fall back to
189
- // 0 decimals, displaying raw base-unit counts instead of whole-token amounts.
190
- if (!result.treasuryCapId || !result.metadataCapId) {
191
- throw new Error('Published, but could not locate the TreasuryCap/MetadataCap to finalize.')
192
- }
193
- onStep?.('finalizing')
194
- const finalizeTx = buildFinalizeTransaction({
195
- config: { ...normalizedConfig, recipient },
196
- coinType: result.coinType,
197
- treasuryCapId: result.treasuryCapId,
198
- metadataCapId: result.metadataCapId,
199
- currencyRef,
200
- sender: args.sender,
201
- gasBudget: args.gasBudget,
202
- })
203
- const fin = await executor.signAndExecute(finalizeTx)
204
- assertSuccess(fin, 'Finalize')
205
- onStep?.('confirming-finalize')
206
- await executor.waitForTransaction(fin.digest)
207
-
208
- onStep?.('done')
209
- return result
210
- }