Skip to content

Commit d17c441

Browse files
committed
feat: Return ObjectChange in ExecuteTx and DryRun
In the previous JSON RPC, iota_executeTransactionBlock and dry run would return object changes and balance changes. However, thee fields are not returned in the current GraphQL sdk.
1 parent e81f8a4 commit d17c441

File tree

12 files changed

+401
-18
lines changed

12 files changed

+401
-18
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"log"
8+
9+
sdk "bindings/iota_sdk_ffi"
10+
)
11+
12+
func main() {
13+
client := sdk.GraphQlClientNewTestnet()
14+
15+
// some fix values on Testnet
16+
privateKeyHex := "0032e67709569b6a1ef551ea7a745aac943b2158fc862ae7c6d08dd1744e15d818"
17+
senderAddressHex := "0x786dff8a4ee13d45b502c8f22f398e3517e6ec78aa4ae564c348acb07fad7f50"
18+
coinObjectIdHex := "0x7714345721694a20c6e2e04a6a289b1c08710cda863f7dd49b4661cde524103d"
19+
gasObjectIdHex := "0xd7913b6c9c1e67c281ad13bffcb4d172ffb8f5a85581e5d5984eacb310365be8"
20+
21+
// Load keypair from hex using SimpleKeypair
22+
privateKeyBytes, err := sdk.HexDecode(privateKeyHex)
23+
if err != nil {
24+
log.Fatalf("Failed to decode private key: %v", err)
25+
}
26+
27+
simpleKeypair, err := sdk.SimpleKeypairFromBytes(privateKeyBytes)
28+
if err != nil {
29+
log.Fatalf("Failed to load private key: %v", err)
30+
}
31+
32+
fromAddress, _ := sdk.AddressFromHex(senderAddressHex)
33+
toAddress, _ := sdk.AddressFromHex(senderAddressHex)
34+
coinObjId, _ := sdk.ObjectIdFromHex(coinObjectIdHex)
35+
gasCoinObjId, _ := sdk.ObjectIdFromHex(gasObjectIdHex)
36+
37+
log.Printf("Building transaction from %s to %s\n", fromAddress.ToHex(), toAddress.ToHex())
38+
39+
// Build the transaction
40+
builder := sdk.TransactionBuilderInit(fromAddress, client)
41+
builder.TransferObjects(toAddress, []*sdk.PtbArgument{sdk.PtbArgumentObjectId(coinObjId)})
42+
builder.Gas(gasCoinObjId).GasBudget(1000000000)
43+
44+
// Finish building to get the transaction
45+
txn, err := builder.Finish()
46+
if err.(*sdk.SdkFfiError) != nil {
47+
log.Fatalf("Failed to build transaction: %v", err)
48+
}
49+
50+
log.Printf("Transaction built, signing with keypair...\n")
51+
52+
// Sign the transaction using SimpleKeypair
53+
simpleSignature, err := simpleKeypair.TrySign(txn.SigningDigest())
54+
if err != nil {
55+
log.Fatalf("Failed to sign transaction: %v", err)
56+
}
57+
58+
// Convert SimpleSignature to UserSignature
59+
userSignature := sdk.UserSignatureNewSimple(simpleSignature)
60+
61+
log.Printf("Transaction signed, executing on-chain...\n")
62+
63+
// Execute the transaction on-chain
64+
effects, err := client.ExecuteTx([]*sdk.UserSignature{userSignature}, txn)
65+
if err.(*sdk.SdkFfiError) != nil {
66+
log.Fatalf("ExecuteTx failed: %v", err)
67+
}
68+
69+
if effects == nil {
70+
log.Println("No transaction effects returned")
71+
return
72+
}
73+
74+
log.Printf("Transaction executed successfully!\n")
75+
76+
// Access and print transaction effects
77+
PrintObjectChanges(*effects)
78+
}
79+
80+
func PrintObjectChanges(effects *sdk.TransactionEffects) {
81+
log.Println("=== Object Changes (from ExecuteTx) ===")
82+
83+
if !effects.IsV1() {
84+
log.Panicln("Effects version is not V1")
85+
}
86+
87+
effectsV1 := effects.AsV1()
88+
log.Printf("Total changed objects: %d\n", len(effectsV1.ChangedObjects))
89+
90+
for i, change := range effectsV1.ChangedObjects {
91+
log.Printf("Object #%d:\n", i+1)
92+
log.Printf(" Object ID: %s\n", change.ObjectId.ToHex())
93+
94+
// Check creation/deletion status using IdOperation
95+
switch change.IdOperation {
96+
case sdk.IdOperationCreated:
97+
log.Println(" Status: CREATED")
98+
case sdk.IdOperationDeleted:
99+
log.Println(" Status: DELETED")
100+
case sdk.IdOperationNone:
101+
log.Println(" Status: MODIFIED")
102+
}
103+
104+
// Object type (if available)
105+
if change.ObjectType != nil {
106+
log.Printf(" Type: %s\n", *change.ObjectType)
107+
} else {
108+
log.Printf(" Type: %v\n", change.ObjectType)
109+
}
110+
111+
// Input state (state before transaction)
112+
switch input := change.InputState.(type) {
113+
case sdk.ObjectInMissing:
114+
log.Println(" Input State: Missing (new object)")
115+
case sdk.ObjectInData:
116+
log.Printf(" Input State: Version=%d, Owner=%s\n", input.Version, input.Owner.String())
117+
}
118+
119+
// Output state (state after transaction)
120+
switch output := change.OutputState.(type) {
121+
case sdk.ObjectOutMissing:
122+
log.Println(" Output State: Missing (deleted)")
123+
case sdk.ObjectOutObjectWrite:
124+
log.Printf(" Output State: ObjectWrite, Owner=%s\n", output.Owner.String())
125+
case sdk.ObjectOutPackageWrite:
126+
log.Printf(" Output State: PackageWrite, Version=%d\n", output.Version)
127+
}
128+
}
129+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"log"
8+
9+
sdk "bindings/iota_sdk_ffi"
10+
)
11+
12+
// === DryRun with ObjectChange Example ===
13+
// This example demonstrates the ObjectChange feature using DryRun,
14+
// which simulates transaction execution without actual on-chain changes.
15+
func main() {
16+
17+
// Initialize client
18+
client := sdk.GraphQlClientNewDevnet()
19+
20+
// Use actual addresses from devnet (these are examples)
21+
fromAddress, _ := sdk.AddressFromHex("0x611830d3641a68f94a690dcc25d1f4b0dac948325ac18f6dd32564371735f32c")
22+
23+
toAddress, _ := sdk.AddressFromHex("0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900")
24+
25+
coinObjId, _ := sdk.ObjectIdFromHex("0xd04077fe3b6fad13b3d4ed0d535b7ca92afcac8f0f2a0e0925fb9f4f0b30c699")
26+
27+
gasCoinObjId, _ := sdk.ObjectIdFromHex("0x0b0270ee9d27da0db09651e5f7338dfa32c7ee6441ccefa1f6e305735bcfc7ab")
28+
29+
builder := sdk.TransactionBuilderInit(fromAddress, client)
30+
builder.TransferObjects(toAddress, []*sdk.PtbArgument{sdk.PtbArgumentObjectId(coinObjId)})
31+
builder.Gas(gasCoinObjId).GasBudget(1000000000)
32+
33+
dryRunResult, err := builder.DryRun(false)
34+
if err.(*sdk.SdkFfiError) != nil {
35+
log.Fatalf("Dry run failed: %v", err)
36+
}
37+
38+
if dryRunResult.Error != nil {
39+
log.Fatalf("Dry run returned an error: %s\n", *dryRunResult.Error)
40+
}
41+
42+
log.Printf("Dry run succeeded!\n")
43+
44+
// Access transaction effects from dry run
45+
if dryRunResult.Effects != nil {
46+
PrintObjectChanges(*dryRunResult.Effects)
47+
} else {
48+
log.Println("No transaction effects available in dry run result")
49+
}
50+
}
51+
52+
func PrintObjectChanges(effects *sdk.TransactionEffects) {
53+
log.Println("=== Object Changes (from DryRun) ===")
54+
55+
if !effects.IsV1() {
56+
log.Println("Effects version is not V1")
57+
return
58+
}
59+
60+
effectsV1 := effects.AsV1()
61+
log.Printf("Total changed objects: %d\n", len(effectsV1.ChangedObjects))
62+
63+
for i, change := range effectsV1.ChangedObjects {
64+
log.Printf("Object #%d:\n", i+1)
65+
log.Printf(" Object ID: %s\n", change.ObjectId.ToHex())
66+
67+
// Check creation/deletion status using IdOperation
68+
switch change.IdOperation {
69+
case sdk.IdOperationCreated:
70+
log.Println(" Status: CREATED")
71+
case sdk.IdOperationDeleted:
72+
log.Println(" Status: DELETED")
73+
case sdk.IdOperationNone:
74+
log.Println(" Status: MODIFIED")
75+
}
76+
77+
// Object type (if available)
78+
if change.ObjectType != nil {
79+
log.Printf(" Type: %s\n", *change.ObjectType)
80+
} else {
81+
log.Printf(" Type: %v\n", change.ObjectType)
82+
}
83+
84+
// Input state (state before transaction)
85+
switch input := change.InputState.(type) {
86+
case sdk.ObjectInMissing:
87+
log.Println(" Input State: Missing (new object)")
88+
case sdk.ObjectInData:
89+
log.Printf(" Input State: Version=%d, Owner=%s\n", input.Version, input.Owner.String())
90+
}
91+
92+
// Output state (state after transaction)
93+
switch output := change.OutputState.(type) {
94+
case sdk.ObjectOutMissing:
95+
log.Println(" Output State: Missing (deleted)")
96+
case sdk.ObjectOutObjectWrite:
97+
log.Printf(" Output State: ObjectWrite, Owner=%s\n", output.Owner.String())
98+
case sdk.ObjectOutPackageWrite:
99+
log.Printf(" Output State: PackageWrite, Version=%d\n", output.Version)
100+
}
101+
}
102+
}

