title | authors | last_edited |
---|---|---|
AlgoSwap Protocol Specification |
Anthony Zhang (@uberi), Haardik Haardik (@haardikk21) |
11 November 2020 |
Refers to the AlgoSwap end user, interacting via Frontend and Wallet. Capable of 4 distinct operations:
- Opt in to AlgoSwap
- Swap between a
TOKEN1
/TOKEN2
pair - Add liquidity for
TOKEN1
/TOKEN2
pair - Withdraw liquidity for
TOKEN1
/TOKEN2
pair
Refers to the AlgoSwap frontend, which will be hosted on the open internet and be the convenient and standard way for User to interact with Algoswap.
Optionally, User may choose to host Frontend themselves, or write their own clients should they wish to.
Frontend interacts with the Algorand blockchain via the Wallet's Algorand API.
Refers to a browser extension that manages Algorand transaction signing. At the time of writing, the best and only option for this is PureStake's AlgoSigner, written for Chrome/Chromium browsers.
Refers to a logicsig Algorand smart contract that validates the transaction fields of every possible transaction for Algoswap.
Refers to a stateful application Algorand smart contract that manages global and local state, and approves transactions to the escrow contracts for each liquidity pair.
Refers to a logicsig Algorand smart contract for a specific liquidity pair (TOKEN1
and TOKEN2
), where TOKEN1
and TOKEN2
refer to distinct Algorand Standard Assets. All withdrawals from this smart contract account require approval from Validator and Manager within the same atomic transaction group.
We denote the 58-byte Algorand address of ESCROW(TOKEN1, TOKEN2)
as ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
Refers to an Algorand Standard Asset representing shares of the liquidity stored in a specific Escrow Contract.
This token is minted when liquidity providers deposit liquidity, and then burned by its holder to recover the amounts of TOKEN1
and TOKEN2
represented by the liquidity represented by this token.
The reserve address for the liquidity token is represented as RESERVE_ADDR(LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2)))
Refers to an AlgoSwap developer deploying, upgrading, and maintaining AlgoSwap. Has the special privelege to be able to withdraw Protocol Fees (PROTOCOL_UNUSED_TOKEN1
and PROTOCOL_UNUSED_TOKEN2
balances in each ESCROW(TOKEN1, TOKEN2)
)
Actual keys in TEAL use shorter names than the ones specified here for readability.
DEVELOPER_ADDRESS
: address of the account that created the Manager contract.
Manager also needs to store some information about each Escrow Contract. To do this, each Escrow Contract opts into the Manager, and we store fields in Manager's local storage for the Escrow Contract.
-
TOKEN1
andTOKEN2
: The asset ID's of Token 1 and Token 2 respectively. -
LIQUIDITY_TOKEN
: The asset ID of the liquidity token for this escrow contract. -
TOTAL_TOKEN1_BALANCE
andTOTAL_TOKEN2_BALANCE
: Total amounts of TOKEN1 and TOKEN2 currently in the liquidity pool, respectively.- Different from getting the Escrow contract's balance for
TOKEN1
andTOKEN2
because the real balances also include the values ofUSER_TOKEN1_UNUSED
andUSER_TOKEN2_UNUSED
for User and the Protocol Fees. - These values are initialized to 0.
- Different from getting the Escrow contract's balance for
-
TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
: Total amount ofLIQUIDITY_TOKEN
distributed to users.- Different from calculating (total supply of
LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
) minus (current balance ofRESERVE_ADDR(LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2)))
), because the real amount ofLIQUIDITY_TOKEN
distributed to users should also include the values ofUSER_UNUSED_LIQUIDITY
for User.
- Different from calculating (total supply of
-
PROTOCOL_UNUSED_TOKEN1
andPROTOCOL_UNUSED_TOKEN2
: Total amounts of Token 1 and Token 2 currently allocated towards protocol fees. These are withdrawable by the Developer
-
USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
andUSER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
: Amounts of Token 1 and Token 2 forESCROW(TOKEN1, TOKEN2)
that are immediately refundable to the User with a withdrawal transaction.- Acts as a holding area for funds that are "refunded" by AlgoSwap
- Due to slippage, it is impossible to know with certainty at transaction creation time exactly how much of Token 2 the User will be able to withdraw afterwards. Instead, the swap updates the refundable balance for the user, that they can then withdraw at their leisure.
- These values are initialized to 0
-
USER_UNUSED_LIQUIDITY(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
: Amount of liquidity token for this escrow contract that are immediately refundable to the User with a withdrawal transaction.- Acts as a holding area for liquidity owed to the user by AlgoSwap.
- Since the ratio of Token1/Token2 can change between transaction creation and finalization time, the amount of liquidity the user should receive can change as well. AlgoSwap thus computes the maximum possible liquidity given User's Token1/Token2 deposit, issues a refund for any remaining Token1/Token2, and updated the refundable balances for the user. User can then withdraw them at their leisure.
- This value is initialized to 0.
LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
is the liquidity token associated withESCROW(TOKEN1, TOKEN2)
. This represents a share of the liquidity pool, and is gained by adding liquidity to AlgoSwap. The User may withdraw their liquidity from AlgoSwap, burningLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
in the process and receiving their share of Token 1 and Token 2.
The swap fee intended to be paid to liquidity providers, and is taken out of the Token 2 balance for every swap. This fee is added to the liquidity pool. Sincce the liquidity providers each own shares of the liquidity pool, the fee is distributed to liquidity providers weighted by how much liquidity they provide. Currently set to 0.45%.
The fee intended by to paid to the AlgoSwap developers to cover costs of ongoing maintainence and development. Currently set to be 0.05%.
AlgoSwap enforces the xy=k
invariant, i.e. TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE = k
where k
is a constant.
Therefore, TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE = (TOTAL_TOKEN1_BALANCE + token1_input) * (TOTAL_TOKEN2_BALANCE - token2_output)
We can solve this to get token2_output = TOTAL_TOKEN2_BALANCE - ((TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE) / (TOTAL_TOKEN1_BALANCE + token1_input))
As per Martin Koppelmann's price formula in On Path Independence, we'll define instantaneous_exchange_rate = TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE
to be the instantaneous exchange rate - the amount of TOKEN2 you receive, divided by the amount of TOKEN1 you sent, if you sent a really really small amount of TOKEN1.
Therefore, we can now define a swap operation as follows:
-
Frontend asks User for their account address, the desired liquidity pair, and the amount of Token 1 to swap.
-
Frontend asks Wallet to sign an atomic transaction group consisting of 3 transactions:
-
An
ApplicationCall
transaction from User to Validator:FirstValid
is the current round (no verification needed)LastValid
is set to be a few rounds in the future (no verification needed)Application ID
is Validator's application ID (no verification needed)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
App Arguments
is a two-element array consisting of:- The string
"s1"
which identifies this transaction group as a swap for Token 1 to Token 2. min_token2_received_from_algoswap
: The minimum amount of Token 2 that the user should receive. If the exchange rate rises such that the User would receive less than this amount of Token 2, the swap fails.
- The string
- All other ApplicationCall-specific transaction fields MUST not be present.
-
An
ApplicationCall
transaction from User to Manager:FirstValid
is the current round (no verification needed)LastValid
is set to be a few rounds in the future (no verification needed)Application ID
is Validator's application ID (no verification needed)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
App Arguments
is a two-element array consisting of:- The string
"s1"
which identifies this transaction group as a swap for Token 1 to Token 2. min_token2_received_from_algoswap
: The minimum amount of Token 2 that the user should receive. If the exchange rate rises such that the User would receive less than this amount of Token 2, the swap fails.
- The string
- All other ApplicationCall-specific transaction fields MUST not be present.
-
An
AssetTransfer
transaction from User to the Escrow Contract for Token1/Token2XferAsset
is Token 1AssetAmount
is amount specified in the frontend (no verification needed). Call thistoken1_input
AssetSender
is Global zero address (should be the case for regular transfers between accounts)AssetReceiver
isADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
- All other AssetTransfer-specific transaction fields MUST not be present
-
-
Validator runs its approval program for validating the fields of the transaction group
-
Manager runs its approval program for updating the Application State for this transaction group
- Retrieve
TOTAL_TOKEN1_BALANCE
andTOTAL_TOKEN2_BALANCE
fromMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. - Set
TOTAL_TOKEN1_BALANCE += token1_input * SWAP_FEE
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. This adds the swap fee amount to the liquidity pool, to be shared by all liquidity providers - Set
PROTOCOL_UNUSED_TOKEN1 += token1_input * PROTOCOL_FEE
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. This adds the protocol fee to the protocol fee account. - Compute
token1_input_minus_fees = token1_input_amount * (1 - SWAP_FEE - PROTOCOL_FEE)
. - Compute
token2_output = TOTAL_TOKEN2_BALANCE - (TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE) / (TOTAL_TOKEN1_BALANCE + token1_input_minus_fees)
. - Assert that
token2_output >= min_token2_received_from_algoswap
. - Set
USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += token2_output
inMANAGER
's local storage forUSER
. - Set
TOTAL_TOKEN1_BALANCE += token1_input_minus_fees
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. - Set
TOTAL_TOKEN2_BALANCE -= token2_output
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. - At this point, we approve the transaction. Since we're depositing,
ESCROW(TOKEN1, TOKEN2)
does not need to run its logicsig.
- Retrieve
-
Frontend reads the value of
unused_token2 = USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
from Manager's local storage for the User. -
Frontend asks Wallet to sign an atomic transaction group consisting of 3 transactions:
- An
ApplicationCall
transaction from User to ValidatorApplication ID
is Validator's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
ApplicationCall
transaction from User to ManagerApplication ID
isMANAGER
's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
AssetTransfer
transaction from the Escrow Contract that sends the User's refundable Token 2 back to User.Sender
isESCROW(TOKEN1, TOKEN2)
.XferAsset
is TOKEN1.AssetAmount
is any amount up to and includingunused_token2
.AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
is the Algorand address forUSER
(no verification needed).- All other AssetTransfer-specific transaction fields must not be present.
- An
-
Validator runs its approval program for validating the fields of the transaction group.
-
Manager runs its approval program for updating the Application State for this transaction group and escrow runs its logicsig:
- Sets
USER_UNUSED_TOKEN2(ADDRESS_OF(TOKEN1, TOKEN2)) -= unused_token2
in local storage for User. ESCROW(TOKEN1, TOKEN2)
checks that the transaction group matches up to the required format.
- Sets
-
Frontend clears Manager's local state for the User, then shows User how much of Token 2 they have received.
Mostly the same as Swap Token 1 for Token 2 except Token 1 and Token 2 switch places.
Solve TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE = (TOTAL_TOKEN1_BALANCE - token1_output) * (TOTAL_TOKEN2_BALANCE + token2_input)
to get token1_output = TOTAL_TOKEN1_BALANCE - ((TOTAL_TOKEN1_BALANCE * TOTAL_TOKEN2_BALANCE) / (TOTAL_TOKEN2_BALANCE + token2_input))
During liquidity addition, AlgoSwap enforces that the instantaneous exchange rate (TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE
) stays constant. This prevents liquidity providers from being able to control the exchange rate by changing the amount of liquidity they make available.
Therefore, liquidity providers must deposit an amount such that (TOTAL_TOKEN1_BALANCE + token1_deposit) / (TOTAL_TOKEN2_BALANCE + token2_deposit) = TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE
.
We can solve to get token2_deposit = token1_deposit * TOTAL_TOKEN2_BALANCE / TOTAL_TOKEN1_BALANCE
. Likewise token1_deposit = token2_deposit * TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE
.
When TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
is 0 (which also implies that one or more of TOTAL_TOKEN1_BALANCE
or TOTAL_TOKEN2_BALANCE
are 0), this means that the user adding liquidity is the very first liquidity provider. In this special case, User is able to control the exchange rate, but this can only happen when User is the very first liquidity provider. User would be advised to deposit amounts of TOKEN1 and TOKEN2 that are equal in value on other exchanges, or else arbitrageurs will quickly skim off the difference.
So, if User is sending x
of Token 1, they need to send x * TOTAL_TOKEN2_BALANCE / TOTAL_TOKEN1_BALANCE
of Token 2 as well, in order to keep the instantaneous exchange rate the same.
Due to slippage, Users cannot be expected to send exact amounts for each deposit as they might never be able to send the right amount on time before the rate changes. Instead
min(token1_deposit, (token2_deposit * TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE))
of Token 1, andmin(token2_deposit, token1_deposit * (TOTAL_TOKEN2_BALANCE / TOTAL_TOKEN1_BALANCE))
of Token 2
are made available in the liquidity pool, and the rest is all refundable to the sender.
To split liquidity fees, we need to measure how much liquidity each liquidity provider is providing. To do this, we define a unitless measure of liquidity where the first liquidity provider on AlgoSwap sets it initially to the amount of TOKEN1 they've provided, and after that liquidity providers gain liquidity as a factor of how much they've increased the amount of TOKEN1 in the liquidity pool. For example, if TOTAL_TOKEN1_BALANCE
is 1000 and someone adds 1000 of TOKEN1, they doubled the amount of liquidity in the system, therefore they should gain an amount of liquidity equal to whatever amount of liquidity currently exists.
Therefore, we can now define an add liquidity operation as follows:
-
Frontend asks User for their account address, the desired liquidity pair, and the amount of each token to add to the liquidity pool.
-
Frontend asks Wallet to sign an atomic transaction group consisting of 4 transactions:
-
An
ApplicationCall
transaction from User to Validator:FirstValid
is the current round (no verification needed)LastValid
is set to be a few rounds in the future (no verification needed)Application ID
is Validator's application ID (no verification needed)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a two-element array consisting of:- The string
"a"
which identifies this transaction group as an add liquidity transaction. min_liquidity_received_from_algoswap
: The minimum amount of liquidity token that the user should receive. If the exchange rate rises such that the User would receive less than this amount of liqudity token, the swap fails.
- The string
Foreign Assets
is a one-element array consisting ofLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
.- All other ApplicationCall-specific transaction fields MUST not be present.
-
An
ApplicationCall
transaction from User to Manager:FirstValid
is the current round (no verification needed)LastValid
is set to be a few rounds in the future (no verification needed)Application ID
is Validator's application ID (no verification needed)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a two-element array consisting of:- The string
"a"
which identifies this transaction group as an add liquidity transaction. min_liquidity_received_from_algoswap
: The minimum amount of liquidity token that the user should receive. If the exchange rate rises such that the User would receive less than this amount of liqudity token, the swap fails.
- The string
Foreign Assets
is a one-element array consisting ofLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
.- All other ApplicationCall-specific transaction fields MUST not be present.
-
An AssetTransfer transaction from User that sends the specified amount of TOKEN1 to
ESCROW(TOKEN1, TOKEN2)
:XferAsset
is TOKEN1.AssetAmount
is anything (no verification needed).AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
isESCROW(TOKEN1, TOKEN2)
.- All other AssetTransfer-specific transaction fields MUST not be present.
-
An AssetTransfer transaction from User that sends the specified amount of TOKEN2 to
ESCROW(TOKEN1, TOKEN2)
:XferAsset
is TOKEN2.AssetAmount
is anything (no verification needed).AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
isESCROW(TOKEN1, TOKEN2)
.- All other AssetTransfer-specific transaction fields MUST not be present.
-
-
Validator runs its approval program for validating the fields of the transaction group
-
Manager runs its approval program for updating the Application State for this transaction group
- Retrieve
TOTAL_TOKEN1_BALANCE
,TOTAL_TOKEN2_BALANCE
, andTOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
fromMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. - If
TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
is 0:- Compute
token1_used = token1_deposit
andtoken2_used = token2_deposit
, wheretoken1_deposit
andtoken2_deposit
are the TOKEN1 and TOKEN2AssetAmount
values from the transaction group. - Compute
new_liquidity = token1_deposit
.
- Compute
- Otherwise (if
TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
is not 0):- Compute
token1_used = min(token1_deposit, token2_deposit * TOTAL_TOKEN1_BALANCE / TOTAL_TOKEN2_BALANCE)
, wheretoken1_deposit
andtoken2_deposit
are the TOKEN1 and TOKEN2AssetAmount
values from the transaction group. - Compute
token2_used = min(token2_deposit, token1_deposit * TOTAL_TOKEN2_BALANCE / TOTAL_TOKEN1_BALANCE)
. - Compute
new_liquidity = TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED * token1_deposit / TOTAL_TOKEN1_BALANCE
.
- Compute
- Set
USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += token1_deposit - token1_used
inMANAGER
's local storage forUSER
. - Set
USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += token2_deposit - token2_used
inMANAGER
's local storage forUSER
. - Set
USER_UNUSED_LIQUIDITY(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += new_liquidity
inMANAGER
's local storage forUSER
. - Set
TOTAL_TOKEN1_BALANCE += token1_used
andTOTAL_TOKEN2_BALANCE += token2_used
andTOTAL_LIQUIDITY_TOKEN_DISTRIBUTED += new_liquidity
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. These amounts are now part of the liquidity pool and can only be retrieved with a liquidity withdrawal operation. The unused remainder ofUSER
's tokens inMANAGER
can now be refunded toUSER
in the following steps.
- Retrieve
-
Frontend reads the value of
unused_token1 = USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
,unused_token2 = USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
, andunused_liquidity = USER_UNUSED_LIQUIDITY(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
from Manager's local storage for the User. -
Frontend asks Wallet to sign an atomic transaction group consisting of 3 transactions:
- An
ApplicationCall
transaction from User to ValidatorApplication ID
is Validator's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
ApplicationCall
transaction from User to ManagerApplication ID
is Manager's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
AssetTransfer
transaction fromESCROW(TOKEN1, TOKEN2)
that sendsUSER
's refundable TOKEN1 back toUSER
.Sender
isESCROW(TOKEN1, TOKEN2)
.XferAsset
is TOKEN1.AssetAmount
is any amount up to and includingunused_token1
.AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
is the Algorand address forUSER
(no verification needed).- All other AssetTransfer-specific transaction fields must not be present.
- An
-
Validator runs its approval program for validating the fields of the transaction group.
-
Manager runs its approval program for updating the Application State for this transaction group and escrow runs its logicsig:
- Sets
USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) -= unused_token1
in local storage for User. ESCROW(TOKEN1, TOKEN2)
checks that the transaction group matches up to the required format.
- Sets
-
Same as steps 6 and 7, but for Token 2 rather than Token 1
-
Same as steps 6 and 7, but for
LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
rather than Token 1 -
Frontend clears Manager's local state for the User, then shows User how much of Token 1 and Token 2 they've successfully added to the liquidity pool.
We can define a withdraw liquidity operation as follows:
-
Frontend asks User for their account address, the desired liquidity pair, and the amount of liquidity to withdraw from the liquidity pool.
-
Frontend asks Wallet to sign an atomic transaction group consisting of 3 transactions:
-
An
ApplicationCall
transaction from User to Validator:FirstValid
is the current round (no verification needed).LastValid
is set to be a few rounds in the future, perhaps around 30 seconds (no verification needed). This is intended to limit frontrunning techniques that delay the transaction for many rounds.Application ID
isMANAGER
's application ID (no verification needed).OnComplete
isNoOp
.Accounts
is a two-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a three-element array consisting of:- The string "w": identifies this transaction as a "withdraw liquidity".
min_token1_received_from_algoswap
: the swap fails if the exchange rate rises such thatUSER
would receive less than this amount of TOKEN1. This is intended to limit frontrunning techniques that manipulate the liquidity pool distribution.min_token2_received_from_algoswap
: the swap fails if the exchange rate rises such thatUSER
would receive less than this amount of TOKEN2. This is intended to limit frontrunning techniques that manipulate the liquidity pool distribution.
Foreign Assets
is a one-element array consisting ofLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
.- All other ApplicationCall-specific transaction fields MUST not be present.
-
An
ApplicationCall
transaction from User to Manager:FirstValid
is the current round (no verification needed).LastValid
is set to be a few rounds in the future, perhaps around 30 seconds (no verification needed). This is intended to limit frontrunning techniques that delay the transaction for many rounds.Application ID
isMANAGER
's application ID (no verification needed).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a three-element array consisting of:- The string "w": identifies this transaction as a "withdraw liquidity".
min_token1_received_from_algoswap
: the swap fails if the exchange rate rises such thatUSER
would receive less than this amount of TOKEN1. This is intended to limit frontrunning techniques that manipulate the liquidity pool distribution.min_token2_received_from_algoswap
: the swap fails if the exchange rate rises such thatUSER
would receive less than this amount of TOKEN2. This is intended to limit frontrunning techniques that manipulate the liquidity pool distribution.
Foreign Assets
is a one-element array consisting ofLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
.- All other ApplicationCall-specific transaction fields MUST not be present.
-
An AssetTransfer transaction from User that sends the specified amount of
LIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
toESCROW(TOKEN1, TOKEN2)
:XferAsset
isLIQUIDITY_TOKEN(ESCROW(TOKEN1, TOKEN2))
.AssetAmount
is anything (no verification needed).AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
isESCROW(TOKEN1, TOKEN2)
.- All other AssetTransfer-specific transaction fields MUST not be present.
-
-
Validator runs its approval program for validating the fields of the transaction group.
-
Manager runs its approval program for updating the Application State for this transaction group
- Retrieve
TOTAL_TOKEN1_BALANCE
,TOTAL_TOKEN2_BALANCE
, andTOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
fromMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
. - Compute
token1_available = TOTAL_TOKEN1_BALANCE * user_liquidity / TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
andtoken2_available = TOTAL_TOKEN2_BALANCE * user_liquidity / TOTAL_LIQUIDITY_TOKEN_DISTRIBUTED
, whereuser_liquidity
is theAssetAmount
value in the transaction group. This is the fraction of the liquidity pool provided byUSER
. As the liquidity pool grows from liquidity provider fees,USER
still owns the same slice of the pie, but the whole pie grew larger, soUSER
's liquidity tokens are worth more than before. - Set
USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += token1_available
inMANAGER
's local storage forUSER
. - Set
USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) += token2_available
inMANAGER
's local storage forUSER
. - Set
TOTAL_TOKEN1_BALANCE -= token1_available
,TOTAL_TOKEN2_BALANCE -= token2_available
, andTOTAL_LIQUIDITY_TOKEN_DISTRIBUTED -= user_liquidity
inMANAGER
's local storage forESCROW(TOKEN1, TOKEN2)
.
- Retrieve
-
Frontend reads the value of
unused_token1 = USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
andunused_token2 = USER_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2)))
from Manager's local storage for the User. -
Frontend asks Wallet to sign an atomic transaction group consisting of 3 transactions:
- An
ApplicationCall
transaction from User to ValidatorApplication ID
is Validator's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
ApplicationCall
transaction from User to ManagerApplication ID
is Manager's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
).OnComplete
isNoOp
.Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
.App Arguments
is a one-element array consisting of the string"r"
, which identifies this transaction group as a "refund".- All other ApplicationCall-specific transaction fields must not be present.
- An
AssetTransfer
transaction fromESCROW(TOKEN1, TOKEN2)
that sendsUSER
's refundable TOKEN1 back toUSER
.Sender
isESCROW(TOKEN1, TOKEN2)
.XferAsset
is TOKEN1.AssetAmount
is any amount up to and includingunused_token1
.AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
is the Algorand address forUSER
(no verification needed).- All other AssetTransfer-specific transaction fields must not be present.
- An
-
Validator runs its approval program for validating the fields of the transaction group.
-
Manager runs its approval program for updating the Application State for this transaction group and escrow runs its logicsig:
- Sets
USER_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) -= unused_token1
in local storage for User. ESCROW(TOKEN1, TOKEN2)
checks that the transaction group matches up to the required format.
- Sets
-
Same as steps 6 and 7, but for Token 2 rather than Token 1
-
Frontend clears Manager's local state for the User, then shows User how much of Token 1 and Token 2 they've successfully withdrawn from the AlgoSwap liquidity pool.
- Developer reads the current values of
PROTOCOL_UNUSED_TOKEN1
andPROTOCOL_UNUSED_TOKEN2
from Manager's local state for some Escrow Contract. - Developer creates and signs an atomic transaction group consisting of 4 transactions:
- An
ApplicationCall
transaction from Developer to ValidatorSender
isDEVELOPER_ADDRESS
Application ID
is Validator's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
App Arguments
is a one-element array consisting of the string"p"
which identifies this transaction group as a protocol fee refund.- All other ApplicationCall-specific transaction fields MUST not be present.
- An
AppliationCall
transaction from Developer to ManagerSender
isDEVELOPER_ADDRESS
Application ID
is Validator's application ID (must be verified byESCROW(TOKEN1, TOKEN2)
)OnComplete
isNoOp
Accounts
is a one-element array consisting ofADDRESS_OF(ESCROW(TOKEN1, TOKEN2))
App Arguments
is a one-element array consisting of the string"p"
which identifies this transaction group as a protocol fee refund.- All other ApplicationCall-specific transaction fields MUST not be present.
- An
AssetTransfer
transaction fromESCROW(TOKEN1, TOKEN2)
that sends unclaimed Token 1 protocol fees toDEVELOPER_ADDRESS
Sender
isESCROW(TOKEN1, TOKEN2)
.XferAsset
is TOKEN1.AssetAmount
is any amountwithdrawn_token1
up to and including the current value ofPROTOCOL_UNUSED_TOKEN1
.AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
isDEVELOPER_ADDRESS
(no verification needed).- All other AssetTransfer-specific transaction fields MUST not be present.
- An
AssetTransfer
transaction fromESCROW(TOKEN1, TOKEN2)
that sends unclaimed Token 2 protocol fees toDEVELOPER_ADDRESS
Sender
isESCROW(TOKEN1, TOKEN2)
.XferAsset
is TOKEN2.AssetAmount
is any amountwithdrawn_token2
up to and including the current value ofPROTOCOL_UNUSED_TOKEN2
.AssetSender
is zero (this should always be the case for regular transfers between accounts).AssetReceiver
isDEVELOPER_ADDRESS
(no verification needed).- All other AssetTransfer-specific transaction fields must not be present.
- An
- Validator runs its approval program to validate the transaction fields for this transaction group
- Manager runs its approval program for updating the Application State for this transaction group and escrow runs its logicsig
- Sets
PROTOCOL_UNUSED_TOKEN1(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) -= withdrawn_token1
andPROTOCOL_UNUSED_TOKEN2(ADDRESS_OF(ESCROW(TOKEN1, TOKEN2))) -= withdrawn_token2
in local storage for User (Developer) ESCROW(TOKEN1, TOKEN2)
checks that the transaction group matches up to the required format.
- Sets