Skip to content

Commit c42e525

Browse files
committed
Begin to add invoice.artifact Invoice generation
o Add ERC-20 token pricing oracle and memo-ized querying functions o Add top 100 ERC-20 tokens, and lookup by symbol/name
1 parent ace3080 commit c42e525

File tree

11 files changed

+1486
-81
lines changed

11 files changed

+1486
-81
lines changed

README.org

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,6 +1730,20 @@ sets of 20 words is difficult, error-prone and time consuming.
17301730
the many intermediary banking industry and government parties involved in any traditional
17311731
financial transaction, even if you are not convicted of a crime.
17321732

1733+
Even if such payments are allowed, and none of the counterparties are actively hostile to you, the
1734+
complexity and expense of quoting a price, signing a client, invoicing for payment, confirming the
1735+
validity of the invoice and making the payment, monitoring for incoming payments and associating
1736+
them with the correct invoices, conforming amounts paid are correct, issuing a receipt,
1737+
book-keeping the incoming payment, converting currencies, retaining the correct taxes for each
1738+
counterparty jurisdiction, reconciling books, and finally preparing and filing taxes, and then
1739+
(perhaps years later) defending your accounting decisions against a hostile tax inspector with
1740+
infinite funds to prosecute -- all this makes it virtually impossible for a small business to
1741+
survive. Furthermore, you must accomplish all this, *without error*, while attempting to defend
1742+
yourself against business adversaries with preferential tax treatment, office-towers full of
1743+
lawyers and accountants, for whom the total percentage of gross revenues paid to accomplish
1744+
compliance is less than 1%, while the small business is likely to spend 10% to 25% of
1745+
their entire gross revenue *just* to financial and regulatory compliance overheads.
1746+
17331747
Fortunately, DeFi (Decentralized Finance) provides you with the capability to receive payments,
17341748
quickly and efficiently, from anyone on the planet who wishes to pay you for your services.
17351749

@@ -1804,53 +1818,62 @@ sets of 20 words is difficult, error-prone and time consuming.
18041818
storing an immutable value associated with the "salt" value from which the "Forwarder" account
18051819
address is derived.
18061820

1807-
*** Client "Forwarder" Contract
1821+
*** The Client "Forwarder" Contract
18081822

18091823
Once the product's =MultiPayoutERC20= "Fee Distribution" contract address is identified, the act
18101824
of obtaining a unique client payee "Forwarder" address is simple.
18111825

18121826
1) A "salt" value unique to the client is deduced, usually consisting of "something they know"
1813-
(eg. a Public Key) plus "something they have" (eg. a Machine ID).
1827+
(eg. a Public Key) plus "something they have" (eg. a Machine ID, or a User Name).
18141828
2) The salt is used to deduce the client's unique "Forwarder" address
18151829

1816-
Here's an example (using the Ethereum "Goerli" testnet):
1830+
**** The Product Owner's MultiPayoutERC20 Contract
18171831

1818-
#+LATEX: {\scriptsize
1819-
#+BEGIN_SRC ipython :session :exports both :results output raw drawer
1832+
The creation of a =MultiPayoutERC20= is simple.
18201833