bindings/go/examples/prepare_split_coins/main.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ func main() {
3030
sender,
3131
[]*sdk.PtbArgument{sdk.PtbArgumentRes("coin1"), sdk.PtbArgumentRes("coin2"), sdk.PtbArgumentRes("coin3")},
3232
)
33-
builder.Gas(coinObjId).GasBudget(1000000000)
3433

3534
txn, err := builder.Finish()
3635
if err.(*sdk.SdkFfiError) != nil {
@@ -44,14 +43,13 @@ func main() {
4443
log.Printf("Signing Digest: %v", sdk.HexEncode(txn.SigningDigest()))
4544
log.Printf("Txn Bytes: %v", sdk.Base64Encode(txnBytes))
4645

47-
res, err := builder.DryRun(false)
46+
skipChecks := bool(false)
47+
res, err := client.DryRunTx(txn, &skipChecks)
4848
if err.(*sdk.SdkFfiError) != nil {
49-
log.Fatalf("Failed to split coins: %v", err)
49+
log.Fatalf("Failed to dry run split coins: %v", err)
5050
}
51-
5251
if res.Error != nil {
5352
log.Fatalf("Failed to split coins: %v", *res.Error)
5453
}
55-
5654
log.Print("Split coins dry run was successful!")
5755
}

bindings/go/iota_sdk_ffi/iota_sdk_ffi.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25290,13 +25290,17 @@ type ChangedObject struct {
2529025290
// This information isn't required by the protocol but is useful for
2529125291
// providing more detailed semantics on object changes.
2529225292
IdOperation IdOperation
25293+
// Optional object type information. This is not part of the BCS protocol
25294+
// data but can be populated from other sources when available.
25295+
ObjectType *string
2529325296
}
2529425297

2529525298
func (r *ChangedObject) Destroy() {
2529625299
FfiDestroyerObjectId{}.Destroy(r.ObjectId);
2529725300
FfiDestroyerObjectIn{}.Destroy(r.InputState);
2529825301
FfiDestroyerObjectOut{}.Destroy(r.OutputState);
2529925302
FfiDestroyerIdOperation{}.Destroy(r.IdOperation);
25303+
FfiDestroyerOptionalString{}.Destroy(r.ObjectType);
2530025304
}
2530125305

2530225306
type FfiConverterChangedObject struct {}
@@ -25313,6 +25317,7 @@ func (c FfiConverterChangedObject) Read(reader io.Reader) ChangedObject {
2531325317
FfiConverterObjectInINSTANCE.Read(reader),
2531425318
FfiConverterObjectOutINSTANCE.Read(reader),
2531525319
FfiConverterIdOperationINSTANCE.Read(reader),
25320+
FfiConverterOptionalStringINSTANCE.Read(reader),
2531625321
}
2531725322
}
2531825323

