Skip to content

Commit 4cb0d47

Browse files
committed
feat: add sign_send_iota example
1 parent b921ab3 commit 4cb0d47

File tree

5 files changed

+329
-0
lines changed

5 files changed

+329
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
// Amount to send in nanos
14+
amount := uint64(1000)
15+
recipientAddress, err := sdk.AddressFromHex("0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900")
16+
if err != nil {
17+
log.Fatalf("Failed to parse recipient address: %v", err)
18+
}
19+
20+
privateKey, err := sdk.NewEd25519PrivateKey(make([]byte, 32))
21+
if err != nil {
22+
log.Fatalf("Failed to create private key: %v", err)
23+
}
24+
publicKey := privateKey.PublicKey()
25+
senderAddress := publicKey.DeriveAddress()
26+
log.Printf("Sender address: %s", senderAddress.ToHex())
27+
28+
// Request funds from faucet
29+
faucet := sdk.FaucetClientNewLocal()
30+
_, err = faucet.RequestAndWait(senderAddress)
31+
if err.(*sdk.SdkFfiError) != nil {
32+
log.Fatalf("Failed to request faucet: %v", err)
33+
}
34+
35+
client := sdk.GraphQlClientNewLocalhost()
36+
// Get coins for the sender address
37+
coinsPage, err := client.Coins(senderAddress, nil, nil)
38+
if err.(*sdk.SdkFfiError) != nil {
39+
log.Fatalf("Failed to get coins: %v", err)
40+
}
41+
if len(coinsPage.Data) == 0 {
42+
log.Fatalf("No coins found")
43+
}
44+
gasCoin := coinsPage.Data[0]
45+
46+
gasObject, err := client.Object(gasCoin.Id(), nil)
47+
if err.(*sdk.SdkFfiError) != nil {
48+
log.Fatalf("Failed to get gas object: %v", err)
49+
}
50+
if gasObject == nil {
51+
log.Fatalf("Missing gas object")
52+
}
53+
54+
builder := sdk.TransactionBuilderInit(senderAddress, client)
55+
56+
// Split the amount from the gas coin
57+
builder.SplitCoins(gasCoin.Id(), []uint64{amount}, []string{"coin1"})
58+
59+
// Transfer the split coin
60+
builder.TransferObjects(recipientAddress, []*sdk.PtbArgument{sdk.PtbArgumentRes("coin1")})
61+
62+
builder.Gas(gasCoin.Id()).GasBudget(50000000)
63+
gasPrice, err := client.ReferenceGasPrice(nil)
64+
if err.(*sdk.SdkFfiError) != nil {
65+
log.Fatalf("Failed to get gas price: %v", err)
66+
}
67+
builder.GasPrice(*gasPrice)
68+
69+
txn, err := builder.Finish()
70+
if err.(*sdk.SdkFfiError) != nil {
71+
log.Fatalf("Failed to create transaction: %v", err)
72+
}
73+
74+
dryRunResult, err := client.DryRunTx(txn, nil)
75+
if err.(*sdk.SdkFfiError) != nil {
76+
log.Fatalf("Failed to dry run: %v", err)
77+
}
78+
if dryRunResult.Error != nil {
79+
log.Fatalf("Dry run failed: %v", *dryRunResult.Error)
80+
}
81+
82+
signature, err := privateKey.TrySignSimple(txn.SigningDigest())
83+
if err != nil {
84+
log.Fatalf("Failed to sign: %v", err)
85+
}
86+
userSignature := sdk.UserSignatureNewSimple(signature)
87+
88+
effects, err := client.ExecuteTx([]*sdk.UserSignature{userSignature}, txn)
89+
if err.(*sdk.SdkFfiError) != nil {
90+
log.Fatalf("Failed to execute: %v", err)
91+
}
92+
if effects == nil {
93+
log.Fatalf("Transaction execution failed")
94+
}
95+
log.Printf("Digest: %s", sdk.HexEncode((*effects).Digest().ToBytes()))
96+
log.Printf("Transaction status: %v", (*effects).AsV1().Status)
97+
log.Printf("Effects: %+v", (*effects).AsV1())
98+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import iota_sdk.*
5+
import kotlinx.coroutines.runBlocking
6+
7+
fun main() = runBlocking {
8+
try {
9+
// Amount to send in nanos
10+
val amount = 1000uL
11+
val recipientAddress =
12+
Address.fromHex(
13+
"0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900"
14+
)
15+
16+
val privateKey = Ed25519PrivateKey(ByteArray(32))
17+
val publicKey = privateKey.publicKey()
18+
val senderAddress = publicKey.deriveAddress()
19+
println("Sender address: ${senderAddress.toHex()}")
20+
21+
// Request funds from faucet
22+
val faucet = FaucetClient.newLocal()
23+
faucet.requestAndWait(senderAddress)
24+
25+
val client = GraphQlClient.newLocalhost()
26+
// Get coins for the sender address
27+
val coinsPage = client.coins(senderAddress, null, null)
28+
if (coinsPage.data.isEmpty()) {
29+
throw Exception("No coins found")
30+
}
31+
val gasCoin = coinsPage.data[0]
32+
33+
val builder = TransactionBuilder.init(senderAddress, client)
34+
35+
// Split the amount from the gas coin
36+
builder.splitCoins(gasCoin.id(), listOf(amount), listOf("coin1"))
37+
38+
// Transfer the split coin
39+
builder.transferObjects(recipientAddress, listOf(PtbArgument.res("coin1")))
40+
41+
builder.gas(gasCoin.id()).gasBudget(50000000.toULong())
42+
val gasPrice = client.referenceGasPrice(null)
43+
if (gasPrice == null) {
44+
throw Exception("Failed to get gas price")
45+
}
46+
builder.gasPrice(gasPrice)
47+
48+
val txn = builder.finish()
49+
50+
val dryRunResult = client.dryRunTx(txn, false)
51+
if (dryRunResult.error != null) {
52+
throw Exception("Dry run failed: ${dryRunResult.error}")
53+
}
54+
55+
val signature = privateKey.trySignSimple(txn.signingDigest())
56+
val userSignature = UserSignature.newSimple(signature)
57+
58+
val effects = client.executeTx(listOf(userSignature), txn)
59+
if (effects == null) {
60+
throw Exception("Transaction execution failed")
61+
}
62+
println("Digest: ${hexEncode(effects.digest().toBytes())}")
63+
println("Transaction status: ${effects.asV1().status}")
64+
println("Effects: ${effects.asV1()}")
65+
} catch (e: Exception) {
66+
e.printStackTrace()
67+
}
68+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
8+
9+
async def main():
10+
try:
11+
# Amount to send in nanos
12+
amount = 1000
13+
recipient_address = Address.from_hex(
14+
"0x0000a4984bd495d4346fa208ddff4f5d5e5ad48c21dec631ddebc99809f16900"
15+
)
16+
17+
private_key = Ed25519PrivateKey(b'\x00' * 32)
18+
public_key = private_key.public_key()
19+
sender_address = public_key.derive_address()
20+
print(f"Sender address: {sender_address.to_hex()}")
21+
22+
# Request funds from faucet
23+
faucet = FaucetClient.new_local()
24+
await faucet.request_and_wait(sender_address)
25+
26+
client = GraphQlClient.new_localhost()
27+
# Get coins for the sender address
28+
coins_page = await client.coins(sender_address, None, None)
29+
if not coins_page.data:
30+
raise Exception("No coins found")
31+
gas_coin = coins_page.data[0]
32+
33+
builder = await TransactionBuilder.init(sender_address, client)
34+
35+
# Split the amount from the gas coin
36+
builder.split_coins(gas_coin.id(), [amount], ["coin1"])
37+
38+
# Transfer the split coin
39+
builder.transfer_objects(recipient_address, [PtbArgument.res("coin1")])
40+
41+
builder.gas(gas_coin.id()).gas_budget(50000000)
42+
gas_price = await client.reference_gas_price(None)
43+
if gas_price is None:
44+
raise Exception("Failed to get gas price")
45+
builder.gas_price(gas_price)
46+
47+
txn = await builder.finish()
48+
49+
dry_run_result = await client.dry_run_tx(txn, False)
50+
if dry_run_result.error is not None:
51+
raise Exception(f"Dry run failed: {dry_run_result.error}")
52+
53+
signature = private_key.try_sign_simple(txn.signing_digest())
54+
user_signature = UserSignature.new_simple(signature)
55+
56+
effects = await client.execute_tx([user_signature], txn)
57+
if effects is None:
58+
raise Exception("Transaction execution failed")
59+
print(f"Digest: {hex_encode(effects.digest().to_bytes())}")
60+
print(f"Transaction status: {effects.as_v1().status}")
61+
print(f"Effects: {effects.as_v1()}")
62+
63+
except Exception as e:
64+
print(f"Error: {e}")
65+
66+
67+
if __name__ == "__main__":
68+
asyncio.run(main())
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use eyre::Result;
5+
use iota_crypto::{IotaSigner, ed25519::Ed25519PrivateKey};
6+
use iota_graphql_client::{Client, faucet::FaucetClient};
7+
use iota_transaction_builder::{TransactionBuilder, res};
8+
use iota_types::Address;
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 mut builder = TransactionBuilder::new(sender_address).with_client(client.clone());
35+
36+
// Split the amount from the gas coin
37+
builder
38+
.split_coins(*gas_coin.id(), vec![amount])
39+
.name("coin1");
40+
41+
// Transfer the split coin
42+
builder.transfer_objects(recipient_address, vec![res("coin1")]);
43+
44+
builder.gas(*gas_coin.id()).gas_budget(50_000_000);
45+
builder.gas_price(
46+
client
47+
.reference_gas_price(None)
48+
.await?
49+
.expect("missing ref gas price"),
50+
);
51+
52+
let tx = builder.finish().await?;
53+
54+
let dry_run_result = client.dry_run_tx(&tx, false).await?;
55+
if let Some(err) = dry_run_result.error {
56+
eyre::bail!("Dry run failed: {err}");
57+
}
58+
59+
let signature = private_key.sign_transaction(&tx)?;
60+
61+
let effects = client.execute_tx(&[signature], &tx).await?;
62+
if let Some(effects) = effects {
63+
println!("Digest: {}", effects.digest());
64+
println!("Transaction status: {:?}", effects.status());
65+
println!("Effects: {effects:#?}");
66+
} else {
67+
println!("Transaction execution failed");
68+
}
69+
70+
Ok(())
71+
}

crates/iota-transaction-builder/src/builder/ptb_arguments.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,30 @@ impl PTBArguments for Vec<iota_types::Input> {
8080
}
8181
}
8282

83+
impl PTBArguments for Vec<Res> {
84+
fn push_args(&self, ptb: &mut TransactionBuildData, args: &mut Vec<Argument>) {
85+
for input in self {
86+
input.push_args(ptb, args);
87+
}
88+
}
89+
}
90+
91+
impl<T: PTBArguments> PTBArguments for [T] {
92+
fn push_args(&self, ptb: &mut TransactionBuildData, args: &mut Vec<Argument>) {
93+
for input in self {
94+
input.push_args(ptb, args);
95+
}
96+
}
97+
}
98+
99+
impl<const N: usize, T: PTBArguments> PTBArguments for [T; N] {
100+
fn push_args(&self, ptb: &mut TransactionBuildData, args: &mut Vec<Argument>) {
101+
for input in self {
102+
input.push_args(ptb, args);
103+
}
104+
}
105+
}
106+
83107
/// Allows specifying shared parameters.
84108
pub struct Shared<T>(pub T);
85109

0 commit comments

Comments
 (0)