Skip to main content

Send a Transaction

info

Though it is possible to send transactions with iota.rs, we strongly recommend that you use the official wallet.rs library together with the stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses, and UTXO.

Pre-requirements

Before you can create a transaction, you will need to:

Create a Wallet

Create a wallet seed

You can create a wallet seed by instantiating an Ed25519Seed using its constructor. You will need to provide secretBytes to the constructor, which you can get by using the Converter.hexToBytes(hex) function.

Retrieve the wallet's address

Once you have created your wallet seed, you can use it

Once you have generated a seed, you can create a wallet using the Ed25519Address.toAddress() fucntion.

Request Funds

Before you can send the transaction, you will need to request funds. If you are on the alphanet, you can request test funds from the faucet. If you are running a private tangle, you can use the '/enqueue' endpoint.

Send the Transaction

Once you have fulfilled the pre-requirements, you can send the transaction using either the sendAdvanced(client, inputsAndSignatureKeyPairs, outputs, taggedData?) function, which will return the created block's id, or the send(client, seed, accountIndex, addressBech32, amount, taggedData?, addressOptions?) function, which will return the created block's id, as well as the constructed blocks.

As this example uses a genesis address, it must use the (client, inputsAndSignatureKeyPairs, outputs, taggedData?) function. However, if you are doing a wallet to wallet transfer, you can use the send(client, seed, accountIndex, addressBech32, amount, taggedData?, addressOptions?) method.

Example Code

import { Bip32Path } from "@iota/crypto.js";
import {
BASIC_OUTPUT_TYPE, Bech32Helper,
Ed25519Address,
Ed25519Seed,
ED25519_ADDRESS_TYPE,
getBalance,
getUnspentAddress,
getUnspentAddresses, IBasicOutput, IKeyPair, IndexerPluginClient, IOutputsResponse, IUTXOInput,
OutputTypes,
sendAdvanced, SingleNodeClient,
UTXO_INPUT_TYPE
} from "@iota/iota.js";
import { Converter } from "@iota/util.js";
import { NeonPowProvider } from "@iota/pow-neon.js";
import bigInt, { BigInteger } from "big-integer";
import fetch from "node-fetch";
import { randomBytes } from "node:crypto";

const API_ENDPOINT = "https://api.alphanet.iotaledger.net/";
const FAUCET = "https://faucet.alphanet.iotaledger.net/api/enqueue"

// If running the node locally
// const API_ENDPOINT = "http://localhost:14265/";
// const FAUCET = "http://localhost:8091/api/enqueue";

