From 0db95e87a6d980012346f626f4a89b8685102a53 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Wed, 12 Aug 2026 18:24:38 +0000 Subject: [PATCH 1/4] SYN-11361: Add crypto fee payment and issuer freeze/seize effect forms - Add ``crypto:payment:fee`` form to represent the payer and value of a transaction fee (the ``crypto:currency:transaction:fee`` prop remains the shorthand total). - Add ``crypto:smart:effect:freeze`` effect form for issuer blocklist freeze/unfreeze actions. - Add ``crypto:smart:effect:seize`` effect form for issuer destruction of the tokens held by an address. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019GyuV9SWnKTZyg8VoPtsYz --- synapse/models/crypto.py | 37 ++++++++++++++++++++++++ synapse/tests/test_model_crypto.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/synapse/models/crypto.py b/synapse/models/crypto.py index d582e0afd8..34388d37cf 100644 --- a/synapse/models/crypto.py +++ b/synapse/models/crypto.py @@ -86,6 +86,14 @@ def getModelDefs(self): 'doc': 'A smart contract effect which grants a non-owner address the ability to manipulate fungible tokens.', 'interfaces': ('crypto:smart:effect',), }), + ('crypto:smart:effect:freeze', ('guid', {}), { + 'doc': 'A smart contract effect which freezes or unfreezes an address on an issuer blocklist.', + 'interfaces': ('crypto:smart:effect',), + }), + ('crypto:smart:effect:seize', ('guid', {}), { + 'doc': 'A smart contract effect which destroys the tokens held by an address.', + 'interfaces': ('crypto:smart:effect',), + }), # TODO crypto:smart:effect:call - call another smart contract # TODO crypto:smart:effect:giveproxy - grant your proxy for a token based vote ('crypto:payment:input', ('guid', {}), { @@ -94,6 +102,9 @@ def getModelDefs(self): ('crypto:payment:output', ('guid', {}), { 'doc': 'A payment received from a transaction.', }), + ('crypto:payment:fee', ('guid', {}), { + 'doc': 'A fee paid to execute a transaction.', + }), ('crypto:smart:token', ('comp', {'fields': (('contract', 'crypto:smart:contract'), ('tokenid', 'hugenum'))}), { 'doc': 'A token managed by a smart contract.', }), @@ -222,6 +233,14 @@ def getModelDefs(self): ('value', ('econ:price', {}), { 'doc': 'The value of the currency received from the transaction.'}), )), + ('crypto:payment:fee', {}, ( + ('transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction the fee was paid for.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which paid the fee.'}), + ('value', ('econ:price', {}), { + 'doc': 'The value of the fee paid to execute the transaction.'}), + )), ('crypto:currency:transaction', {}, ( ('hash', ('hex', {}), { 'doc': 'The unique transaction hash for the transaction.'}), @@ -398,6 +417,24 @@ def getModelDefs(self): 'doc': 'The hex encoded amount of tokens the proxy is allowed to manipulate.'}), )), + ('crypto:smart:effect:freeze', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which enforces the freeze.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which was frozen or unfrozen.'}), + ('frozen', ('bool', {}), { + 'doc': 'Set to true if the address was frozen or false if it was unfrozen.'}), + )), + + ('crypto:smart:effect:seize', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the tokens.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address whose tokens were destroyed.'}), + ('amount', ('hugenum', {}), { + 'doc': 'The amount of tokens destroyed.'}), + )), + ('crypto:currency:address', {}, ( ('coin', ('crypto:currency:coin', {}), { 'doc': 'The crypto coin to which the address belongs.', 'ro': True, }), diff --git a/synapse/tests/test_model_crypto.py b/synapse/tests/test_model_crypto.py index c93350f932..d694f0fc5c 100644 --- a/synapse/tests/test_model_crypto.py +++ b/synapse/tests/test_model_crypto.py @@ -121,6 +121,13 @@ async def test_model_crypto_currency(self): self.eq(payees[0].get('index'), 0) self.eq(payees[0].get('address'), ('btc', '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')) + payfees = await core.nodes('[ crypto:payment:fee=(t1, feepayer) :transaction=(t1,) :address=(btc, 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2) :value=0.0001 ]') + self.len(1, payfees) + self.eq(payfees[0].get('value'), '0.0001') + self.eq(payfees[0].get('address'), ('btc', '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')) + self.len(1, await core.nodes('crypto:payment:fee -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:payment:fee -> crypto:currency:address')) + payor = payors[0].ndef[1] payee = payees[0].ndef[1] @@ -355,6 +362,44 @@ async def test_model_crypto_currency(self): self.len(1, await core.nodes('crypto:smart:effect:proxytokens -> crypto:currency:transaction')) self.len(1, await core.nodes('crypto:smart:effect:proxytokens -> crypto:smart:contract')) + nodes = await core.nodes(''' + [ + crypto:smart:effect:freeze=* + :index=0 + :transaction=* + :contract=* + :address=(eth, 0xd73d44149deb8aabd4b53197654a286dedfda827) + :frozen=$lib.true + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 0) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0xd73d44149deb8aabd4b53197654a286dedfda827')) + self.true(node.get('frozen')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:smart:effect:freeze -> crypto:smart:contract')) + + nodes = await core.nodes(''' + [ + crypto:smart:effect:seize=* + :index=0 + :transaction=* + :contract=* + :address=(eth, 0xd73d44149deb8aabd4b53197654a286dedfda827) + :amount=960007.90 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 0) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0xd73d44149deb8aabd4b53197654a286dedfda827')) + self.eq(node.get('amount'), '960007.9') + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:currency:transaction')) + self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:smart:contract')) + nodes = await core.nodes(''' [ crypto:smart:token=(2bdea834252a220b61aadf592cc0de66, 30) From 605bf493f60a1f97bdee8ac8067060ca835a1e75 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Thu, 13 Aug 2026 12:55:30 +0000 Subject: [PATCH 2/4] SYN-11361: Add crypto swaptokens effect and cross-chain bridge swap forms - Add ``crypto:smart:effect:swaptokens`` effect form to represent a DEX/AMM swap as a distinct fact (a swap is not derivable from the individual token transfers). A null ``:sent:contract`` / ``:received:contract`` indicates a native currency leg. - Add ``crypto:currency:bridge:swap`` form to represent a cross-chain swap which links the origin and destination transactions on different chains, enabling pivots between the two legs. Destination properties other than the transaction are optional since a matched swap may lack destination details. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019GyuV9SWnKTZyg8VoPtsYz --- synapse/models/crypto.py | 45 ++++++++++++++++++ synapse/tests/test_model_crypto.py | 75 ++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/synapse/models/crypto.py b/synapse/models/crypto.py index 34388d37cf..1719a9afca 100644 --- a/synapse/models/crypto.py +++ b/synapse/models/crypto.py @@ -45,6 +45,9 @@ def getModelDefs(self): ('crypto:currency:transaction', ('guid', {}), { 'doc': 'An individual crypto currency transaction recorded on the blockchain.', }), + ('crypto:currency:bridge:swap', ('guid', {}), { + 'doc': 'A cross-chain swap which bridges value between two transactions on different chains.', + }), ('crypto:currency:block', ('comp', {'fields': ( ('coin', 'crypto:currency:coin'), ('offset', 'int'), @@ -94,6 +97,10 @@ def getModelDefs(self): 'doc': 'A smart contract effect which destroys the tokens held by an address.', 'interfaces': ('crypto:smart:effect',), }), + ('crypto:smart:effect:swaptokens', ('guid', {}), { + 'doc': 'A smart contract effect which swaps one token or currency for another.', + 'interfaces': ('crypto:smart:effect',), + }), # TODO crypto:smart:effect:call - call another smart contract # TODO crypto:smart:effect:giveproxy - grant your proxy for a token based vote ('crypto:payment:input', ('guid', {}), { @@ -293,6 +300,29 @@ def getModelDefs(self): # TODO break out args/retvals and maybe make humon repr? )), + ('crypto:currency:bridge:swap', {}, ( + ('name', ('str', {'lower': True, 'strip': True}), { + 'doc': 'The name of the bridge protocol which executed the swap.'}), + ('matched', ('bool', {}), { + 'doc': 'Set to true if the two transactions were matched together by an external observer.'}), + ('origin:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which sent value on the origin chain.'}), + ('origin:address', ('crypto:currency:address', {}), { + 'doc': 'The address which sent value on the origin chain.'}), + ('origin:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the tokens sent on the origin chain. This is not set if the sent value was a native currency.'}), + ('origin:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency sent on the origin chain.'}), + ('destination:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which received value on the destination chain.'}), + ('destination:address', ('crypto:currency:address', {}), { + 'doc': 'The address which received value on the destination chain.'}), + ('destination:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the tokens received on the destination chain. This is not set if the received value was a native currency.'}), + ('destination:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency received on the destination chain.'}), + )), + ('crypto:currency:block', {}, ( ('coin', ('crypto:currency:coin', {}), { 'doc': 'The coin/blockchain this block resides on.', 'ro': True, }), @@ -435,6 +465,21 @@ def getModelDefs(self): 'doc': 'The amount of tokens destroyed.'}), )), + ('crypto:smart:effect:swaptokens', {}, ( + ('contract', ('crypto:smart:contract', {}), { + 'doc': 'The pool or router contract which executed the swap.'}), + ('address', ('crypto:currency:address', {}), { + 'doc': 'The address which swapped the tokens.'}), + ('sent:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the sent tokens. This is not set if the sent value was a native currency.'}), + ('sent:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency sent by the address.'}), + ('received:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the received tokens. This is not set if the received value was a native currency.'}), + ('received:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency received by the address.'}), + )), + ('crypto:currency:address', {}, ( ('coin', ('crypto:currency:coin', {}), { 'doc': 'The crypto coin to which the address belongs.', 'ro': True, }), diff --git a/synapse/tests/test_model_crypto.py b/synapse/tests/test_model_crypto.py index d694f0fc5c..d81d117beb 100644 --- a/synapse/tests/test_model_crypto.py +++ b/synapse/tests/test_model_crypto.py @@ -400,6 +400,81 @@ async def test_model_crypto_currency(self): self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:currency:transaction')) self.len(1, await core.nodes('crypto:smart:effect:seize -> crypto:smart:contract')) + nodes = await core.nodes(''' + [ + crypto:smart:effect:swaptokens=* + :index=179 + :transaction=* + :contract=* + :address=(eth, 0x241ebd14e40899ddb1191c140f85c37917b0505b) + :sent:contract=* + :sent:amount=0.025194371619325567 + :received:contract=* + :received:amount=22574.721 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('index'), 179) + self.nn(node.get('contract')) + self.eq(node.get('address'), ('eth', '0x241ebd14e40899ddb1191c140f85c37917b0505b')) + self.nn(node.get('sent:contract')) + self.eq(node.get('sent:amount'), '0.025194371619325567') + self.nn(node.get('received:contract')) + self.eq(node.get('received:amount'), '22574.721') + self.len(1, await core.nodes('crypto:smart:effect:swaptokens -> crypto:currency:address')) + self.len(1, await core.nodes('crypto:smart:effect:swaptokens -> crypto:currency:transaction')) + self.len(3, await core.nodes('crypto:smart:effect:swaptokens -> crypto:smart:contract')) + + nodes = await core.nodes(''' + [ + crypto:currency:bridge:swap=(bridge1,) + :name="deBridge.finance" + :matched=true + :origin:transaction=(otxn,) + :origin:address=(eth, 0xaaaa) + :origin:contract=* + :origin:amount=1.5 + :destination:transaction=(dtxn,) + :destination:address=(bsc, 0xbbbb) + :destination:contract=* + :destination:amount=1490.0 + ]''') + self.len(1, nodes) + node = nodes[0] + self.eq(node.get('name'), 'debridge.finance') + self.eq(node.get('matched'), True) + self.eq(node.get('origin:address'), ('eth', '0xaaaa')) + self.nn(node.get('origin:contract')) + self.eq(node.get('origin:amount'), '1.5') + self.eq(node.get('destination:address'), ('bsc', '0xbbbb')) + self.nn(node.get('destination:contract')) + self.eq(node.get('destination:amount'), '1490') + + # the swap is reachable from either leg via its indexed transaction prop, and bridges to the other leg + self.len(1, await core.nodes('crypto:currency:bridge:swap:origin:transaction=(otxn,)')) + self.len(1, await core.nodes('crypto:currency:bridge:swap:destination:transaction=(dtxn,)')) + self.len(2, await core.nodes('crypto:currency:bridge:swap=(bridge1,) -> crypto:currency:transaction')) + nodes = await core.nodes('crypto:currency:bridge:swap:origin:transaction=(otxn,) :destination:transaction -> crypto:currency:transaction') + self.len(1, nodes) + self.eq(nodes[0].ndef, ('crypto:currency:transaction', s_common.guid(('dtxn',)))) + + # is:matched does not imply the destination leg is populated + nodes = await core.nodes(''' + [ + crypto:currency:bridge:swap=(bridge2,) + :matched=true + :origin:transaction=(otxn2,) + :origin:address=(eth, 0xcccc) + :origin:amount=2.0 + :destination:transaction=(dtxn2,) + ]''') + self.len(1, nodes) + node = nodes[0] + self.nn(node.get('destination:transaction')) + self.none(node.get('destination:address')) + self.none(node.get('destination:contract')) + self.none(node.get('destination:amount')) + nodes = await core.nodes(''' [ crypto:smart:token=(2bdea834252a220b61aadf592cc0de66, 30) From cdeeca9e1bfa5d2236cd7c1a591a32ad55b097c9 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Thu, 13 Aug 2026 13:02:22 +0000 Subject: [PATCH 3/4] SYN-11361: Preserve case on crypto:currency:bridge:swap:name Match the 3.x ``base:name`` behavior (case-preserving) so the same bridge protocol name normalizes identically on both version lines. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019GyuV9SWnKTZyg8VoPtsYz --- synapse/models/crypto.py | 2 +- synapse/tests/test_model_crypto.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/models/crypto.py b/synapse/models/crypto.py index 1719a9afca..17bfd7b7af 100644 --- a/synapse/models/crypto.py +++ b/synapse/models/crypto.py @@ -301,7 +301,7 @@ def getModelDefs(self): )), ('crypto:currency:bridge:swap', {}, ( - ('name', ('str', {'lower': True, 'strip': True}), { + ('name', ('str', {'strip': True}), { 'doc': 'The name of the bridge protocol which executed the swap.'}), ('matched', ('bool', {}), { 'doc': 'Set to true if the two transactions were matched together by an external observer.'}), diff --git a/synapse/tests/test_model_crypto.py b/synapse/tests/test_model_crypto.py index d81d117beb..c81d1f512f 100644 --- a/synapse/tests/test_model_crypto.py +++ b/synapse/tests/test_model_crypto.py @@ -441,7 +441,7 @@ async def test_model_crypto_currency(self): ]''') self.len(1, nodes) node = nodes[0] - self.eq(node.get('name'), 'debridge.finance') + self.eq(node.get('name'), 'deBridge.finance') self.eq(node.get('matched'), True) self.eq(node.get('origin:address'), ('eth', '0xaaaa')) self.nn(node.get('origin:contract')) From ac6b7d75c8f863d69a316de5f08f1ebd3d073dd3 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Tue, 18 Aug 2026 12:34:30 +0000 Subject: [PATCH 4/4] SYN-11361: Rename bridge swap origin/destination to source/target Use the house source/target convention for the crypto:currency:bridge:swap leg properties instead of origin/destination. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019GyuV9SWnKTZyg8VoPtsYz --- synapse/models/crypto.py | 32 +++++++++--------- synapse/tests/test_model_crypto.py | 52 +++++++++++++++--------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/synapse/models/crypto.py b/synapse/models/crypto.py index 17bfd7b7af..209eae7570 100644 --- a/synapse/models/crypto.py +++ b/synapse/models/crypto.py @@ -305,22 +305,22 @@ def getModelDefs(self): 'doc': 'The name of the bridge protocol which executed the swap.'}), ('matched', ('bool', {}), { 'doc': 'Set to true if the two transactions were matched together by an external observer.'}), - ('origin:transaction', ('crypto:currency:transaction', {}), { - 'doc': 'The transaction which sent value on the origin chain.'}), - ('origin:address', ('crypto:currency:address', {}), { - 'doc': 'The address which sent value on the origin chain.'}), - ('origin:contract', ('crypto:smart:contract', {}), { - 'doc': 'The contract which defines the tokens sent on the origin chain. This is not set if the sent value was a native currency.'}), - ('origin:amount', ('hugenum', {}), { - 'doc': 'The amount of tokens or currency sent on the origin chain.'}), - ('destination:transaction', ('crypto:currency:transaction', {}), { - 'doc': 'The transaction which received value on the destination chain.'}), - ('destination:address', ('crypto:currency:address', {}), { - 'doc': 'The address which received value on the destination chain.'}), - ('destination:contract', ('crypto:smart:contract', {}), { - 'doc': 'The contract which defines the tokens received on the destination chain. This is not set if the received value was a native currency.'}), - ('destination:amount', ('hugenum', {}), { - 'doc': 'The amount of tokens or currency received on the destination chain.'}), + ('source:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which sent the bridged value.'}), + ('source:address', ('crypto:currency:address', {}), { + 'doc': 'The address which sent the bridged value.'}), + ('source:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the sent tokens. This is not set if the sent value was a native currency.'}), + ('source:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency sent.'}), + ('target:transaction', ('crypto:currency:transaction', {}), { + 'doc': 'The transaction which received the bridged value.'}), + ('target:address', ('crypto:currency:address', {}), { + 'doc': 'The address which received the bridged value.'}), + ('target:contract', ('crypto:smart:contract', {}), { + 'doc': 'The contract which defines the received tokens. This is not set if the received value was a native currency.'}), + ('target:amount', ('hugenum', {}), { + 'doc': 'The amount of tokens or currency received.'}), )), ('crypto:currency:block', {}, ( diff --git a/synapse/tests/test_model_crypto.py b/synapse/tests/test_model_crypto.py index c81d1f512f..25b37a30ee 100644 --- a/synapse/tests/test_model_crypto.py +++ b/synapse/tests/test_model_crypto.py @@ -430,50 +430,50 @@ async def test_model_crypto_currency(self): crypto:currency:bridge:swap=(bridge1,) :name="deBridge.finance" :matched=true - :origin:transaction=(otxn,) - :origin:address=(eth, 0xaaaa) - :origin:contract=* - :origin:amount=1.5 - :destination:transaction=(dtxn,) - :destination:address=(bsc, 0xbbbb) - :destination:contract=* - :destination:amount=1490.0 + :source:transaction=(otxn,) + :source:address=(eth, 0xaaaa) + :source:contract=* + :source:amount=1.5 + :target:transaction=(dtxn,) + :target:address=(bsc, 0xbbbb) + :target:contract=* + :target:amount=1490.0 ]''') self.len(1, nodes) node = nodes[0] self.eq(node.get('name'), 'deBridge.finance') self.eq(node.get('matched'), True) - self.eq(node.get('origin:address'), ('eth', '0xaaaa')) - self.nn(node.get('origin:contract')) - self.eq(node.get('origin:amount'), '1.5') - self.eq(node.get('destination:address'), ('bsc', '0xbbbb')) - self.nn(node.get('destination:contract')) - self.eq(node.get('destination:amount'), '1490') + self.eq(node.get('source:address'), ('eth', '0xaaaa')) + self.nn(node.get('source:contract')) + self.eq(node.get('source:amount'), '1.5') + self.eq(node.get('target:address'), ('bsc', '0xbbbb')) + self.nn(node.get('target:contract')) + self.eq(node.get('target:amount'), '1490') # the swap is reachable from either leg via its indexed transaction prop, and bridges to the other leg - self.len(1, await core.nodes('crypto:currency:bridge:swap:origin:transaction=(otxn,)')) - self.len(1, await core.nodes('crypto:currency:bridge:swap:destination:transaction=(dtxn,)')) + self.len(1, await core.nodes('crypto:currency:bridge:swap:source:transaction=(otxn,)')) + self.len(1, await core.nodes('crypto:currency:bridge:swap:target:transaction=(dtxn,)')) self.len(2, await core.nodes('crypto:currency:bridge:swap=(bridge1,) -> crypto:currency:transaction')) - nodes = await core.nodes('crypto:currency:bridge:swap:origin:transaction=(otxn,) :destination:transaction -> crypto:currency:transaction') + nodes = await core.nodes('crypto:currency:bridge:swap:source:transaction=(otxn,) :target:transaction -> crypto:currency:transaction') self.len(1, nodes) self.eq(nodes[0].ndef, ('crypto:currency:transaction', s_common.guid(('dtxn',)))) - # is:matched does not imply the destination leg is populated + # is:matched does not imply the target leg is populated nodes = await core.nodes(''' [ crypto:currency:bridge:swap=(bridge2,) :matched=true - :origin:transaction=(otxn2,) - :origin:address=(eth, 0xcccc) - :origin:amount=2.0 - :destination:transaction=(dtxn2,) + :source:transaction=(otxn2,) + :source:address=(eth, 0xcccc) + :source:amount=2.0 + :target:transaction=(dtxn2,) ]''') self.len(1, nodes) node = nodes[0] - self.nn(node.get('destination:transaction')) - self.none(node.get('destination:address')) - self.none(node.get('destination:contract')) - self.none(node.get('destination:amount')) + self.nn(node.get('target:transaction')) + self.none(node.get('target:address')) + self.none(node.get('target:contract')) + self.none(node.get('target:amount')) nodes = await core.nodes(''' [