Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fix XLM withdrawal & support XDB #38

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ venv.bak/

# mypy
.mypy_cache/

.DS_Store
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ termcolor==1.1.0
substrate-interface==1.1.4
py-algorand-sdk==1.8.0
solana==0.19.1
stellar_base==1.1.2.0
stellar-sdk==8.1.1
requests==2.28.1
crc16==0.1.1
gql==3.4.0
cbor2==5.4.3
bech32==1.2.0
123 changes: 88 additions & 35 deletions utils/xlm_helpers.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
import base64
import struct
from stellar_base.builder import Builder
from stellar_base.address import Address
from stellar_base.stellarxdr import Xdr
from stellar_base.keypair import Keypair
import requests
from crc16 import crc16xmodem

from stellar_sdk import stellar_xdr, Server, Network, Asset, Keypair, SignerKey, SignerKeyType, TransactionBuilder
from utils import eddsa_sign

BIP_44_CONSTANT = 44
XLM_ASSET_NUM = 146
CHANGE = 0
ADDR_INDEX = 0

"""
Set the variables `NETWORK_PASSPHRASE`, `RPC_URL`, and `EXPLORER_URL` for the appropriate chain.
"""

# Network passphrase
XLM_LIVENET_PASSPHRASE = Network.PUBLIC_NETWORK_PASSPHRASE
XLM_TESTNET_PASSPHRASE = Network.TESTNET_NETWORK_PASSPHRASE
XDB_LIVENET_PASSPHRASE = 'LiveNet Global DigitalBits Network ; February 2021'
XDB_TESTNET_PASSPHRASE = 'TestNet Global DigitalBits Network ; December 2020'
NETWORK_PASSPHRASE = XLM_LIVENET_PASSPHRASE

# RPC URL
XLM_LIVENET_RPC_URL = 'https://horizon.stellar.org'
XLM_TESTNET_RPC_URL = 'https://horizon-testnet.stellar.org'
XDB_LIVENET_RPC_URL = 'https://frontier.livenet.digitalbits.io'
XDB_TESTNET_RPC_URL = 'https://frontier.testnet.digitalbits.io'
RPC_URL = XLM_LIVENET_RPC_URL

# Explorer URL
XLM_EXPLORER_URL = 'https://stellarchain.io/transactions'
XDB_EXPLORER_URL = 'https://xdbexplorer.com/transaction'
EXPLORER_URL = XLM_EXPLORER_URL


class RawKeypair(Keypair):
def __init__(self, signing_key) -> None:
self.signing_key = signing_key
self.verify_key = eddsa_sign.private_key_to_public_key(signing_key)

def xdr_public_key(self) -> Xdr.types.PublicKey:
return Xdr.types.PublicKey(Xdr.const.KEY_TYPE_ED25519, self.verify_key)

def xdr_signing_key(self) -> SignerKey:
return SignerKey(self.signing_key, SignerKeyType.SIGNER_KEY_TYPE_ED25519)

def xdr_public_key(self) -> stellar_xdr.PublicKey:
return stellar_xdr.PublicKey(SignerKeyType.SIGNER_KEY_TYPE_ED25519, self.verify_key)

def raw_public_key(self) -> bytes:
return self.verify_key
Expand All @@ -30,49 +50,82 @@ def signature_hint(self) -> bytes:
def sign(self, data: bytes) -> bytes:
return eddsa_sign.eddsa_sign(self.signing_key, data)

def withdraw(key, to_address, amount = None, dst_tag = None):

def withdraw(key, to_address, amount=None, dst_tag=None):
server = Server(horizon_url=RPC_URL)

keypair = RawKeypair(key)
builder = Builder(address=public_key_to_address(keypair.raw_public_key()), network='PUBLIC')

builder = TransactionBuilder(
source_account=server.load_account(keypair),
network_passphrase=NETWORK_PASSPHRASE,
base_fee=100
)

if amount is None:
builder.append_account_merge_op(to_address)
else:
builder.append_payment_op(to_address, str(round(float(amount),7)), 'XLM')
builder.append_payment_op(
to_address, Asset.native(), str(round(float(amount), 7)))

if dst_tag is not None:
builder.add_text_memo(dst_tag)
builder.keypair = keypair
builder.sign()
ret = builder.submit()
if ret['successful']:
return ret['hash']
print(ret)
return None

def getBalance(addr):
address = Address(address=addr, network='PUBLIC')
address.get()
builder.add_text_memo(dst_tag)

tx = builder.build()

tx.sign(keypair)

res = server.submit_transaction(tx)

if res['successful']:
print(f"{EXPLORER_URL}/{res['hash']}")
return res['hash']
print(res)


def get_balance(addr):
url = f'{RPC_URL}/accounts/{addr}'

res = requests.get(url)

if res.status_code == 404:
raise Exception('Account not found')

account = res.json()

balance = 0
for b in address.balances:

for b in account['balances']:
if b['asset_type'] == 'native':
balance = float(b['balance'])
break

return balance


def public_key_to_address(public_key: bytes) -> str:
if public_key is None:
raise ValueError("cannot encode null public_key")

version_byte = b'0'
payload = version_byte + public_key
crc = struct.pack("<H", crc16xmodem(payload))
return base64.b32encode(payload + crc).decode("utf-8").rstrip("=")
unpacked_crc = crc16xmodem(payload)
crc = struct.pack("<H", unpacked_crc)
addr = base64.b32encode(payload + crc).decode("utf-8").rstrip("=")
return addr


def xpub_to_address(xpub: str, account: int) -> str:
path = f'{BIP_44_CONSTANT}/{XLM_ASSET_NUM}/{account}/{CHANGE}/{ADDR_INDEX}'
_, pub = eddsa_sign.eddsa_derive(xpub, path)
def get_derivation_path(account: int) -> str:
return f'44/146/{account}/0/0'


def xpub_to_address(xdb_pub: str, account: int) -> str:
path = get_derivation_path(account)
_, pub = eddsa_sign.eddsa_derive(xdb_pub, path)
return public_key_to_address(pub)

def withdraw_from_account(xpriv: str, account: int, to_address : str, amount: float = None, dst_tag: str = None) -> str:
path = f'{BIP_44_CONSTANT}/{XLM_ASSET_NUM}/{account}/{CHANGE}/{ADDR_INDEX}'

def withdraw_from_account(xpriv: str, account: int, to_address: str, amount: float = None, dst_tag: str = None) -> str:
path = get_derivation_path(account)
priv, _ = eddsa_sign.eddsa_derive(xpriv, path)
return withdraw(priv, to_address, amount, dst_tag)