async function run() {
const client = new SingleNodeClient(API_ENDPOINT, {powProvider: new NeonPowProvider()});
const nodeInfo = await client.info();

// Generate the seed from the Mnemonic
// const mnemonic =
// "assist file add kidney sense anxiety march quality sphere stamp crime swift mystery bind thrive impact walk solar asset pottery nation dutch column beef";
// const genesisSeed = Ed25519Seed.fromMnemonic(mnemonic);

// Generate the seed from random bytes
const genesisSeed = new Ed25519Seed(randomBytes(32));

const genesisPath = new Bip32Path("m/44'/4218'/0'/0'/0'");
const genesisWalletSeed = genesisSeed.generateSeedFromPath(genesisPath);
const genesisWalletKeyPair = genesisWalletSeed.keyPair();

// Get the address for the path seed which is actually the Blake2b.sum256 of the public key
// display it in both Ed25519 and Bech 32 format
const genesisEd25519Address = new Ed25519Address(genesisWalletKeyPair.publicKey);
const genesisWalletAddress = genesisEd25519Address.toAddress();
const genesisWalletAddressHex = Converter.bytesToHex(genesisWalletAddress, true);
const genesisWalletAddressBech32 = Bech32Helper.toBech32(ED25519_ADDRESS_TYPE, genesisWalletAddress, nodeInfo.protocol.bech32Hrp);

console.log("Genesis");
console.log("\tSeed", Converter.bytesToHex(genesisWalletSeed.toBytes()));
console.log("\tAddress Ed25519", genesisWalletAddressHex);
console.log("\tAddress Bech32", genesisWalletAddressBech32);

// Create a new seed for the wallet
const walletSeed = new Ed25519Seed(
Converter.hexToBytes("e57fb750f3a3a67969ece5bd9ae7eef5b2256a818b2aac458941f7274985a410")
);

// Use the new seed like a wallet with Bip32 Paths 44,4128,accountIndex,isInternal,addressIndex
const walletPath = new Bip32Path("m/44'/4218'/0'/0'/0'");
const walletAddressSeed = walletSeed.generateSeedFromPath(walletPath);
const walletEd25519Address = new Ed25519Address(walletAddressSeed.keyPair().publicKey);
const newAddress = walletEd25519Address.toAddress();
const newAddressHex = Converter.bytesToHex(newAddress, true);

console.log("Wallet 1");
console.log("\tSeed:", Converter.bytesToHex(walletSeed.toBytes()));
console.log("\tPath:", walletPath.toString());
console.log(`\tAddress Ed25519 ${walletPath.toString()}:`, newAddressHex);
console.log(
`\tAddress Bech32 ${walletPath.toString()}:`,
Bech32Helper.toBech32(ED25519_ADDRESS_TYPE, newAddress, nodeInfo.protocol.bech32Hrp)
);

//Request funds from faucet
const genesisFunds = await requestFunds(FAUCET, genesisWalletAddressBech32);
console.log("genesisFunds: ", genesisFunds)

// Because we are using the genesis address we must use send advanced as the input address is
// not calculated from a Bip32 path, if you were doing a wallet to wallet transfer you can just use send
// which calculates all the inputs/outputs for you
const indexerPlugin = new IndexerPluginClient(client);
// const genesisAddressOutputs = await indexerPlugin.basicOutputs({ addressBech32: genesisWalletAddressBech32 });
const genesisAddressOutputs = await fetchAndWaitForBasicOutputs(genesisWalletAddressBech32, indexerPlugin);

const inputsWithKeyPairs: {
input: IUTXOInput;
addressKeyPair: IKeyPair;
consumingOutput: OutputTypes;
}[] = [];

let totalGenesis: BigInteger = bigInt(0);

for (let i = 0; i < genesisAddressOutputs.items.length; i++) {
const output = await client.output(genesisAddressOutputs.items[i]);
if (!output.metadata.isSpent) {
inputsWithKeyPairs.push({
input: {
type: UTXO_INPUT_TYPE,
transactionId: output.metadata.transactionId,
transactionOutputIndex: output.metadata.outputIndex
},
addressKeyPair: genesisWalletKeyPair,
consumingOutput: output.output
});
if (output.output.type === BASIC_OUTPUT_TYPE) {
totalGenesis = totalGenesis.plus((output.output as IBasicOutput).amount);
}
}
}

const amountToSend = bigInt(10000000);

const outputs: {
address: string;
addressType: number;
amount: BigInteger;
}[] = [
// This is the transfer to the new address
{
address: newAddressHex,
addressType: ED25519_ADDRESS_TYPE,
amount: amountToSend
},
// Sending remainder back to genesis
{
address: genesisWalletAddressHex,
addressType: ED25519_ADDRESS_TYPE,
amount: totalGenesis.minus(amountToSend)
}
];

const { blockId } = await sendAdvanced(client, inputsWithKeyPairs, outputs, {
tag: Converter.utf8ToBytes("WALLET"),
data: Converter.utf8ToBytes("Fireflea")
});

console.log("Created Block Id", blockId);

const newAddressBalance = await getBalance(client, walletSeed, 0);
console.log("Wallet 1 Address Balance", newAddressBalance);

const unspentAddress = await getUnspentAddress(client, walletSeed, 0);
console.log("Wallet 1 First Unspent Address", unspentAddress);

const allUspentAddresses = await getUnspentAddresses(client, walletSeed, 0);
console.log("Wallet 1 Unspent Addresses", allUspentAddresses);
}

run()
.then(() => console.log("Done"))
.catch(err => console.error(err));

async function requestFunds(url: string, addressBech32: string): Promise<object> {
const requestFounds = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ address: addressBech32 })
});

return await requestFounds.json();
}

async function fetchAndWaitForBasicOutputs(addressBech32: string, client: IndexerPluginClient): Promise<IOutputsResponse> {
let outputsResponse: IOutputsResponse = { ledgerIndex: 0, cursor: "", pageSize: "", items: [] };
let maxTries = 10;
let tries = 0;
while(outputsResponse.items.length == 0 ){
if (tries > maxTries){break}
tries++;
console.log("\tTry #",tries,": fetching basic output for address ", addressBech32)
outputsResponse = await client.basicOutputs({
addressBech32: addressBech32,
hasStorageDepositReturn: false,
hasExpiration: false,
hasTimelock: false,
hasNativeTokens: false,
});
if(outputsResponse.items.length == 0){
console.log("\tDidn't find any, retrying soon...")
await new Promise(f => setTimeout(f, 1000));}
}
if(tries > maxTries){
throw new Error("Didn't find any outputs for address");
}

return outputsResponse;
};

