Skip to content

Commit 9a70b6e

Browse files
committed
feat: add sign_send_iota example
1 parent 54106f5 commit 9a70b6e

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"encoding/binary"
8+
"log"
9+
10+
sdk "bindings/iota_sdk_ffi"
11+
)
12+
13+
func main() {
14+
// Amount to send in nanos
15+
amount := uint64(1000)
16+
recipientAddress, err := sdk.AddressFromHex("0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900")
17+
if err != nil {
18+
log.Fatalf("Failed to parse recipient address: %v", err)
19+
}
20+
21+
privateKey, err := sdk.NewEd25519PrivateKey(make([]byte, 32))
22+
if err != nil {
23+
log.Fatalf("Failed to create private key: %v", err)
24+
}
25+
publicKey := privateKey.PublicKey()
26+
senderAddress := publicKey.DeriveAddress()
27+
log.Printf("Sender address: %s", senderAddress.ToHex())
28+
29+
// Request funds from faucet
30+
faucet := sdk.FaucetClientNewLocal()
31+
_, err = faucet.RequestAndWait(senderAddress)
32+
if err.(*sdk.SdkFfiError) != nil {
33+
log.Fatalf("Failed to request faucet: %v", err)
34+
}
35+
36+
client := sdk.GraphQlClientNewLocalhost()
37+
// Get coins for the sender address
38+
coinsPage, err := client.Coins(senderAddress, nil, nil)
39+
if err.(*sdk.SdkFfiError) != nil {
40+
log.Fatalf("Failed to get coins: %v", err)
41+
}
42+
if len(coinsPage.Data) == 0 {
43+
log.Fatalf("No coins found")
44+
}
45+
gasCoin := coinsPage.Data[0]
46+
47+
gasObject, err := client.Object(gasCoin.Id(), nil)
48+
if err.(*sdk.SdkFfiError) != nil {
49+
log.Fatalf("Failed to get gas object: %v", err)
50+
}
51+
if gasObject == nil {
52+
log.Fatalf("Missing gas object")
53+
}
54+
55+
builder := sdk.NewTransactionBuilder()
56+
57+
amountBytes := make([]byte, 8)
58+
binary.LittleEndian.PutUint64(amountBytes, amount)
59+
amountArg := builder.Input(sdk.UnresolvedInputNewPure(amountBytes))
60+
// Split the amount from the gas coin
61+
splitResult := builder.SplitCoins(builder.Gas(), []*sdk.Argument{amountArg})
62+
coinToSend := splitResult.GetNestedResult(0)
63+
64+
// Transfer the split coin
65+
recipientAddressArg := builder.Input(sdk.UnresolvedInputNewPure(recipientAddress.ToBytes()))
66+
builder.TransferObjects([]*sdk.Argument{*coinToSend}, recipientAddressArg)
67+
68+
builder.SetSender(senderAddress)
69+
builder.SetGasBudget(50000000)
70+
gasPrice, err := client.ReferenceGasPrice(nil)
71+
if err.(*sdk.SdkFfiError) != nil {
72+
log.Fatalf("Failed to get gas price: %v", err)
73+
}
74+
builder.SetGasPrice(*gasPrice)
75+
builder.AddGasObjects([]*sdk.UnresolvedInput{sdk.UnresolvedInputFromObject(*gasObject).WithOwnedKind()})
76+
77+
txn, err := builder.Finish()
78+
if err != nil {
79+
log.Fatalf("Failed to create transaction: %v", err)
80+
}
81+
82+
dryRunResult, err := client.DryRunTx(txn, nil)
83+
if err.(*sdk.SdkFfiError) != nil {
84+
log.Fatalf("Failed to dry run: %v", err)
85+
}
86+
if dryRunResult.Error != nil {
87+
log.Fatalf("Dry run failed: %v", *dryRunResult.Error)
88+
}
89+
90+
signature, err := privateKey.TrySignSimple(txn.SigningDigest())
91+
if err != nil {
92+
log.Fatalf("Failed to sign: %v", err)
93+
}
94+
userSignature := sdk.UserSignatureNewSimple(signature)
95+
96+
effects, err := client.ExecuteTx([]*sdk.UserSignature{userSignature}, txn)
97+
if err.(*sdk.SdkFfiError) != nil {
98+
log.Fatalf("Failed to execute: %v", err)
99+
}
100+
if effects == nil {
101+
log.Fatalf("Transaction execution failed")
102+
}
103+
log.Printf("Digest: %s", sdk.HexEncode((*effects).Digest().ToBytes()))
104+
log.Printf("Transaction status: %v", (*effects).AsV1().Status)
105+
log.Printf("Effects: %+v", (*effects).AsV1())
106+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import iota_sdk.*
5+
import kotlinx.coroutines.runBlocking
6+
import java.nio.ByteBuffer
7+
import java.nio.ByteOrder
8+
9+
fun main() = runBlocking {
10+
try {
11+
// Amount to send in nanos
12+
val amount = 1000uL
13+
val recipientAddress =
14+
Address.fromHex(
15+
"0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900"
16+
)
17+
18+
val privateKey = Ed25519PrivateKey(ByteArray(32))
19+
val publicKey = privateKey.publicKey()
20+
val senderAddress = publicKey.deriveAddress()
21+
println("Sender address: ${senderAddress.toHex()}")
22+
23+
// Request funds from faucet
24+
val faucet = FaucetClient.newLocal()
25+
faucet.requestAndWait(senderAddress)
26+
27+
val client = GraphQlClient.newLocalhost()
28+
// Get coins for the sender address
29+
val coinsPage = client.coins(senderAddress, null, null)
30+
if (coinsPage.data.isEmpty()) {
31+
throw Exception("No coins found")
32+
}
33+
val gasCoin = coinsPage.data[0]
34+
35+
val gasObject = client.`object`(gasCoin.id(), null)
36+
if (gasObject == null) {
37+
throw Exception("Missing gas object")
38+
}
39+
40+
val builder = TransactionBuilder()
41+
42+
val amountBytes = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(amount.toLong()).array()
43+
val amountArg = builder.input(UnresolvedInput.newPure(amountBytes))
44+
// Split the amount from the gas coin
45+
val splitResult = builder.splitCoins(builder.gas(), listOf(amountArg))
46+
val coinToSend = splitResult.getNestedResult(0u.toUShort())
47+
48+
// Transfer the split coin
49+
val recipientAddressArg = builder.input(UnresolvedInput.newPure(recipientAddress.toBytes()))
50+
builder.transferObjects(listOf(coinToSend!!), recipientAddressArg)
51+
52+
builder.setSender(senderAddress)
53+
builder.setGasBudget(50000000.toULong())
54+
val gasPrice = client.referenceGasPrice(null)
55+
if (gasPrice == null) {
56+
throw Exception("Failed to get gas price")
57+
}
58+
builder.setGasPrice(gasPrice)
59+
builder.addGasObjects(listOf(UnresolvedInput.fromObject(gasObject).withOwnedKind()))
60+
61+
val txn = builder.finish()
62+
63+
val dryRunResult = client.dryRunTx(txn, false)
64+
if (dryRunResult.error != null) {
65+
throw Exception("Dry run failed: ${dryRunResult.error}")
66+
}
67+
68+
val signature = privateKey.trySignSimple(txn.signingDigest())
69+
val userSignature = UserSignature.newSimple(signature)
70+
71+
val effects = client.executeTx(listOf(userSignature), txn)
72+
if (effects == null) {
73+
throw Exception("Transaction execution failed")
74+
}
75+
println("Digest: ${hexEncode(effects.digest().toBytes())}")
76+
println("Transaction status: ${effects.asV1().status}")
77+
println("Effects: ${effects.asV1()}")
78+
} catch (e: Exception) {
79+
e.printStackTrace()
80+
}
81+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright (c) 2025 IOTA Stiftung
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from lib.iota_sdk_ffi import *
5+
6+
import asyncio
7+
import struct
8+
9+
10+
async def main():
11+
try:
12+
# Amount to send in nanos
13+
amount = 1000
14+
recipient_address = Address.from_hex(
15+
"0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900"
16+
)
17+
18+
private_key = Ed25519PrivateKey(b'\x00' * 32)
19+
public_key = private_key.public_key()
20+
sender_address = public_key.derive_address()
21+
print(f"Sender address: {sender_address.to_hex()}")
22+
23+
# Request funds from faucet
24+
faucet = FaucetClient.new_local()
25+
await faucet.request_and_wait(sender_address)
26+
27+
client = GraphQlClient.new_localhost()
28+
# Get coins for the sender address
29+
coins_page = await client.coins(sender_address, None, None)
30+
if not coins_page.data:
31+
raise Exception("No coins found")
32+
gas_coin = coins_page.data[0]
33+
34+
gas_object = await client.object(gas_coin.id(), None)
35+
if gas_object is None:
36+
raise Exception("Missing gas object")
37+
38+
builder = TransactionBuilder()
39+
40+
amount_bytes = struct.pack('<Q', amount)
41+
amount_arg = builder.input(UnresolvedInput.new_pure(amount_bytes))
42+
# Split the amount from the gas coin
43+
split_result = builder.split_coins(builder.gas(), [amount_arg])
44+
coin_to_send = split_result.get_nested_result(0)
45+
46+
# Transfer the split coin
47+
recipient_address_arg = builder.input(UnresolvedInput.new_pure(recipient_address.to_bytes()))
48+
builder.transfer_objects([coin_to_send], recipient_address_arg)
49+
50+
builder.set_sender(sender_address)
51+
builder.set_gas_budget(50000000)
52+
gas_price = await client.reference_gas_price(None)
53+
if gas_price is None:
54+
raise Exception("Failed to get gas price")
55+
builder.set_gas_price(gas_price)
56+
builder.add_gas_objects([UnresolvedInput.from_object(gas_object).with_owned_kind()])
57+
58+
txn = builder.finish()
59+
60+
dry_run_result = await client.dry_run_tx(txn, False)
61+
if dry_run_result.error is not None:
62+
raise Exception(f"Dry run failed: {dry_run_result.error}")
63+
64+
signature = private_key.try_sign_simple(txn.signing_digest())
65+
user_signature = UserSignature.new_simple(signature)
66+
67+
effects = await client.execute_tx([user_signature], txn)
68+
if effects is None:
69+
raise Exception("Transaction execution failed")
70+
print(f"Digest: {hex_encode(effects.digest().to_bytes())}")
71+
print(f"Transaction status: {effects.as_v1().status}")
72+
print(f"Effects: {effects.as_v1()}")
73+
74+
except Exception as e:
75+
print(f"Error: {e}")
76+
77+
78+
if __name__ == "__main__":
79+
asyncio.run(main())
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use anyhow::Result;
5+
use iota_crypto::{IotaSigner, ed25519::Ed25519PrivateKey};
6+
use iota_graphql_client::{Client, faucet::FaucetClient};
7+
use iota_transaction_builder::{TransactionBuilder, unresolved::Input};
8+
use iota_types::{Address, Argument};
9+
10+
#[tokio::main]
11+
async fn main() -> Result<()> {
12+
// Amount to send in nanos
13+
let amount = 1_000u64;
14+
let recipient_address =
15+
Address::from_hex("0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900")?;
16+
17+
let private_key = Ed25519PrivateKey::new([0; Ed25519PrivateKey::LENGTH]);
18+
let public_key = private_key.public_key();
19+
let sender_address = public_key.derive_address();
20+
println!("Sender address: {sender_address}");
21+
22+
// Request funds from faucet
23+
FaucetClient::local()
24+
.request_and_wait(sender_address)
25+
.await?;
26+
27+
let client = Client::new_localhost();
28+
// Get coins for the sender address
29+
let coins_page = client
30+
.coins(sender_address, None, Default::default())
31+
.await?;
32+
let gas_coin = coins_page.data.first().expect("no gas coin found");
33+
34+
let gas_object = client
35+
.object(*gas_coin.id(), None)
36+
.await?
37+
.expect("missing gas object");
38+
39+
let mut builder = TransactionBuilder::new();
40+
41+
let amount_arg = builder.input(Input::pure(&amount)?);
42+
// Split the amount from the gas coin
43+
let split_result = builder.split_coins(Argument::Gas, vec![amount_arg]);
44+
let coin_to_send = split_result.get_nested_result(0).unwrap();
45+
46+
// Transfer the split coin
47+
let recipient_address_arg = builder.input(Input::pure(&recipient_address)?);
48+
builder.transfer_objects(vec![coin_to_send], recipient_address_arg);
49+
50+
builder.set_sender(sender_address);
51+
builder.set_gas_budget(50_000_000);
52+
builder.set_gas_price(
53+
client
54+
.reference_gas_price(None)
55+
.await?
56+
.expect("missing ref gas price"),
57+
);
58+
builder.add_gas_objects([Input::from(&gas_object).with_owned_kind()]);
59+
60+
let tx = builder.finish()?;
61+
62+
let dry_run_result = client.dry_run_tx(&tx, false).await?;
63+
if let Some(err) = dry_run_result.error {
64+
anyhow::bail!("Dry run failed: {err}");
65+
}
66+
67+
let signature = private_key.sign_transaction(&tx)?;
68+
69+
let effects = client.execute_tx(&[signature], &tx).await?;
70+
if let Some(effects) = effects {
71+
println!("Digest: {}", effects.digest());
72+
println!("Transaction status: {:?}", effects.status());
73+
println!("Effects: {effects:#?}");
74+
} else {
75+
println!("Transaction execution failed");
76+
}
77+
78+
Ok(())
79+
}

0 commit comments

Comments
 (0)