@@ -25325,6 +25330,7 @@ func (c FfiConverterChangedObject) Write(writer io.Writer, value ChangedObject)
2532525330
FfiConverterObjectInINSTANCE.Write(writer, value.InputState);
2532625331
FfiConverterObjectOutINSTANCE.Write(writer, value.OutputState);
2532725332
FfiConverterIdOperationINSTANCE.Write(writer, value.IdOperation);
25333+
FfiConverterOptionalStringINSTANCE.Write(writer, value.ObjectType);
2532825334
}
2532925335

2533025336
type FfiDestroyerChangedObject struct {}

bindings/kotlin/lib/iota_sdk/iota_sdk_ffi.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45255,7 +45255,12 @@ data class ChangedObject (
4525545255
* This information isn't required by the protocol but is useful for
4525645256
* providing more detailed semantics on object changes.
4525745257
*/
45258-
var `idOperation`: IdOperation
45258+
var `idOperation`: IdOperation,
45259+
/**
45260+
* Optional object type information. This is not part of the BCS protocol
45261+
* data but can be populated from other sources when available.
45262+
*/
45263+
var `objectType`: kotlin.String? = null
4525945264
) : Disposable {
4526045265

4526145266
@Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
@@ -45265,7 +45270,8 @@ data class ChangedObject (
4526545270
this.`objectId`,
4526645271
this.`inputState`,
4526745272
this.`outputState`,
45268-
this.`idOperation`
45273+
this.`idOperation`,
45274+
this.`objectType`
4526945275
)
4527045276
}
4527145277