Expected Output

Genesis
Seed bd80f079560f25b35ab6a5f95e31e57e1f549f81140d1d28884037dc9fa6ed10
Address Ed25519 0xd49b2cf7f727faf9cad957d14d7f13993c10010f43b0c2a5de72b728b9d01835
Address Bech32 rms1qr2fkt8h7unl47w2m9tazntlzwvncyqppapmps49meetw29e6qvr2wrxcz6
Wallet 1
Seed: e57fb750f3a3a67969ece5bd9ae7eef5b2256a818b2aac458941f7274985a410
Path: m/44'/4218'/0'/0'/0'
Address Ed25519 m/44'/4218'/0'/0'/0': 0x515582fe648b0f10a2b2a1b91d7502190c979baabfee85b6bbb5020692e55d16
Address Bech32 m/44'/4218'/0'/0'/0': rms1qpg4tqh7vj9s7y9zk2smj8t4qgvse9um42l7apdkhw6syp5ju4w3vajenuh
genesisFunds: {
address: 'rms1qr2fkt8h7unl47w2m9tazntlzwvncyqppapmps49meetw29e6qvr2wrxcz6',
waitingRequests: 1
}
Try # 1 : fetching basic output for address rms1qr2fkt8h7unl47w2m9tazntlzwvncyqppapmps49meetw29e6qvr2wrxcz6
Didn't find any, retrying soon...
Try # 2 : fetching basic output for address rms1qr2fkt8h7unl47w2m9tazntlzwvncyqppapmps49meetw29e6qvr2wrxcz6
Didn't find any, retrying soon...
Try # 3 : fetching basic output for address rms1qr2fkt8h7unl47w2m9tazntlzwvncyqppapmps49meetw29e6qvr2wrxcz6
inputsWithKeyPairs: [{"input":{"type":0,"transactionId":"0xab9c00cf096a80b8414dacb78f256d6604d968621533ca5a314c690392d5bd3d","transactionOutputIndex":0},"addressKeyPair":{"publicKey":{"0":133,"1":225,"2":45,"3":246,"4":113,"5":97,"6":179,"7":226,"8":184,"9":195,"10":212,"11":50,"12":205,"13":196,"14":138,"15":221,"16":95,"17":3,"18":229,"19":242,"20":69,"21":223,"22":2,"23":212,"24":169,"25":203,"26":248,"27":141,"28":222,"29":162,"30":252,"31":176},"privateKey":{"0":189,"1":128,"2":240,"3":121,"4":86,"5":15,"6":37,"7":179,"8":90,"9":182,"10":165,"11":249,"12":94,"13":49,"14":229,"15":126,"16":31,"17":84,"18":159,"19":129,"20":20,"21":13,"22":29,"23":40,"24":136,"25":64,"26":55,"27":220,"28":159,"29":166,"30":237,"31":16,"32":133,"33":225,"34":45,"35":246,"36":113,"37":97,"38":179,"39":226,"40":184,"41":195,"42":212,"43":50,"44":205,"45":196,"46":138,"47":221,"48":95,"49":3,"50":229,"51":242,"52":69,"53":223,"54":2,"55":212,"56":169,"57":203,"58":248,"59":141,"60":222,"61":162,"62":252,"63":176}},"consumingOutput":{"type":3,"amount":"1000000000","unlockConditions":[{"type":0,"address":{"type":0,"pubKeyHash":"0xd49b2cf7f727faf9cad957d14d7f13993c10010f43b0c2a5de72b728b9d01835"}}]}}]
Created Block Id 0xa48c85b745e964d66ca48912a88faab7c86c7662a2993a488239377f2dc07728
Wallet 1 Address Balance Integer { value: 1010000000n }
Wallet 1 First Unspent Address {
address: 'rms1qpg4tqh7vj9s7y9zk2smj8t4qgvse9um42l7apdkhw6syp5ju4w3vajenuh',
path: "m/44'/4218'/0'/0'/0'",
balance: Integer { value: 1020000000n }
}
Wallet 1 Unspent Addresses [
{
address: 'rms1qpg4tqh7vj9s7y9zk2smj8t4qgvse9um42l7apdkhw6syp5ju4w3vajenuh',
path: "m/44'/4218'/0'/0'/0'",
balance: Integer { value: 1020000000n }
}
]
Done