2.0.1 • Published 4 months ago

ecash-agora v2.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

ecash-agora: Non-interactive XEC -> token swaps using Script

What's an "Agora"?

The agora (ἀγορά) was a central public space in ancient Greek city-states, and means "market" in modern Greek. The eCash Agora is similar in that sense, it's a protocol on eCash that allows anyone to offer their tokens in exchange for XEC.

Is Agora a DEX?

Agora is a NEX, a "non-custodial exchange"; in contrast to e.g. Uniswap or other DEXes that are common on ETH, Agora doesn't pool any funds, but everyone offering tokens has them in their own independently controlled UTXOs.

You don't send your tokens to a central smart contract, you keep them in your wallet but expose them for others to take if they meet certain criteria.

In that sense, "exchange" might even be a misnomer, as there's no special server or platform required, it's using the P2P network directly, and trades are accepted on and broadcast from the user's own wallets.

How can it be used?

eCash Agora allows users to lock their SLP/ALP tokens in a special UTXO, which behaves similar to to a "virtual vending machine", where other's can "insert" XEC and get tokens out.

For example, Alice can lock 1000 GRUMPY into an output with a Agora P2SH Script, and in the rules of the Script it requires others to send 20000 XEC to an output with an address controlled by Alice.

So, if Bob has 20000 XEC, he can send the 1000 GRUMPY to his own address by spending Alice's output with a transaction that sends the 20000 XEC to Alice. And the Agora Script enforces that this is handled correctly.

Why does it work?

We can use Bitcoin's Script language to put spending conditions on a UTXO, for example a normal P2PKH requires us to provide a matching public key + signature, like this:

OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG

These Scripts are little programs that a spender has to make happy (i.e. make them result in producing a "true" result) in order to spend them.

eCash has the ability to put conditions on a transaction spending a UTXO for a while already, you can read about them here.

We can use this to constrain that an output can only be spent if it sends some XEC to a specific address, which is essentially all that Agora does

At the end, it's an OP_EQUALVERIFY operation that will fail if someone tries to get the tokens without sending the required tokens to the required address.

How does it work?

Transaction

A transaction will look roughly like this, if Bob accepts all available tokens: | Inputs | Outputs | |--------|---------| | Value: dust Token: 1000 GRUMPY Script: Alice's Agora P2SH | Value: 0 Script: SLP OP_RETURN | | Value: 20000 XEC + fee Script: Bob's P2PKH Script | Value: 20000 XEC Script: Alice's P2PKH Script | | | Value: dust Token: 1000 GRUMPY Script: Bob's P2PKH Script |

You can see that after the transaction, Alice has 20000 XEC and Bob has 1000 Grumpy.

The Agora P2SH script will ensure that the transaction has the shape as described above.

Usage

You can create a "one shot" offer (one that offers all or nothing) using AgoraOneshot, here to sell an SLP NFT:

const enforcedOutputs: TxOutput[] = [
    { sats: 0n, script: slpSend(tokenId, SLP_NFT1_CHILD, [0n, 1n]) },
    { sats: 80000n, script: sellerP2pkh },
];
const agoraOneshot = new AgoraOneshot({
    enforcedOutputs,
    cancelPk: sellerPk,
});
const agoraScript = agoraOneshot.script();
const agoraP2sh = Script.p2sh(shaRmd160(agoraScript.bytecode));

A buyer can then accept this using AgoraOneshotSignatory:

