@@ -160,6 +160,69 @@ function preamble() {
160
160
}
161
161
```
162
162
163
+ ```python
164
+ from decimal import Decimal
165
+ from typing import List, Any, Dict
166
+
167
+ from stellar_sdk import *
168
+
169
+ server = Server("https://horizon-testnet.stellar.org")
170
+
171
+
172
+ # Preamble
173
+ def new_tx_builder(source: str) -> TransactionBuilder:
174
+ network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
175
+ base_fee = 100
176
+ source_account = server.load_account(source)
177
+ builder = TransactionBuilder(
178
+ source_account =source_account, network_passphrase =network_passphrase, base_fee =base_fee
179
+ ).set_timeout(30)
180
+ return builder
181
+
182
+
183
+ # Returns the given asset pair in " protocol order."
184
+ def order_asset(a: Asset, b: Asset) -> List[Asset]:
185
+ return [a, b] if LiquidityPoolAsset.is_valid_lexicographic_order(a, b) else [b, a]
186
+
187
+
188
+ secrets = [
189
+ " SBGCD73TK2PTW2DQNWUYZSTCTHHVJPL4GZF3GVZMCDL6GYETYNAYOADN" ,
190
+ " SAAQFHI2FMSIC6OFPWZ3PDIIX3OF64RS3EB52VLYYZBX6GYB54TW3Q4U" ,
191
+ " SCJWYFTBDMDPAABHVJZE3DRMBRTEH4AIC5YUM54QGW57NUBM2XX6433P" ,
192
+ ]
193
+ kps = [Keypair.from_secret(secret=secret) for secret in secrets]
194
+
195
+ # kp0 issues the assets
196
+ kp0 = kps[0]
197
+ asset_a, asset_b = order_asset(Asset("A", kp0.public_key), Asset("B", kp0.public_key))
198
+
199
+
200
+ def distribute_assets(
201
+ issuer_kp: Keypair, recipient_kp: Keypair, assets: List[Asset]
202
+ ) -> Dict[str, Any]:
203
+ builder = new_tx_builder(issuer_kp.public_key)
204
+ for asset in assets:
205
+ builder.append_change_trust_op(
206
+ asset =asset, limit = " 100000" , source =recipient_kp.public_key
207
+ ).append_payment_op(
208
+ destination =recipient_kp.public_key,
209
+ asset =asset,
210
+ amount = " 100000" ,
211
+ source =issuer_kp.public_key,
212
+ )
213
+
214
+ tx = builder.build()
215
+ tx.sign(issuer_kp)
216
+ tx.sign(recipient_kp)
217
+ resp = server.submit_transaction(tx)
218
+ return resp
219
+
220
+
221
+ def preamble() -> None:
222
+ resp1 = distribute_assets(kp0, kps[1], [asset_a, asset_b])
223
+ resp2 = distribute_assets(kp0, kps[2], [asset_a, asset_b])
224
+ # ...
225
+ ```
163
226
</CodeExample>
164
227
165
228
Here, we use `distributeAssets()` to establish trustlines and set up initial balances of two custom assets (`A` and `B`, issued by `kp1`) for two accounts (`kp2` and `kp3`). For someone to participate in the pool, they must establish trustlines to each of the asset issuers *and* to the pool share asset (explained below).
@@ -188,6 +251,19 @@ function establishPoolTrustline(account, keypair, poolAsset) {
188
251
}
189
252
```
190
253
254
+ ```python
255
+ pool_share_asset = LiquidityPoolAsset(asset_a=asset_a, asset_b =asset_b)
256
+
257
+
258
+ def establish_pool_trustline(source: Keypair, pool_asset: LiquidityPoolAsset) -> Dict[str, Any]:
259
+ tx = (
260
+ new_tx_builder(source.public_key)
261
+ .append_change_trust_op(asset=pool_asset, limit = " 100000" )
262
+ .build()
263
+ )
264
+ tx.sign(source)
265
+ return server.submit_transaction(tx)
266
+ ```
191
267
</CodeExample>
192
268
193
269
This lets the participants hold pool shares (refer to the discussion about [pool shares earlier](#liquidity-pool-participation) for details), which means now they can perform deposits and withdrawals.
@@ -223,6 +299,33 @@ function addLiquidity(source, signer, poolId, maxReserveA, maxReserveB) {
223
299
}
224
300
```
225
301
302
+ ```python
303
+ pool_id = pool_share_asset.liquidity_pool_id
304
+
305
+
306
+ def add_liquidity(
307
+ source: Keypair,
308
+ pool_id: str,
309
+ max_reserve_a: Decimal,
310
+ max_reserve_b: Decimal,
311
+ ) -> dict[str, Any]:
312
+ exact_price = max_reserve_a / max_reserve_b
313
+ min_price = exact_price - exact_price * Decimal("0.1")
314
+ max_price = exact_price + exact_price * Decimal("0.1")
315
+ tx = (
316
+ new_tx_builder(source.public_key)
317
+ .append_liquidity_pool_deposit_op(
318
+ liquidity_pool_id =pool_id,
319
+ max_amount_a =f"{max_reserve_a:.7f}",
320
+ max_amount_b =f"{max_reserve_b:.7f}",
321
+ min_price =min_price,
322
+ max_price =max_price,
323
+ )
324
+ .build()
325
+ )
326
+ tx.sign(source)
327
+ return server.submit_transaction(tx)
328
+ ```
226
329
</CodeExample>
227
330
228
331
When depositing assets into a liquidity pool, you need to define your acceptable price bounds. In the above function, we allow for a +/-10% margin of error from the " spot price" . This margin is **by no means a recommendation** and is chosen just for demonstration.
@@ -249,6 +352,38 @@ function removeLiquidity(source, signer, poolId, minReserveA, minReserveB) {
249
352
}
250
353
```
251
354
355
+ ```python
356
+ def remove_liquidity(
357
+ source: Keypair, pool_id: str, shares_amount: Decimal
358
+ ) -> dict[str, Any]:
359
+ pool_info = server.liquidity_pools().liquidity_pool(pool_id).call()
360
+ total_shares = Decimal(pool_info["total_shares"])
361
+ min_reserve_a = (
362
+ shares_amount
363
+ / total_shares
364
+ * Decimal(pool_info["reserves"][0]["amount"])
365
+ * Decimal("0.95")
366
+ ) #
367
+ min_reserve_b = (
368
+ shares_amount
369
+ / total_shares
370
+ * Decimal(pool_info["reserves"][1]["amount"])
371
+ * Decimal("0.95")
372
+ )
373
+ tx = (
374
+ new_tx_builder(source.public_key)
375
+ .append_liquidity_pool_withdraw_op(
376
+ liquidity_pool_id =pool_id,
377
+ amount =f"{shares_amount:.7f}",
378
+ min_amount_a =f"{min_reserve_a:.7f}",
379
+ min_amount_b =f"{min_reserve_b:.7f}",
380
+ )
381
+ .build()
382
+ )
383
+ tx.sign(source)
384
+ return server.submit_transaction(tx)
385
+ ```
386
+
252
387
</CodeExample>
253
388
254
389
Notice here that we specify the minimum amount. Much like with a strict-receive path payment, we're specifying that we're not willing to receive less than this amount of each asset from the pool. This effectively defines a minimum withdrawal price.
@@ -291,6 +426,45 @@ function getSpotPrice() {
291
426
preamble().then(main);
292
427
```
293
428
429
+ ```python
430
+ def main():
431
+ deposit_a = Decimal(1000)
432
+ deposit_b = Decimal(3000) # maintain a 1:3 ratio
433
+ establish_pool_trustline(kps[1], pool_share_asset)
434
+ add_liquidity(kps[1], pool_id, deposit_a, deposit_b)
435
+ get_spot_price()
436
+
437
+ deposit_a = Decimal(2000)
438
+ deposit_b = Decimal(6000) # maintain a 1:3 ratio
439
+ establish_pool_trustline(kps[2], pool_share_asset)
440
+ add_liquidity(kps[2], pool_id, deposit_a, deposit_b)
441
+ get_spot_price()
442
+
443
+ # kp1 takes all his/her shares out
444
+ balance = 0
445
+ for b in server.accounts().account_id(kps[1].public_key).call()["balances"]:
446
+ if (
447
+ b["asset_type"] == " liquidity_pool_shares"
448
+ and b["liquidity_pool_id"] == pool_id
449
+ ):
450
+ balance = Decimal(b["balance"])
451
+ break
452
+ if not balance:
453
+ raise
454
+ remove_liquidity(kps[1], pool_id, balance)
455
+ get_spot_price()
456
+
457
+ def get_spot_price():
458
+ resp = server.liquidity_pools().liquidity_pool(pool_id).call()
459
+ amount_a = resp["reserves"][0]["amount"]
460
+ amount_b = resp["reserves"][1]["amount"]
461
+ spot_price = Decimal(amount_a) / Decimal(amount_b)
462
+ print(f"Price: { amount_a } /{amount_b} = { spot_price :.7f } " )
463
+
464
+ if __name__ == '__main__':
465
+ preamble()
466
+ main()
467
+ ```
294
468
</CodeExample>
295
469
296
470
### Watching Liquidity Pool Activity
@@ -315,4 +489,19 @@ server.operations()
315
489
});
316
490
```
317
491
492
+ ```python
493
+ def watch_liquidity_pool_activity():
494
+ for op in (
495
+ server.operations()
496
+ .for_liquidity_pool(liquidity_pool_id=pool_id)
497
+ .cursor("now")
498
+ .stream()
499
+ ):
500
+ if op["type"] == " liquidity_pool_deposit" :
501
+ print("Reserves deposited:")
502
+ for r in op["reserves_deposited"]:
503
+ print(f" { r [' amount' ]} of { r [' asset' ]} " )
504
+ print(f" for pool shares: { op [' shares_received' ]} " )
505
+ # ...
506
+ ```
318
507
</CodeExample>
0 commit comments