1821-
# Provide yourself with a Goerli testnet account under your control;
1822-
# provide an "xpub..." key for it, or the BIP-39 Mnemonic phrase to
1823-
# derive its HD wallet. Use the https://goerlifaucet.com to fund the
1824-
# account with some Goerli test Ethereum; requires you to set up an
1825-
# https://alchemy.com account, and put your API token in the
1826-
# ALCHEMY_API_TOKEN environment variable.
1827-
goerli_xprvkey = os.getenv( 'GOERLI_XPRVKEY' )
1828-
if not goerli_xprvkey:
1829-
goerli_seed = os.getenv( 'GOERLI_SEED' )
1830-
if goerli_seed:
1831-
try:
1832-
# why m/44'/1'/... instead of m/44'/60'/...? Dunno;
1833-
# That's the derivation path that Trezor Suite uses for
1834-
# Goerli testnet wallets...
1835-
goerli_xprvkey = slip39.account(
1836-
goerli_seed, crypto="ETH", path="m/44'/1'/0'"
1837-
).xprvkey
1838-
except Exception:
1839-
pass
1840-
if goerli_xprvkey:
1841-
# the Account.address/.prvkey
1842-
goerli_src = slip39.account(
1843-
goerli_xprvkey, crypto='ETH', path="m/0/0"
1844-
)
1845-
1846-
from slip39.invoice import MultiPayoutERC20
1847-
1848-
mp = MultiPayoutERC20(
1849-
agent = goerli_src.address,
1850-
agent_prvkey = goerli_src.prvkey,
1851-
address = "0xb2D03aD9a84F0E10697BF2CDc2B98765688134d8",
1852-
)
1853-
salt
1834+
The product owner must know the Ethereum addresses of the payees, and each payee's proportion
1835+
of the product revenue. A payee may be another MultiPayoutERC20 contract (eg. for a product
1836+
module sub-license), which may in turn have its own payees.
1837+
1838+
An Ethereum account containing sufficient funds to establish the MultiPayoutERC20 contract must
1839+
be available. Here's an example (using the Ethereum "Goerli" testnet):
1840+
1841+
#+LATEX: {\scriptsize
1842+
#+BEGIN_SRC ipython :session :exports both :results output raw drawer
1843+
1844+
# Provide yourself with a Goerli testnet account under your control;
1845+
# provide an "xpub..." key for it, or the BIP-39 Mnemonic phrase to
1846+
# derive its HD wallet. Use the https://goerlifaucet.com to fund the
1847+
# account with some Goerli test Ethereum; requires you to set up an
1848+
# https://alchemy.com account, and put your API token in the
1849+
# ALCHEMY_API_TOKEN environment variable.
1850+
goerli_xprvkey = os.getenv( 'GOERLI_XPRVKEY' )
1851+
if not goerli_xprvkey:
1852+
goerli_seed = os.getenv( 'GOERLI_SEED' )
1853+
if goerli_seed:
1854+
try:
1855+
# why m/44'/1'/... instead of m/44'/60'/...? Dunno;
1856+
# That's the derivation path that Trezor Suite uses for
1857+
# Goerli testnet wallets...
1858+
goerli_xprvkey = slip39.account(
1859+
goerli_seed, crypto="ETH", path="m/44'/1'/0'"
1860+
).xprvkey
1861+
except Exception:
1862+
pass
1863+
if goerli_xprvkey:
1864+
# the Account.address/.prvkey
1865+
goerli_src = slip39.account(
1866+
goerli_xprvkey, crypto='ETH', path="m/0/0"
1867+
)
1868+
1869+
from slip39.invoice import MultiPayoutERC20
1870+
1871+
mp = MultiPayoutERC20(
1872+
agent = goerli_src.address,
1873+
agent_prvkey = goerli_src.prvkey,
1874+
address = "0xb2D03aD9a84F0E10697BF2CDc2B98765688134d8",
1875+
)
1876+
salt
18541877

18551878
#+END_SRC
18561879
#+LATEX: }
@@ -1863,8 +1886,8 @@ sets of 20 words is difficult, error-prone and time consuming.
18631886
payments to a plain Account (for which you hold the private key) may not be acceptable to all
18641887
parties involved. A product owner providing a licensee the capability to sub-licensing their
18651888
product may, for example, charge a much better fee, if the licensee can prove that payments will
1866-
*automatically* flow back to the licensor, every time the licensee sells their product which
1867-
contains the sub-license.
1889+
*automatically* flow back to the license owner, every time the licensee sells their product
1890+
which contains the sub-license.
18681891

18691892
There are ways to ensure that each client payment *must* be distributed to each payee, as agreed,
18701893
using cryptocurrencies which implement Smart Contracts.

slip39/api.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
scrypt = None
5959
message = f"Unable to support Paper Wallet output: {exc}"
6060
warnings.warn( message, ImportWarning )
61-
log.warning( message )
6261
if log.isEnabledFor( logging.DEBUG ):
6362
log.exception( message )
6463
paper_wallet_issues.append( message )
@@ -69,7 +68,6 @@
6968
eth_account = None
7069
message = f"Unable to support Paper Wallet output: {exc}"
7170
warnings.warn( message, ImportWarning )
72-
log.warning( message )
7371
if log.isEnabledFor( logging.DEBUG ):
7472
log.exception( message )
7573
paper_wallet_issues.append( message )

slip39/communications.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@ def autoresponder( address, from_addr, to_addrs, server, port, reinject ):
610610
611611
Install python-slip39 (no extras required for just slip39.communications)
612612
613-
# python3.10 -m pip install https://github.com/pjkundert/python-slip39/archive/feature-invoice.zip#egg=slip39
613+
# rm -rf ~/.cache/pip
614+
# python3.10 -m pip install --upgrade https://github.com/pjkundert/python-slip39/archive/feature-invoice.zip#egg=slip39
614615
615616
Configure Postfix system roughly as per: https://github.com/innovara/autoreply,
616617
to run our slip39.communications autoresponder
@@ -622,7 +623,7 @@ def autoresponder( address, from_addr, to_addrs, server, port, reinject ):
622623
623624
# autoresponder pipe
624625
autoreply unix - n n - - pipe
625-
flags= user=autoreply null_sender=
626+
flags= user=nobody null_sender=
626627
argv=/usr/local/bin/python3.10 -m slip39.communications autoresponder [email protected] ${sender} ${recipient}
627628
628629
- Create /etc/postfix/autoreply w/ lines like (and postmap /etc/postfix/autoreply):

