Skip to content

Commit

Permalink
Add Python exchange examples (#732)
Browse files Browse the repository at this point in the history
* Add Python exchange examples, fix listen_wallet

* snake_case in comment, move example description to the top

* Exit after receiving an event

* Fix comment and remove empty newline
  • Loading branch information
Thoralf-M authored Jul 10, 2023
1 parent 74537b7 commit 5691911
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 31 deletions.
6 changes: 3 additions & 3 deletions bindings/nodejs/examples/exchange/0-generate-mnemonic.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Utils } from '@iota/sdk';

// This example generates a new random mnemonic.
// Run with command:
// yarn run-example ./exchange/0-generate-mnemonic.ts

// This example generates a new random mnemonic.
import { Utils } from '@iota/sdk';

async function run() {
try {
// Set the generated mnemonic as env variable MNEMONIC so it can be used in the next examples.
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/examples/exchange/1-create-account.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// This example creates a new database and account.
// Run with command:
// yarn run-example ./exchange/1-create-account.ts

import { Wallet, WalletOptions, CoinType } from '@iota/sdk';

// This example uses secrets in environment variables for simplicity which should not be done in production.
require('dotenv').config({ path: '.env' });

// Run with command:
// yarn run-example ./exchange/1-create-account.ts

// This example creates a new database and account.
async function run() {
try {
if (!process.env.WALLET_DB_PATH) {
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/examples/exchange/2-generate-address.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// This example generates an address for an account.
// Run with command:
// yarn run-example ./exchange/2-generate-address.ts

import { Wallet } from '@iota/sdk';

// This example uses secrets in environment variables for simplicity which should not be done in production.
require('dotenv').config({ path: '.env' });

// Run with command:
// yarn run-example ./exchange/2-generate-address.ts

// This example generates an address for an account.
async function run() {
try {
if (!process.env.WALLET_DB_PATH) {
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/examples/exchange/3-check-balance.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// This example gets the balance of an account.
// Run with command:
// yarn run-example ./exchange/3-check-balance.ts

import { Wallet } from '@iota/sdk';

// This example uses secrets in environment variables for simplicity which should not be done in production.
require('dotenv').config({ path: '.env' });

// Run with command:
// yarn run-example ./exchange/3-check-balance.ts

// This example gets the balance of an account.
async function run() {
try {
if (!process.env.WALLET_DB_PATH) {
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/examples/exchange/4-listen-events.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// This example listens to the NewOutput event.
// Run with command:
// yarn run-example ./exchange/4-listen-events.ts

import { Wallet, Event, WalletEventType } from '@iota/sdk';

// This example uses secrets in environment variables for simplicity which should not be done in production.
require('dotenv').config({ path: '.env' });

// Run with command:
// yarn run-example ./exchange/4-listen-events.ts

// This example listens to the NewOutput event.
async function run() {
try {
if (!process.env.WALLET_DB_PATH) {
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/examples/exchange/5-send-amount.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

// This example sends tokens to an address.
// Run with command:
// yarn run-example ./exchange/5-send-amount.ts

import { Wallet } from '@iota/sdk';

// This example uses secrets in environment variables for simplicity which should not be done in production.
require('dotenv').config({ path: '.env' });

// Run with command:
// yarn run-example ./exchange/5-send-amount.ts

// This example sends tokens to an address.
async function run() {
try {
if (!process.env.WALLET_DB_PATH) {
Expand Down
10 changes: 10 additions & 0 deletions bindings/python/examples/exchange/0_generate_mnemonic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example generates a new random mnemonic.

from iota_sdk import Utils

# Set the generated mnemonic as env variable MNEMONIC so it can be used in the next examples.
mnemonic = Utils.generate_mnemonic()
print(f'Mnemonic: {mnemonic}')
47 changes: 47 additions & 0 deletions bindings/python/examples/exchange/1_create_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example creates a new database and account.

from iota_sdk import Wallet, StrongholdSecretManager, SyncOptions, CoinType
from dotenv import load_dotenv
import os

# This example uses secrets in environment variables for simplicity which should not be done in production.
load_dotenv()

if 'WALLET_DB_PATH' not in os.environ:
raise Exception(".env WALLET_DB_PATH is undefined, see .env.example")
if 'NODE_URL' not in os.environ:
raise Exception(".env NODE_URL is undefined, see .env.example")
if 'STRONGHOLD_SNAPSHOT_PATH' not in os.environ:
raise Exception(
".env STRONGHOLD_SNAPSHOT_PATH is undefined, see .env.example")
if 'STRONGHOLD_PASSWORD' not in os.environ:
raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example")
if 'MNEMONIC' not in os.environ:
raise Exception(".env MNEMONIC is undefined, see .env.example")

client_options = {
'nodes': [os.environ.get('NODE_URL')],
}

secret_manager = StrongholdSecretManager(
os.environ.get('STRONGHOLD_SNAPSHOT_PATH'), os.environ['STRONGHOLD_PASSWORD'])

wallet = Wallet(os.environ.get('WALLET_DB_PATH'),
client_options, CoinType.IOTA, secret_manager)

# Store the mnemonic in the Stronghold snapshot, this only needs to be done once
wallet.store_mnemonic(
os.environ['MNEMONIC'])

created_account = wallet.create_account('Alice')
account = wallet.get_account('Alice')

# Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked,
# have a storage deposit return, expiration or are nft/alias/foundry outputs.
account.set_default_sync_options(
SyncOptions(sync_only_most_basic_outputs=True))

print(created_account)
25 changes: 25 additions & 0 deletions bindings/python/examples/exchange/2_generate_address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example generates an address for an account.

from iota_sdk import Wallet
from dotenv import load_dotenv
import os

# This example uses secrets in environment variables for simplicity which should not be done in production.
load_dotenv()

if 'WALLET_DB_PATH' not in os.environ:
raise Exception(".env WALLET_DB_PATH is undefined, see .env.example")
if 'STRONGHOLD_PASSWORD' not in os.environ:
raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example")

wallet = Wallet(os.environ.get('WALLET_DB_PATH'))

wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"])

account = wallet.get_account('Alice')

address = account.generate_ed25519_addresses(1)[0]
print(f'Address:', address['address'])
29 changes: 29 additions & 0 deletions bindings/python/examples/exchange/3_check_balance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example gets the balance of an account.

from iota_sdk import Wallet, SyncOptions
from dotenv import load_dotenv
import os

# This example uses secrets in environment variables for simplicity which should not be done in production.
load_dotenv()

if 'WALLET_DB_PATH' not in os.environ:
raise Exception(".env WALLET_DB_PATH is undefined, see .env.example")

wallet = Wallet(os.environ.get('WALLET_DB_PATH'))

account = wallet.get_account('Alice')

addresses = account.addresses()
print(f'Addresses:', addresses)

# Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked,
# have a storage deposit return, expiration or are nft/alias/foundry outputs.
balance = account.sync(SyncOptions(sync_only_most_basic_outputs=True))
print('Balance', balance)

# Use the faucet to send tokens to your address.
print('Fill your address with the Faucet: https://faucet.testnet.shimmer.network/')
55 changes: 55 additions & 0 deletions bindings/python/examples/exchange/4_listen_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example listens to the NewOutput event.

from iota_sdk import Wallet, SyncOptions
from dotenv import load_dotenv
import json
import os
import time

# This example uses secrets in environment variables for simplicity which should not be done in production.
load_dotenv()

if 'WALLET_DB_PATH' not in os.environ:
raise Exception(".env WALLET_DB_PATH is undefined, see .env.example")

wallet = Wallet(os.environ.get('WALLET_DB_PATH'))

account = wallet.get_account('Alice')

received_event = False


def callback(event):
event_dict = json.loads(event)
print('AccountIndex:', event_dict["accountIndex"])
print('Event:', event_dict["event"])

# Exit after receiving an event.
global received_event
received_event = True


# Only interested in new outputs here.
wallet.listen(callback, [2])

account = wallet.get_account('Alice')

# Use the faucet to send testnet tokens to your address.
print('Fill your address with the faucet: https://faucet.testnet.shimmer.network/')

addresses = account.addresses()
print('Send funds to:', addresses[0]["address"])

# Sync every 5 seconds until the faucet transaction gets confirmed.
for _ in range(100):
if received_event:
exit()
time.sleep(5)

# Sync to detect new outputs
# Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked,
# have a storage deposit return , expiration or are nft/alias/foundry outputs.
account.sync(SyncOptions(sync_only_most_basic_outputs=True))
37 changes: 37 additions & 0 deletions bindings/python/examples/exchange/5_send_amount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

# This example sends tokens to an address.

from iota_sdk import Wallet, SyncOptions
from dotenv import load_dotenv
import os

# This example uses secrets in environment variables for simplicity which should not be done in production.
load_dotenv()

if 'WALLET_DB_PATH' not in os.environ:
raise Exception(".env WALLET_DB_PATH is undefined, see .env.example")
if 'STRONGHOLD_PASSWORD' not in os.environ:
raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example")
if 'EXPLORER_URL' not in os.environ:
raise Exception(".env EXPLORER_URL is undefined, see .env.example")

wallet = Wallet(os.environ.get('WALLET_DB_PATH'))

account = wallet.get_account('Alice')

wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"])

# Set sync_only_most_basic_outputs to True if not interested in outputs that are timelocked,
# have a storage deposit return, expiration or are nft/alias/foundry outputs.
balance = account.sync(SyncOptions(sync_only_most_basic_outputs=True))
print('Balance', balance)

transaction = account.send([{
"address": "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu",
"amount": "1000000",
}])
print(transaction)
print(
f'Check your block on: {os.environ["EXPLORER_URL"]}/block/{transaction.blockId}')
2 changes: 1 addition & 1 deletion bindings/python/iota_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def compute_alias_id(output_id: OutputId) -> HexStr:
"""Computes the alias id for the given alias output id.
"""
return _call_method('computeAliasId', {
'outputId': output_id
'outputId': repr(output_id)
})

@staticmethod
Expand Down
4 changes: 2 additions & 2 deletions bindings/python/iota_sdk/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ def stop_background_sync(self):
'stopBackgroundSync',
)

def listen(self, handler, events: Optional[List[str]] = None):
def listen(self, handler, events: Optional[List[int]] = None):
"""Listen to wallet events, empty array or None will listen to all events
The default value for events is None
"""
events_array = [] if events is None else events
listen_wallet(self.handle, events_array, handler)

def clear_listeners(self, events: Optional[List[str]] = None):
def clear_listeners(self, events: Optional[List[int]] = None):
"""Remove wallet event listeners, empty array or None will remove all listeners
The default value for events is None
"""
Expand Down
12 changes: 7 additions & 5 deletions bindings/python/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use iota_sdk_bindings_core::{
iota_sdk::wallet::{events::types::WalletEventType, Wallet as RustWallet},
Response, WalletMethod, WalletOptions,
};
use pyo3::prelude::*;
use pyo3::{prelude::*, types::PyTuple};
use tokio::sync::RwLock;

use crate::{
Expand Down Expand Up @@ -58,11 +58,11 @@ pub fn call_wallet_method(wallet: &Wallet, method: String) -> Result<String> {

/// Listen to wallet events.
#[pyfunction]
pub fn listen_wallet(wallet: &Wallet, events: Vec<String>, handler: PyObject) {
pub fn listen_wallet(wallet: &Wallet, events: Vec<u8>, handler: PyObject) {
let mut rust_events = Vec::with_capacity(events.len());

for event in events {
let event = match serde_json::from_str::<WalletEventType>(&event) {
let event = match WalletEventType::try_from(event) {
Ok(event) => event,
Err(e) => {
panic!("Wrong event to listen! {e:?}");
Expand All @@ -78,9 +78,11 @@ pub fn listen_wallet(wallet: &Wallet, events: Vec<String>, handler: PyObject) {
.await
.as_ref()
.expect("wallet got destroyed")
.listen(rust_events, move |_| {
.listen(rust_events, move |event| {
let event_string = serde_json::to_string(&event).expect("json to string error");
Python::with_gil(|py| {
handler.call0(py).unwrap();
let args = PyTuple::new(py, &[event_string]);
handler.call1(py, args).expect("failed to call python callback");
});
})
.await;
Expand Down

0 comments on commit 5691911

Please sign in to comment.