Skip to content

Commit 558bca5

Browse files
Alex6323Thoralf-Mthibault-martinez
authored
chore(examples): add TransactionBuilder publish and upgrade example (#271)
* getting into the mess * publish and upgrade * fixes * clean up * more clean up * commit upgrade * make it work * bindings publish_upgrade examples * let user compile first * add UpgradePolicy type * update binding libs * update bindings * add .vscode to gitignore * docs * review * review * improved docs? * exit example on error Co-authored-by: Thibault Martinez <[email protected]> * use MovePackageData::from_json in bindings * align upgrade arg order * fix tests * revert blank * nit * move json in python * reorder part2 --------- Co-authored-by: Thoralf Müller <[email protected]> Co-authored-by: Thibault Martinez <[email protected]>
1 parent 9a51813 commit 558bca5

File tree

16 files changed

+1423
-46
lines changed

16 files changed

+1423
-46
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Cargo.lock
66
*.dll
77
__pycache__
88
build
9+
.vscode
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// This example allows you to publish any Move package by compiling it
5+
// first using the `iota` binary. For demonstration purposes this example
6+
// immediately upgrades the package after publishing it.
7+
//
8+
// bash:
9+
// cd /path/to/your/move/package
10+
// export COMPILED_PACKAGE=$(iota move build --dump-bytecode-as-base64)
11+
//
12+
// fish:
13+
// cd /path/to/your/move/package
14+
// set -x COMPILED_PACKAGE (iota move build --dump-bytecode-as-base64)
15+
//
16+
// With this example it is necessary to run a localnet:
17+
//
18+
// iota start --with-faucet --with-graphql --committee-size 1 --force-regenesis
19+
20+
package main
21+
22+
import (
23+
"fmt"
24+
"log"
25+
"os"
26+
"time"
27+
28+
sdk "bindings/iota_sdk_ffi"
29+
)
30+
31+
func main() {
32+
// Read and parse the compiled package, or use the default package
33+
packageDataString := os.Getenv("COMPILED_PACKAGE")
34+
if packageDataString == "" {
35+
fmt.Println("No compiled package found in env var. Using default.")
36+
packageDataString = PRECOMPILED_PACKAGE
37+
} else {
38+
fmt.Println("Using custom Move package found in env var.")
39+
}
40+
41+
packageData, err := sdk.MovePackageDataFromJson(packageDataString)
42+
if err != nil {
43+
log.Fatalf("Failed to deserialize Move package data: %v", err)
44+
}
45+
modules := packageData.Modules()
46+
fmt.Printf("Modules: %d\n", len(modules))
47+
dependencies := packageData.Dependencies()
48+
fmt.Printf("Dependencies: %d\n", len(dependencies))
49+
digest := packageData.Digest()
50+
fmt.Printf("Digest: %s\n", digest.ToBase58())
51+
52+
// Create a random private key to derive a sender address and for signing
53+
privateKey := sdk.Ed25519PrivateKeyGenerate()
54+
publicKey := privateKey.PublicKey()
55+
sender := publicKey.DeriveAddress()
56+
fmt.Printf("Sender: %s\n", sender.ToHex())
57+
58+
// Fund the sender address for gas payment
59+
faucet := sdk.FaucetClientNewLocalnet()
60+
faucetReceipt, err := faucet.RequestAndWait(sender)
61+
if err.(*sdk.SdkFfiError) != nil {
62+
log.Fatalf("Failed to request coins from faucet: %v", err)
63+
}
64+
totalBalance := uint64(0)
65+
for _, coin := range faucetReceipt.Sent {
66+
totalBalance += coin.Amount
67+
}
68+
69+
client := sdk.GraphQlClientNewLocalnet()
70+
71+
// Build the `publish` PTB
72+
builderPublish := sdk.TransactionBuilderInit(sender, client)
73+
// Publish the package and receive the upgrade cap in return
74+
builderPublish.Publish(packageData, "upgrade_cap")
75+
// Transfer the upgrade cap to the sender address
76+
builderPublish.TransferObjects(sender, []*sdk.PtbArgument{sdk.PtbArgumentRes("upgrade_cap")})
77+
txPublish, err := builderPublish.Finish()
78+
if err.(*sdk.SdkFfiError) != nil {
79+
log.Fatalf("Failed to finish transaction: %v", err)
80+
}
81+
82+
// Perform a dry-run first to check if everything is correct
83+
fmt.Println("> Publishing package (dry run):")
84+
resultPublish, err := client.DryRunTx(txPublish, false)
85+
if err.(*sdk.SdkFfiError) != nil {
86+
log.Fatalf("Dry run failed: %v", err)
87+
}
88+
if resultPublish.Error != nil {
89+
log.Fatalf("Dry run failed: %v", *resultPublish.Error)
90+
}
91+
if resultPublish.Effects == nil {
92+
log.Fatal("Dry run failed: no effects")
93+
}
94+
fmt.Println("Success")
95+
96+
// Sign and execute the transaction (publish the package)
97+
fmt.Println("> Publishing package:")
98+
sigPublish, err := privateKey.TrySignSimple(txPublish.SigningDigest())
99+
if err != nil {
100+
log.Fatalf("Failed to sign: %v", err)
101+
}
102+
userSigPublish := sdk.UserSignatureNewSimple(sigPublish)
103+
effectsPublish, err := client.ExecuteTx([]*sdk.UserSignature{userSigPublish}, txPublish)
104+
if err.(*sdk.SdkFfiError) != nil {
105+
log.Fatalf("Transaction failed: %v", err)
106+
}
107+
if effectsPublish == nil {
108+
log.Fatal("Transaction failed: no effects")
109+
}
110+
fmt.Println("Success")
111+
112+
// Wait some time for the indexer to process the tx
113+
time.Sleep(3 * time.Second)
114+
115+
// Resolve UpgradeCap and PackageId via the client
116+
var upgradeCap *sdk.ObjectId
117+
var packageId *sdk.ObjectId
118+
for _, changedObj := range (*effectsPublish).AsV1().ChangedObjects {
119+
if objectWrite, ok := changedObj.OutputState.(sdk.ObjectOutObjectWrite); ok {
120+
objectId := changedObj.ObjectId
121+
objPtr, err := client.Object(objectId, nil)
122+
if err.(*sdk.SdkFfiError) != nil {
123+
log.Fatalf("Failed to get object: %v", err)
124+
}
125+
obj := *objPtr
126+
if obj.AsStructOpt() != nil {
127+
structType := obj.AsStruct().StructType
128+
packageIdent, _ := sdk.NewIdentifier("package")
129+
upgradeCapIdent, _ := sdk.NewIdentifier("UpgradeCap")
130+
upgradeCapType := sdk.NewStructTag(sdk.AddressFramework(), packageIdent, upgradeCapIdent, []*sdk.TypeTag{})
131+
if structType.Eq(upgradeCapType) {
132+
fmt.Printf("UpgradeCap: %s\n", objectId.ToHex())
133+
fmt.Printf("UpgradeCapOwner: %s\n", objectWrite.Owner.AsAddress().ToHex())
134+
upgradeCap = objectId
135+
}
136+
}
137+
} else if _, ok := changedObj.OutputState.(sdk.ObjectOutPackageWrite); ok {
138+
pkgId := changedObj.ObjectId
139+
fmt.Printf("Package ID: %s\n", pkgId.ToHex())
140+
version := changedObj.OutputState.(sdk.ObjectOutPackageWrite).Version
141+
fmt.Printf("Package version: %d\n", version)
142+
packageId = pkgId
143+
}
144+
}
145+
if upgradeCap == nil {
146+
log.Fatal("Missing upgrade cap")
147+
}
148+
if packageId == nil {
149+
log.Fatal("Missing package id")
150+
}
151+
152+
// Build the `upgrade` PTB
153+
builderUpgrade := sdk.TransactionBuilderInit(sender, client)
154+
155+
// Authorize the upgrade by providing the upgrade cap object id to receive an upgrade
156+
// ticket
157+
upgradeTicketName := "upgrade_ticket"
158+
packageIdent, _ := sdk.NewIdentifier("package")
159+
authorizeUpgrade, _ := sdk.NewIdentifier("authorize_upgrade")
160+
upgradeCapArg := sdk.PtbArgumentObjectId(upgradeCap)
161+
upgradePolicy := sdk.PtbArgumentU8(sdk.UpgradePolicyCompatible().AsU8())
162+
builderUpgrade.MoveCall(
163+
sdk.AddressFramework(),
164+
packageIdent,
165+
authorizeUpgrade,
166+
[]*sdk.PtbArgument{upgradeCapArg, upgradePolicy, sdk.PtbArgumentU8Vec(digest.ToBytes())},
167+
nil,
168+
[]string{upgradeTicketName},
169+
)
170+
171+
// Upgrade the package to receive an upgrade receipt
172+
upgradeReceiptName := "upgrade_receipt"
173+
builderUpgrade.Upgrade(packageId, packageData, sdk.PtbArgumentRes(upgradeTicketName), &upgradeReceiptName)
174+
175+
// Commit the upgrade using the receipt
176+
commitUpgrade, _ := sdk.NewIdentifier("commit_upgrade")
177+
builderUpgrade.MoveCall(
178+
sdk.AddressFramework(),
179+
packageIdent,
180+
commitUpgrade,
181+
[]*sdk.PtbArgument{upgradeCapArg, sdk.PtbArgumentRes(upgradeReceiptName)},
182+
nil,
183+
nil,
184+
)
185+
186+
txUpgrade, err := builderUpgrade.Finish()
187+
if err.(*sdk.SdkFfiError) != nil {
188+
log.Fatalf("Failed to finish transaction: %v", err)
189+
}
190+
191+
// Perform a dry-run first to check if everything is correct
192+
fmt.Println("> Upgrading package (dry run):")
193+
resultUpgrade, err := client.DryRunTx(txUpgrade, false)
194+
if err.(*sdk.SdkFfiError) != nil {
195+
log.Fatalf("Dry run failed: %v", err)
196+
}
197+
if resultUpgrade.Error != nil {
198+
log.Fatalf("Dry run failed: %v", *resultUpgrade.Error)
199+
}
200+
if resultUpgrade.Effects == nil {
201+
log.Fatal("Dry run failed: no effects")
202+
}
203+
fmt.Println("Success")
204+
205+
// Sign and execute the transaction (upgrade the package)
206+
fmt.Println("> Upgrading package:")
207+
sigUpgrade, err := privateKey.TrySignSimple(txUpgrade.SigningDigest())
208+
if err != nil {
209+
log.Fatalf("Failed to sign: %v", err)
210+
}
211+
userSigUpgrade := sdk.UserSignatureNewSimple(sigUpgrade)
212+
effectsUpgrade, err := client.ExecuteTx([]*sdk.UserSignature{userSigUpgrade}, txUpgrade)
213+
if err.(*sdk.SdkFfiError) != nil {
214+
log.Fatalf("Transaction failed: %v", err)
215+
}
216+
if effectsUpgrade == nil {
217+
log.Fatal("Transaction failed: no effects")
218+
}
219+
fmt.Println("Success")
220+
221+
// Wait some time for the indexer to process the tx
222+
time.Sleep(3 * time.Second)
223+
224+
// Print the new package version (should now be 2)
225+
for _, changedObj := range (*effectsUpgrade).AsV1().ChangedObjects {
226+
if _, ok := changedObj.OutputState.(sdk.ObjectOutPackageWrite); ok {
227+
pkgId := changedObj.ObjectId
228+
fmt.Printf("New Package ID: %s\n", pkgId.ToHex())
229+
version := changedObj.OutputState.(sdk.ObjectOutPackageWrite).Version
230+
fmt.Printf("New Package version: %d\n", version)
231+
}
232+
}
233+
}
234+
235+
// Pre-compiled `first_package` example
236+
const PRECOMPILED_PACKAGE = `{"modules":["oRzrCwYAAAAKAQAIAggUAxw+BFoGBWBBB6EBwQEI4gJACqIDGgy8A5cBDdMEBgAKAQ0BEwEUAAIMAAABCAAAAAgAAQQEAAMDAgAACAABAAAJAgMAABACAwAAEgQDAAAMBQYAAAYHAQAAEQgBAAAFCQoAAQsACwACDg8BAQwCEw8BAQgDDwwNAAoOCgYJBgEHCAQAAQYIAAEDAQYIAQQHCAEDAwcIBAEIAAQDAwUHCAQDCAAFBwgEAgMHCAQBCAIBCAMBBggEAQUBCAECCQAFBkNvbmZpZwVGb3JnZQVTd29yZAlUeENvbnRleHQDVUlEDWNyZWF0ZV9jb25maWcMY3JlYXRlX3N3b3JkAmlkBGluaXQFbWFnaWMJbXlfbW9kdWxlA25ldwluZXdfc3dvcmQGb2JqZWN0D3B1YmxpY190cmFuc2ZlcgZzZW5kZXIIc3RyZW5ndGgOc3dvcmRfdHJhbnNmZXIOc3dvcmRzX2NyZWF0ZWQIdHJhbnNmZXIKdHhfY29udGV4dAV2YWx1ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgMHCAMJAxADAQICBwgDEgMCAgIHCAMVAwAAAAABCQoAEQgGAAAAAAAAAAASAQsALhELOAACAQEAAAEECwAQABQCAgEAAAEECwAQARQCAwEAAAEECwAQAhQCBAEAAAEOCgAQAhQGAQAAAAAAAAAWCwAPAhULAxEICwELAhIAAgUBAAABCAsDEQgLAAsBEgALAjgBAgYBAAABBAsACwE4AgIHAQAAAQULAREICwASAgIAAQACAQEA"],"dependencies":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000001"],"digest":[246,127,102,77,186,19,68,12,161,181,56,248,210,0,91,211,245,251,165,152,0,197,250,135,171,37,177,240,133,76,122,124]}`

0 commit comments

Comments
 (0)