slip39/communications_test.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
# No local key (the usual case, unless you're Dominion R&D!)
2929
dkim_selector = '20221230'
3030
dkim_msg = message_from_string( """\
31+
Content-Type: multipart/alternative; boundary================1903566236404015660==
3132
MIME-Version: 1.0
3233
3334
3435
3536
Subject: Hello, world!
36-
Content-Type: multipart/alternative; boundary================1903566236404015660==
3737
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=licensing.dominionrnd.com; [email protected]; q=dns/txt; s=20221230; t=1673405994; h=from : to;\
3838
bh=RkF6KP4Q94MVDBEv7pluaWdzw0z0GNQxK72rU02XNcE=; b=Tao30CJGcqyX86f37pSrSFLSDvA8VkzQW0jiMf+aFg5D99LsUYmUZxSgnDhW2ZEzjwu6bzjkEEyvSEv8LxfDUW+AZZG3enbq/mnnUZw3PXp4l\
3939
MaZGN9whvTIUy4/QUlMGKuf+7Vzi+8eKKjh4CWKN/UEyX6YoU7V5eyjTTA7q1jIjEl8jiM4LXYEFQ9LaKUmqqmRh2OkxBVf1QG+fEYTYUed+oS05m/d1SyVLjxv8ldeXT/mGgm1CrGk1qfRTzfcksX4qNAluTfJTa\
@@ -71,19 +71,22 @@ def test_communications_matchaddr():
7171

7272

7373
def test_communications_dkim():
74-
msg = dkim_msg if not dkim_key else dkim_message(
75-
sender_email = SMTP_FROM, # Message From: specifies claimed sender
76-
to_email = SMTP_TO, # See https://dkimvalidator.com to test!
77-
reply_to_email = "[email protected]",
78-
subject = "Hello, world!",
79-
message_text = "Testing 123",
80-
message_html = "<em>Testing 123</em>",
81-
dkim_private_key_path = dkim_key,
82-
dkim_selector = dkim_selector,
83-
headers = ['From', 'To'],
84-
)
74+
if dkim_key:
75+
msg = dkim_message(
76+
sender_email = SMTP_FROM, # Message From: specifies claimed sender
77+
to_email = SMTP_TO, # See https://dkimvalidator.com to test!
78+
reply_to_email = "[email protected]",
79+
subject = "Hello, world!",
80+
message_text = "Testing 123",
81+
message_html = "<em>Testing 123</em>",
82+
dkim_private_key_path = dkim_key,
83+
dkim_selector = dkim_selector,
84+
headers = ['From', 'To'],
85+
)
86+
else:
87+
msg = dkim_msg
8588

86-
log.info( f"DKIM Message: {msg}" )
89+
log.info( f"DKIM {'signed' if dkim_key else 'canned'} Message:\n{msg}" )
8790

8891
sig = msg['DKIM-Signature']
8992
sig_kvs = sig.split( ';' )
@@ -136,17 +139,20 @@ def test_communications_autoresponder( monkeypatch ):
136139
the auto-forwarded message(s).
137140
138141
"""
139-
msg = dkim_msg if not dkim_key else dkim_message(
140-
sender_email = SMTP_FROM, # Message From: specifies claimed sender
141-
to_email = SMTP_TO, # See https://dkimvalidator.com to test!
142-
reply_to_email = "[email protected]",
143-
subject = "Hello, world!",
144-
message_text = "Testing 123",
145-
message_html = "<em>Testing 123</em>",
146-
dkim_private_key_path = dkim_key,
147-
dkim_selector = dkim_selector,
148-
headers = ['From', 'To'],
149-
)
142+
if dkim_key:
143+
msg = dkim_message(
144+
sender_email = SMTP_FROM, # Message From: specifies claimed sender
145+
to_email = SMTP_TO, # See https://dkimvalidator.com to test!
146+
reply_to_email = "[email protected]",
147+
subject = "Hello, world!",
148+
message_text = "Testing 123",
149+
message_html = "<em>Testing 123</em>",
150+
dkim_private_key_path = dkim_key,
151+
dkim_selector = dkim_selector,
152+
headers = ['From', 'To'],
153+
)
154+
else:
155+
msg = dkim_msg
150156

151157
envelopes = []
152158

slip39/defaults.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@
163163
# We'll default to 30-second intervals for querying Etherscan for Gas, ETH, ERC-20 Pricing info
164164
ETHERSCAN_MEMO_MAXAGE = 30
165165
ETHERSCAN_MEMO_MAXSIZE = None
166-
166+
# For token prices, default to 5 minute refreshes
167+
TOKPRICES_MEMO_MAXAGE = 5*60
168+
TOKPRICES_MEMO_MAXSIZE = None
167169

168170
SMTP_TO = "[email protected]"
169171
SMTP_FROM = "[email protected]"

0 commit comments

Comments
 (0)