const txBuilder = new TxBuilder({
    version: 2,
    inputs: [
        {
            input: {
                prevOut: {
                    txid: offerTxid,
                    outIdx: 1,
                },
                signData: {
                    sats: 546n,
                    redeemScript: agoraScript,
                },
            },
            signatory: AgoraOneshotSignatory(
                buyerSk,
                buyerPk,
                enforcedOutputs.length,
            ),
        },
        {
            input: {
                prevOut: {
                    txid: buyerSatsTxid,
                    outIdx: 0,
                },
                signData: {
                    sats: 90000n,
                    outputScript: buyerP2pkh,
                },
            },
            signatory: P2PKHSignatory(buyerSk, buyerPk, ALL_BIP143),
        },
    ],
    outputs: [
        {
            sats: 0n,
            script: slpSend(tokenId, SLP_NFT1_CHILD, [0n, 1n]),
        },
        { sats: 80000n, script: sellerP2pkh },
        { sats: 546n, script: buyerP2pkh },
    ],
});
const acceptTx = txBuilder.sign();
await chronik.broadcastTx(acceptTx.ser());

Development

Running the integration tests locally

  1. Build the node software from source with chronik and plugins enabled
mkdir build/
cd build/
cmake -GNinja .. -DBUILD_BITCOIN_CHRONIK=ON -DBUILD_BITCOIN_CHRONIK_PLUGINS=ON
ninja
  1. You may need to adjust your openssl settings

  2. Specify the location of your built chronik-with-plugins node with the BUILD_DIR env variable, e.g.

Running from bitcoin-abc/modules/ecash-agora if your build dir is bitcoin-abc/build/:

BUILD_DIR="${PWD}/../../build" npm run integration-tests

Changelog

0.2.0

  • Add agora.py plugin D16544
  • Plugin support D16745|D16753|D16754|D16755
  • Improve test framework D16741
  • Websocket subscriptions D16845
  • Build script for partial SLP offers D16743
  • Approximation logic for partial offers D16735
  • Add historicOffers function to Agora D16819
  • Patch burned tokens issue in agora partial scripts D16821
  • Export partial modules D16820
  • Syntax linting D16919|D16928
  • README patch for local integration testing D16952
  • Patch minAcceptedTokens() to return true minimum (prepared value) D16920
  • Add validation to acceptTx method of AgoraPartial to prevent creation of unspendable offers D16944
  • Export scriptOps helper function D16972
  • Improve approximation for USD-esque tokens D16995
  • Update tsconfig to support use in nodejs D17019
  • Monorepo linting D17072
  • CI publishing D17243

0.3.0

  • Add TakenInfo in historicOffers method to support parsing historic Agora offers D17422

0.3.1

  • Do not allow creation of unacceptable agora partials D17517

0.3.2

  • Improve offer checks in historicOffers D17630

0.4.0

  • Add getAgoraPartialAcceptFuelInputs and getAgoraCancelFuelInputs D17637

1.0.0

  • Remove unneeded ecc param from various functions D17640

1.0.1

  • Do not validate for unspendable offer creation when we calculate fee in acceptFeeSats() D17648

2.0.0

  • Improve types and shapes in line with chronik proto updates D17650
  • Introduce 'atoms' as term for base unit of tokens. Implement in lib. The term "token" is ambiguous as it is not clear that we are talking about base tokens.

2.0.1

  • Ensure special case of agora partial offers where minAcceptedAtoms should equal offeredAtoms will work out this way D17776
2.0.1

4 months ago

2.0.0

4 months ago

1.0.1

5 months ago

1.0.0

5 months ago

0.4.1-rc

5 months ago

1.0.1-rc

5 months ago

0.3.2

5 months ago

0.4.0

5 months ago

0.3.2-rc

5 months ago

0.2.1-rc20

6 months ago

0.2.1-rc21

6 months ago

0.3.0

6 months ago

0.3.1

6 months ago

0.3.0-rc2

6 months ago

0.2.1-rc19

6 months ago

0.3.0-rc1

6 months ago

0.2.1-rc4

6 months ago

0.2.1-rc3

6 months ago

0.2.1-rc6

6 months ago

0.2.1-rc5

6 months ago

0.2.1-rc2

6 months ago

0.2.0

7 months ago

0.2.2-rc

8 months ago

0.2.1-rc

8 months ago

0.2.0-rc

8 months ago

0.1.1-rc

10 months ago