@@ -45282,21 +45288,24 @@ public object FfiConverterTypeChangedObject: FfiConverterRustBuffer<ChangedObjec
4528245288
FfiConverterTypeObjectIn.read(buf),
4528345289
FfiConverterTypeObjectOut.read(buf),
4528445290
FfiConverterTypeIdOperation.read(buf),
45291+
FfiConverterOptionalString.read(buf),
4528545292
)
4528645293
}
4528745294

4528845295
override fun allocationSize(value: ChangedObject) = (
4528945296
FfiConverterTypeObjectId.allocationSize(value.`objectId`) +
4529045297
FfiConverterTypeObjectIn.allocationSize(value.`inputState`) +
4529145298
FfiConverterTypeObjectOut.allocationSize(value.`outputState`) +
45292-
FfiConverterTypeIdOperation.allocationSize(value.`idOperation`)
45299+
FfiConverterTypeIdOperation.allocationSize(value.`idOperation`) +
45300+
FfiConverterOptionalString.allocationSize(value.`objectType`)
4529345301
)
4529445302

4529545303
override fun write(value: ChangedObject, buf: ByteBuffer) {
4529645304
FfiConverterTypeObjectId.write(value.`objectId`, buf)
4529745305
FfiConverterTypeObjectIn.write(value.`inputState`, buf)
4529845306
FfiConverterTypeObjectOut.write(value.`outputState`, buf)
4529945307
FfiConverterTypeIdOperation.write(value.`idOperation`, buf)
45308+
FfiConverterOptionalString.write(value.`objectType`, buf)
4530045309
}
4530145310
}
4530245311

0 commit comments

Comments
 (0)