diff --git a/justfile.local.just b/justfile.local.just index 4b54102..178441d 100644 --- a/justfile.local.just +++ b/justfile.local.just @@ -7,6 +7,12 @@ mprocs: test-ts: bun run wrappers/fedimint-ts/test.ts +test-py: + python3.11 wrappers/fedimint-py/test.py + +test-py-async: + python3.11 wrappers/fedimint-py/test_async.py + test-go: cd wrappers/fedimint-go && go run cmd/main.go diff --git a/wrappers/fedimint-py/AsyncFedimintClient.py b/wrappers/fedimint-py/AsyncFedimintClient.py index 411d102..95a26a8 100644 --- a/wrappers/fedimint-py/AsyncFedimintClient.py +++ b/wrappers/fedimint-py/AsyncFedimintClient.py @@ -1,40 +1,94 @@ +import json +import logging +from typing import List, Literal, Optional, Union +import asyncio import aiohttp +import atexit + from models.common import ( + DiscoverVersionRequest, + DiscoverVersionResponse, InfoResponse, ListOperationsRequest, - OperationOutput, - BackupRequest, ) -from models.ln import ( - LnInvoiceRequest, - LnInvoiceResponse, - AwaitInvoiceRequest, - LnPayRequest, - LnPayResponse, - AwaitLnPayRequest, + +from models.lightning import ( Gateway, - SwitchGatewayRequest, + LightningClaimPubkeReceivesRequest, + LightningAwaitInvoiceRequest, + LightningCreateInvoiceRequest, + LightningCreateInvoiceResponse, + LightningInvoiceForPubkeyTweakRequest, + LightningInvoiceForPubkeyTweakResponse, + LightningPayRequest, + LightningPayResponse, + LightningAwaitPayRequest, +) + +from models.onchain import ( + OnchainAwaitDepositRequest, + OnchainAwaitDepositResponse, + OnchainDepositAddressRequest, + OnchainDepositAddressResponse, + OnchainWithdrawRequest, + OnchainWithdrawResponse, ) -from models.wallet import DepositAddressRequest, AwaitDepositRequest, WithdrawRequest + from models.mint import ( - ReissueRequest, - SpendRequest, - ValidateRequest, - SplitRequest, - CombineRequest, + MintDecodeNotesRequest, + MintDecodeNotesResponse, + MintEncodeNotesRequest, + MintEncodeNotesResponse, + MintReissueRequest, + MintReissueResponse, + MintSpendRequest, + MintSpendResponse, + MintValidateRequest, + MintValidateResponse, + MintSplitRequest, + MintCombineRequest, + NotesJson, ) class AsyncFedimintClient: - def __init__(self, base_url: str, password: str, active_federation_id: str): - self.base_url = f"{base_url}/fedimint/v2" + + def __init__( + self, + base_url: str, + password: str, + active_federation_id: str, + active_gateway_id: str = None, + ): + self.base_url = f"{base_url}/v2" self.password = password self.active_federation_id = active_federation_id + self.active_gateway_id = active_gateway_id self.session = aiohttp.ClientSession() + atexit.register(self.close_sync) # Register the cleanup function - self.ln = self.LN(self) - self.wallet = self.Wallet(self) + self.lightning = self.Lightning(self) + self.onchain = self.Onchain(self) self.mint = self.Mint(self) + logging.info( + "Initialized fedimint client, must set active gateway id after intitalization to use lightning module methods or manually pass in gateways" + ) + + async def close(self): + await self.session.close() + + def close_sync(self): + try: + loop = asyncio.get_event_loop() + except RuntimeError as e: + # If no event loop is available, create a new one for cleanup + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + if loop.is_running(): + loop.create_task(self.close()) + else: + loop.run_until_complete(self.close()) def get_active_federation_id(self): return self.active_federation_id @@ -42,6 +96,22 @@ def get_active_federation_id(self): def set_active_federation_id(self, federation_id: str): self.active_federation_id = federation_id + def get_active_gateway_id(self): + return self.active_gateway_id + + def set_active_gateway_id(self, gateway_id: str): + self.active_gateway_id = gateway_id + + async def use_default_gateway(self): + # hits list_gateways and sets active_gatewayId to the first gateway + try: + gateways = await self.lightning.list_gateways() + logging.info("Gateways: ", gateways) + self.active_gateway_id = gateways[0]["info"]["gateway_id"] + logging.info("Set active gateway id to: ", self.active_gateway_id) + except Exception as e: + logging.error("Error setting default gateway id: ", e) + async def _handle_response(self, response): if response.status != 200: text = await response.text() @@ -66,7 +136,9 @@ async def _post(self, endpoint: str, data=None): await self._handle_response(response) return await response.json() - async def _post_with_id(self, endpoint: str, data=None, federation_id: str = None): + async def _post_with_federation_id( + self, endpoint: str, data=None, federation_id: str = None + ): if federation_id is None: federation_id = self.get_active_federation_id() @@ -76,118 +148,259 @@ async def _post_with_id(self, endpoint: str, data=None, federation_id: str = Non return await self._post(endpoint, data) - async def info(self): - return await self._get("/admin/info") + async def _post_with_gateway_id_and_federation_id( + self, + endpoint: str, + data=None, + gateway_id: str = None, + federation_id: str = None, + ): - async def backup(self, request: BackupRequest, federation_id: str = None): - return await self._post_with_id("/admin/backup", request, federation_id) + if gateway_id is None: + gateway_id = self.get_active_gateway_id() + + if federation_id is None: + federation_id = self.get_active_federation_id() + + if federation_id is None or gateway_id is None: + raise Exception( + "Must set active gateway id and active federation id before calling this method" + ) + + if data is None: + data = {} + data["gatewayId"] = gateway_id + data["federationId"] = federation_id + + return await self._post(endpoint, data) + + async def info(self) -> InfoResponse: + return await self._get("/admin/info") async def config(self): return await self._get("/admin/config") - async def discover_version(self): - return await self._get("/admin/discover-version") + # TODO: Unsupported method + # async def backup(self, request: BackupRequest, federationId: str = None): + # return await self._post_with_id("/admin/backup", request, federationId) + + async def discover_version(self, threshold: int) -> DiscoverVersionResponse: + request: DiscoverVersionRequest = {"threshold": threshold} + return await self._post("/admin/discover-version", request) async def federation_ids(self): return await self._get("/admin/federation-ids") async def list_operations(self, request: ListOperationsRequest): - return await self._post_with_id("/admin/list-operations", request) + return await self._post_with_federation_id("/admin/list-operations", request) - async def join(self, invite_code: str, set_default: bool = False): + async def join(self, invite_code: str, use_manual_secret: bool = False): return await self._post( - "/admin/join", {"inviteCode": invite_code, "setDefault": set_default} + "/admin/join", + {"inviteCode": invite_code, "useManualSecret": use_manual_secret}, ) - class LN: + class Lightning: def __init__(self, client): self.client = client async def create_invoice( - self, request: LnInvoiceRequest, federation_id: str = None - ): - return await self.client._post_with_id( - "/ln/invoice", request, federation_id + self, + amount_msat: int, + description: str, + expiry_time: int = None, + gateway_id: str = None, + federation_id: str = None, + ) -> LightningCreateInvoiceResponse: + request: LightningCreateInvoiceRequest = { + "amountMsat": amount_msat, + "description": description, + "expiryTime": expiry_time, + } + + return await self.client._post_with_gateway_id_and_federation_id( + "/ln/invoice", + data=request, + gateway_id=gateway_id, + federation_id=federation_id, ) - async def await_invoice( - self, request: AwaitInvoiceRequest, federation_id: str = None - ): - return await self.client._post_with_id( - "/ln/await-invoice", request, federation_id + async def create_invoice_for_pubkey_tweak( + self, + pubkey: str, + tweak: int, + amount_msat: int, + description: str, + expiry_time: int = None, + gateway_id: str = None, + federation_id: str = None, + ) -> LightningInvoiceForPubkeyTweakResponse: + request: LightningInvoiceForPubkeyTweakRequest = { + "externalPubkey": pubkey, + "tweak": tweak, + "amountMsat": amount_msat, + "description": description, + "expiryTime": expiry_time, + } + + return await self.client._post_with_gateway_id_and_federation_id( + "/ln/invoice-external-pubkey-tweaked", + data=request, + gateway_id=gateway_id, + federation_id=federation_id, ) - async def pay(self, request: LnPayRequest, federation_id: str = None): - return await self.client._post_with_id("/ln/pay", request, federation_id) + async def claim_pubkey_tweak_receives( + self, + private_key: str, + tweaks: List[int], + federation_id: str = None, + ) -> InfoResponse: + request: LightningClaimPubkeReceivesRequest = { + "privateKey": private_key, + "tweaks": tweaks, + } + + return await self.client._post_with_federation_id( + "/ln/claim-external-receive-tweaked", + data=request, + federation_id=federation_id, + ) - async def await_pay( - self, request: AwaitLnPayRequest, federation_id: str = None - ): - return await self.client._post_with_id( - "/ln/await-pay", request, federation_id + async def await_invoice( + self, operation_id: str, federation_id: str = None + ) -> InfoResponse: + request: LightningAwaitInvoiceRequest = {"operationId": operation_id} + return await self.client._post_with_gateway_id_and_federation_id( + "/ln/await-invoice", request, federation_id=federation_id ) - async def list_gateways(self): - return await self.client._get("/ln/list-gateways") + async def pay( + self, + payment_info: str, + amount_msat: Optional[int], + lightning_url_comment: Optional[str], + gateway_id: str = None, + federation_id: str = None, + ) -> LightningPayResponse: + request: LightningPayRequest = { + "paymentInfo": payment_info, + "amountMsat": amount_msat, + "lightningUrlComment": lightning_url_comment, + } + return await self.client._post_with_gateway_id_and_federation_id( + "/ln/pay", request, gateway_id=gateway_id, federation_id=federation_id + ) - async def switch_gateway( - self, request: SwitchGatewayRequest, federation_id: str = None + async def await_pay( + self, operation_id: str, gateway_id: str = None, federation_id: str = None ): - return await self.client._post_with_id( - "/ln/switch-gateway", request, federation_id + request: LightningAwaitPayRequest = {"operationId": operation_id} + return await self.client._post_with_gateway_id_and_federation_id( + "/ln/await-pay", + request, + gateway_id=gateway_id, + federation_id=federation_id, ) - class Wallet: + async def list_gateways(self) -> List[Gateway]: + return await self.client._post_with_federation_id("/ln/list-gateways") + + class Mint: def __init__(self, client): self.client = client - async def create_deposit_address( - self, request: DepositAddressRequest, federation_id: str = None - ): - return self.client._post_with_id( - "/wallet/deposit-address", data=request, federation_id=federation_id + async def decode_notes( + self, notes: str, federation_id: str = None + ) -> MintDecodeNotesResponse: + request: MintDecodeNotesRequest = {"notes": notes} + return await self.client._post_with_federation_id( + "/mint/decode-notes", request, federation_id ) - async def await_deposit( - self, request: AwaitDepositRequest, federation_id: str = None - ): - return await self.client._post_with_id( - "/wallet/await-deposit", request, federation_id - ) + async def encode_notes( + self, notes_json: NotesJson, federation_id: str = None + ) -> MintEncodeNotesResponse: + request: MintEncodeNotesRequest = {"notesJsonStr": json.dumps(notes_json)} - async def withdraw(self, request: WithdrawRequest, federation_id: str = None): - return await self.client._post_with_id( - "/wallet/withdraw", request, federation_id + return await self.client._post_with_federation_id( + "/mint/encode-notes", request, federation_id ) - class Mint: - def __init__(self, client): - self.client = client - - async def reissue(self, request: ReissueRequest, federation_id: str = None): - return await self.client._post_with_id( + async def reissue( + self, notes: str, federation_id: str = None + ) -> MintReissueResponse: + request: MintReissueRequest = {"notes": notes} + return await self.client._post_with_federation_id( "/mint/reissue", request, federation_id ) - async def spend(self, request: SpendRequest, federation_id: str = None): - return await self.client._post_with_id( + async def spend( + self, + amount_msat: int, + allow_overpay: bool, + timeout: int, + include_invite: bool, + federation_id: str = None, + ) -> MintSpendResponse: + request: MintSpendRequest = { + "amountMsat": amount_msat, + "allowOverpay": allow_overpay, + "timeout": timeout, + "includeInvite": include_invite, + } + return await self.client._post_with_federation_id( "/mint/spend", request, federation_id ) - async def validate(self, request: ValidateRequest, federation_id: str = None): - return await self.client._post_with_id( + async def validate( + self, notes: str, federation_id: str = None + ) -> MintValidateResponse: + request: MintValidateRequest = {"notes": notes} + return await self.client._post_with_federation_id( "/mint/validate", request, federation_id ) - async def split(self, request: SplitRequest, federation_id: str = None): - return await self.client._post_with_id( + async def split(self, notes: str, federation_id: str = None): + request: MintSplitRequest = {"notes": notes} + return await self.client._post_with_federation_id( "/mint/split", request, federation_id ) - async def combine(self, request: CombineRequest, federation_id: str = None): - return await self.client._post_with_id( + async def combine(self, notes_vec: List[str], federation_id: str = None): + request: MintCombineRequest = {"notesVec": notes_vec} + return await self.client._post_with_federation_id( "/mint/combine", request, federation_id ) - async def close(self): - await self.session.close() + class Onchain: + def __init__(self, client): + self.client = client + + async def create_deposit_address(self, timeout: int, federation_id: str = None): + request: OnchainDepositAddressRequest = {"timeout": timeout} + return await self.client._post_with_federation_id( + "/wallet/deposit-address", request, federation_id + ) + + async def await_deposit( + self, operation_id: str, federation_id: str = None + ) -> OnchainAwaitDepositResponse: + request: OnchainAwaitDepositRequest = {"operationId": operation_id} + return await self.client._post_with_federation_id( + "/wallet/await-deposit", request, federation_id + ) + + async def withdraw( + self, + address: str, + amount_sat: Union[int, Literal["all"]], + federationId: str = None, + ) -> OnchainWithdrawResponse: + request: OnchainWithdrawRequest = { + "address": address, + "amountSat": amount_sat, + } + return await self.client._post_with_federation_id( + "/wallet/withdraw", request, federationId + ) diff --git a/wrappers/fedimint-py/FedimintClient.py b/wrappers/fedimint-py/FedimintClient.py index 61b605e..ecd5810 100644 --- a/wrappers/fedimint-py/FedimintClient.py +++ b/wrappers/fedimint-py/FedimintClient.py @@ -1,39 +1,71 @@ +import json +import logging +from typing import List, Literal, Union import requests + from models.common import ( + DiscoverVersionRequest, + DiscoverVersionResponse, InfoResponse, ListOperationsRequest, - OperationOutput, - BackupRequest, ) -from models.ln import ( - LnInvoiceRequest, - LnInvoiceResponse, - AwaitInvoiceRequest, - LnPayRequest, - LnPayResponse, - AwaitLnPayRequest, + +from models.lightning import ( Gateway, - SwitchGatewayRequest, + LightningAwaitInvoiceRequest, + LightningClaimPubkeReceivesRequest, + LightningCreateInvoiceRequest, + LightningCreateInvoiceResponse, + LightningInvoiceForPubkeyTweakRequest, + LightningInvoiceForPubkeyTweakResponse, + LightningPayRequest, + LightningPayResponse, ) -from models.wallet import DepositAddressRequest, AwaitDepositRequest, WithdrawRequest + +from models.onchain import ( + OnchainAwaitDepositRequest, + OnchainAwaitDepositResponse, + OnchainDepositAddressRequest, + OnchainWithdrawRequest, + OnchainWithdrawResponse, +) + from models.mint import ( - ReissueRequest, - SpendRequest, - ValidateRequest, - SplitRequest, - CombineRequest, + MintDecodeNotesRequest, + MintDecodeNotesResponse, + MintEncodeNotesRequest, + MintEncodeNotesResponse, + MintReissueRequest, + MintReissueResponse, + MintSpendRequest, + MintSpendResponse, + MintValidateRequest, + MintValidateResponse, + MintSplitRequest, + MintCombineRequest, + NotesJson, ) class FedimintClient: - def __init__(self, base_url: str, password: str, active_federation_id: str): - self.base_url = f"{base_url}/fedimint/v2" + def __init__( + self, + base_url: str, + password: str, + active_federation_id: str, + active_gateway_id: str = None, + ): + self.base_url = f"{base_url}/v2" self.password = password self.active_federation_id = active_federation_id + self.active_gateway_id = active_gateway_id - self.ln = self.LN(self) - self.wallet = self.Wallet(self) + self.lightning = self.Lightning(self) + self.onchain = self.Onchain(self) self.mint = self.Mint(self) + logging.info( + "Initialized fedimint client, must set active gateway id after initialization to use lightning module methods or manually pass in gateways" + ) def get_active_federation_id(self): return self.active_federation_id @@ -41,8 +73,24 @@ def get_active_federation_id(self): def set_active_federation_id(self, federation_id: str): self.active_federation_id = federation_id + def get_active_gateway_id(self): + return self.active_gateway_id + + def set_active_gateway_id(self, gateway_id: str): + self.active_gateway_id = gateway_id + + def use_default_gateway(self): + # hits list_gateways and sets active_gatewayId to the first gateway + try: + gateways = self.lightning.list_gateways() + logging.info("Gateways: ", gateways) + self.active_gateway_id = gateways[0]["info"]["gateway_id"] + logging.info("Set active gateway id to: ", self.active_gateway_id) + except Exception as e: + logging.error("Error setting default gateway id: ", e) + def _handle_response(self, response): - if not response.ok: + if response.status_code != 200: raise Exception( f"HTTP error! status: {response.status_code}, Body: {response.text}" ) @@ -64,7 +112,9 @@ def _post(self, endpoint: str, data=None): self._handle_response(response) return response.json() - def _post_with_id(self, endpoint: str, data=None, federation_id: str = None): + def _post_with_federation_id( + self, endpoint: str, data=None, federation_id: str = None + ): if federation_id is None: federation_id = self.get_active_federation_id() @@ -74,119 +124,245 @@ def _post_with_id(self, endpoint: str, data=None, federation_id: str = None): return self._post(endpoint, data) - def info(self): - return self._get("/admin/info") + def _post_with_gateway_id_and_federation_id( + self, + endpoint: str, + data=None, + gateway_id: str = None, + federation_id: str = None, + ): + if gateway_id is None: + gateway_id = self.get_active_gateway_id() + + if federation_id is None: + federation_id = self.get_active_federation_id() + + if federation_id is None or gateway_id is None: + raise Exception( + "Must set active gateway id and active federation id before calling this method" + ) + + if data is None: + data = {} + data["gatewayId"] = gateway_id + data["federationId"] = federation_id - def backup(self, request: BackupRequest, federation_id: str = None): - return self._post_with_id("/admin/backup", request, federation_id) + return self._post(endpoint, data) + + def info(self) -> InfoResponse: + return self._get("/admin/info") def config(self): - return self.get("/admin/config") + return self._get("/admin/config") - def discover_version(self): - return self._get("/admin/discover-version") + def discover_version(self, threshold: int) -> DiscoverVersionResponse: + request: DiscoverVersionRequest = {"threshold": threshold} + return self._post("/admin/discover-version", request) def federation_ids(self): return self._get("/admin/federation-ids") def list_operations(self, request: ListOperationsRequest): - return self._fetch_with_auth("/admin/list-operations", "POST", data=request) + return self._post_with_federation_id("/admin/list-operations", request) - def join(self, invite_code: str, set_default: bool = False): + def join(self, invite_code: str, use_manual_secret: bool = False): return self._post( - "/admin/join", {"inviteCode": invite_code, "setDefault": set_default} + "/admin/join", + {"inviteCode": invite_code, "useManualSecret": use_manual_secret}, ) - # def module(self, name: str): - # return self._fetch_with_auth(f'/admin/module', 'POST') - - # def restore(self, request: RestoreRequest): - # return self._fetch_with_auth('/admin/restore', 'POST', data=request) - - class LN: + class Lightning: def __init__(self, client): self.client = client - def create_invoice(self, request: LnInvoiceRequest, federation_id: str = None): - return self.client._post_with_id( - "/ln/invoice", data=request, federation_id=federation_id + def create_invoice( + self, + amount_msat: int, + description: str, + expiry_time: int = None, + gateway_id: str = None, + federation_id: str = None, + ) -> LightningCreateInvoiceResponse: + request: LightningCreateInvoiceRequest = { + "amountMsat": amount_msat, + "description": description, + "expiryTime": expiry_time, + } + + return self.client._post_with_gateway_id_and_federation_id( + "/ln/invoice", + data=request, + gateway_id=gateway_id, + federation_id=federation_id, ) def await_invoice( - self, request: AwaitInvoiceRequest, federation_id: str = None - ): - return self.client._post_with_id( - "/ln/await-invoice", data=request, federation_id=federation_id + self, operation_id: str, federation_id: str = None + ) -> InfoResponse: + request: LightningAwaitInvoiceRequest = {"operationId": operation_id} + return self.client._post_with_federation_id( + "/ln/await-invoice", request, federation_id ) - def pay(self, request: LnPayRequest, federation_id: str = None): - return self.client._post_with_id( - "/ln/pay", data=request, federation_id=federation_id + def create_invoice_for_pubkey_tweak( + self, + pubkey: str, + tweak: int, + amount_msat: int, + description: str, + expiry_time: int = None, + gateway_id: str = None, + federation_id: str = None, + ) -> LightningInvoiceForPubkeyTweakResponse: + request: LightningInvoiceForPubkeyTweakRequest = { + "externalPubkey": pubkey, + "tweak": tweak, + "amountMsat": amount_msat, + "description": description, + "expiryTime": expiry_time, + } + + return self.client._post_with_gateway_id_and_federation_id( + "/ln/invoice-external-pubkey-tweaked", + data=request, + gateway_id=gateway_id, + federation_id=federation_id, ) - def await_pay(self, request: AwaitLnPayRequest, federation_id: str = None): - return self.client._post_with_id( - "/ln/await-pay", data=request, federation_id=federation_id + def claim_pubkey_tweak_receives( + self, + private_key: str, + tweaks: List[int], + federation_id: str = None, + ) -> InfoResponse: + request: LightningClaimPubkeReceivesRequest = { + "privateKey": private_key, + "tweaks": tweaks, + } + + return self.client._post_with_federation_id( + "/ln/claim-external-receive-tweaked", + data=request, + federation_id=federation_id, ) - def list_gateways(self): - return self.client._get("/ln/list-gateways") - - def switch_gateway( - self, request: SwitchGatewayRequest, federation_id: str = None - ): - return self.client._post_with_id( - "/ln/switch-gateway", data=request, federation_id=federation_id + def pay( + self, + payment_info: str, + amount_msat: int, + lightning_url_comment: str, + gateway_id: str = None, + federation_id: str = None, + ) -> LightningPayResponse: + request: LightningPayRequest = { + "paymentInfo": payment_info, + "amountMsat": amount_msat, + "lightningUrlComment": lightning_url_comment, + } + return self.client._post_with_gateway_id_and_federation_id( + "/ln/pay", + data=request, + gateway_id=gateway_id, + federation_id=federation_id, ) - class Wallet: + def list_gateways(self) -> List[Gateway]: + return self.client._post_with_federation_id("/ln/list-gateways") + + class Mint: def __init__(self, client): self.client = client - def create_deposit_address( - self, request: DepositAddressRequest, federation_id: str = None - ): - return self.client._post_with_id( - "/wallet/deposit-address", data=request, federation_id=federation_id + def decode_notes( + self, notes: str, federation_id: str = None + ) -> MintDecodeNotesResponse: + request: MintDecodeNotesRequest = {"notes": notes} + return self.client._post_with_federation_id( + "/mint/decode-notes", request, federation_id ) - def await_deposit( - self, request: AwaitDepositRequest, federation_id: str = None - ): - return self.client._post_with_id( - "/wallet/await-deposit", data=request, federation_id=federation_id + def encode_notes( + self, notes_json: NotesJson, federation_id: str = None + ) -> MintEncodeNotesResponse: + request: MintEncodeNotesRequest = {"notesJsonStr": json.dumps(notes_json)} + + return self.client._post_with_federation_id( + "/mint/encode-notes", request, federation_id ) - def withdraw(self, request: WithdrawRequest, federation_id: str = None): - return self.client._post_with_id( - "/wallet/withdraw", data=request, federation_id=federation_id + def reissue(self, notes: str, federation_id: str = None) -> MintReissueResponse: + request: MintReissueRequest = {"notes": notes} + return self.client._post_with_federation_id( + "/mint/reissue", request, federation_id ) - class Mint: - def __init__(self, client): - self.client = client + def spend( + self, + amount_msat: int, + allow_overpay: bool, + timeout: int, + include_invite: bool, + federation_id: str = None, + ) -> MintSpendResponse: + request: MintSpendRequest = { + "amountMsat": amount_msat, + "allowOverpay": allow_overpay, + "timeout": timeout, + "includeInvite": include_invite, + } + + return self.client._post_with_federation_id( + "/mint/spend", request, federation_id + ) + + def validate( + self, notes: str, federation_id: str = None + ) -> MintValidateResponse: + request: MintValidateRequest = {"notes": notes} + return self.client._post_with_federation_id( + "/mint/validate", request, federation_id + ) - def reissue(self, request: ReissueRequest, federation_id: str = None): - return self.client._post_with_id( - "/mint/reissue", data=request, federation_id=federation_id + def split(self, notes: str, federation_id: str = None): + request: MintSplitRequest = {"notes": notes} + return self.client._post_with_federation_id( + "/mint/split", request, federation_id ) - def spend(self, request: SpendRequest, federation_id: str = None): - return self.client._post_with_id( - "/mint/spend", data=request, federation_id=federation_id + def combine(self, notes_vec: List[str], federation_id: str = None): + request: MintCombineRequest = {"notesVec": notes_vec} + return self.client._post_with_federation_id( + "/mint/combine", request, federation_id ) - def validate(self, request: ValidateRequest, federation_id: str = None): - return self.client._post_with_id( - "/mint/validate", data=request, federation_id=federation_id + class Onchain: + def __init__(self, client): + self.client = client + + def create_deposit_address(self, timeout: int, federation_id: str = None): + request: OnchainDepositAddressRequest = {"timeout": timeout} + return self.client._post_with_federation_id( + "/wallet/deposit-address", request, federation_id ) - def split(self, request: SplitRequest, federation_id: str = None): - return self.client._post_with_id( - "/mint/split", data=request, federation_id=federation_id + def await_deposit( + self, operation_id: str, federation_id: str = None + ) -> OnchainAwaitDepositResponse: + request: OnchainAwaitDepositRequest = {"operationId": operation_id} + return self.client._post_with_federation_id( + "/wallet/await-deposit", request, federation_id ) - def combine(self, request: CombineRequest, federation_id: str = None): - return self.client._post_with_id( - "/mint/combine", data=request, federation_id=federation_id + def withdraw( + self, + address: str, + amount_sat: Union[int, Literal["all"]], + federation_id: str = None, + ) -> OnchainWithdrawResponse: + request: OnchainWithdrawRequest = { + "address": address, + "amountSat": amount_sat, + } + return self.client._post_with_federation_id( + "/wallet/withdraw", request, federation_id ) diff --git a/wrappers/fedimint-py/models/common.py b/wrappers/fedimint-py/models/common.py index 4eebd4a..3268185 100644 --- a/wrappers/fedimint-py/models/common.py +++ b/wrappers/fedimint-py/models/common.py @@ -1,22 +1,23 @@ from pydantic import RootModel, BaseModel -from typing import Optional, Dict, Any +from typing import List, Optional, Dict, Any -class Tiered(RootModel): - root: Dict[int, Any] +Tiered = RootModel[Dict[int, Any]] class TieredSummary(BaseModel): tiered: Tiered -class InfoResponse(BaseModel): - federation_id: str +class FederationInfo(BaseModel): network: str meta: Dict[str, str] - total_amount_msat: int - total_num_notes: int - denominations_msat: TieredSummary + totalAmountMsat: int + totalNumNotes: int + denominationsMsat: TieredSummary + + +InfoResponse = RootModel[Dict[str, FederationInfo]] class BackupRequest(BaseModel): @@ -29,7 +30,37 @@ class ListOperationsRequest(BaseModel): class OperationOutput(BaseModel): id: str - creation_time: str - operation_kind: str - operation_meta: Any + creationTime: str + operationKind: str + operationMeta: Any + outcome: Optional[Any] + + +class DiscoverVersionRequest(BaseModel): + threshold: Optional[int] + + +# Returns a dictionary of federation_ids and their api versions +DiscoverVersionResponse = RootModel[Dict[str, Any]] + + +class JoinRequest(BaseModel): + inviteCode: str + useManualSecret: bool + + +class JoinResponse(BaseModel): + thisFederationId: str + federationIds: List[str] + + +class ListOperationsRequest(BaseModel): + limit: int + + +class OperationOutput(BaseModel): + id: str + creationTime: str + operationKind: str + operationMeta: Any outcome: Optional[Any] diff --git a/wrappers/fedimint-py/models/lightning.py b/wrappers/fedimint-py/models/lightning.py new file mode 100644 index 0000000..b291d52 --- /dev/null +++ b/wrappers/fedimint-py/models/lightning.py @@ -0,0 +1,81 @@ +from pydantic import BaseModel +from typing import Any, List, Optional + + +class LightningCreateInvoiceRequest(BaseModel): + amountMsat: int + description: str + expiryTime: Optional[int] + + +class LightningCreateInvoiceResponse(BaseModel): + operationId: str + invoice: str + + +class LightningInvoiceForPubkeyTweakRequest(BaseModel): + amountMsat: int + description: str + externalPubkey: str + tweak: int + expiryTime: Optional[int] + + +class LightningInvoiceForPubkeyTweakResponse(BaseModel): + operationId: str + invoice: str + + +class LightningClaimPubkeReceivesRequest(BaseModel): + privateKey: str + tweaks: List[int] + + +class LightningAwaitInvoiceRequest(BaseModel): + operationId: str + + +class LightningPayRequest(BaseModel): + paymentInfo: str + amountMsat: Optional[int] + lightningUrlComment: Optional[str] + + +class LightningPayResponse(BaseModel): + operationId: str + paymentType: str + contractId: str + fee: int + + +class LightningAwaitPayRequest(BaseModel): + operationId: str + + +class GatewayFees(BaseModel): + baseMsat: int + proportionalMillionths: int + + +class GatewayInfo(BaseModel): + api: str + fees: GatewayFees + gatewayId: str + gatewayRedeemKey: str + lightningAlias: str + mintChannelId: int + nodePubKey: str + routeHints: List[Any] + supportsPrivatePayments: bool + + +class GatewayTTL(BaseModel): + nanos: int + secs: int + + +class Gateway(BaseModel): + federationId: str + info: GatewayInfo + ttl: GatewayTTL + vetted: bool diff --git a/wrappers/fedimint-py/models/ln.py b/wrappers/fedimint-py/models/ln.py deleted file mode 100644 index 3d09006..0000000 --- a/wrappers/fedimint-py/models/ln.py +++ /dev/null @@ -1,44 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class LnInvoiceRequest(BaseModel): - amount_msat: int - description: str - expiry_time: Optional[int] - - -class LnInvoiceResponse(BaseModel): - operation_id: str - invoice: str - - -class AwaitInvoiceRequest(BaseModel): - operation_id: str - - -class LnPayRequest(BaseModel): - payment_info: str - amount_msat: Optional[int] - finish_in_background: bool - lnurl_comment: Optional[str] - - -class LnPayResponse(BaseModel): - operation_id: str - payment_type: str - contract_id: str - fee: int - - -class AwaitLnPayRequest(BaseModel): - operation_id: str - - -class Gateway(BaseModel): - node_pub_key: str - active: bool - - -class SwitchGatewayRequest(BaseModel): - gateway_id: str diff --git a/wrappers/fedimint-py/models/mint.py b/wrappers/fedimint-py/models/mint.py index bde3ce2..674dc76 100644 --- a/wrappers/fedimint-py/models/mint.py +++ b/wrappers/fedimint-py/models/mint.py @@ -1,90 +1,72 @@ -from pydantic import BaseModel, RootModel -from typing import Optional, Dict, List, Union +from pydantic import BaseModel +from typing import Dict, List -class FederationIdPrefix(RootModel): - root: List[int] +class MintDecodeNotesRequest(BaseModel): + notes: str -class Fp(RootModel): - root: List[int] +class Note(BaseModel): + signature: str + spendKey: str -class Choice(RootModel): - root: int +class NotesJson(BaseModel): + federation_id_prefix: str + notes: Dict[str, List[Note]] -class G1Affine(BaseModel): - x: Fp - y: Fp - infinity: Choice +class MintDecodeNotesResponse(BaseModel): + notesJson: NotesJson -class Signature(RootModel): - root: G1Affine +class MintEncodeNotesRequest(BaseModel): + notesJsonStr: str -class KeyPair(RootModel): - root: List[int] +class MintEncodeNotesResponse(BaseModel): + notes: str -class SpendableNote(BaseModel): - signature: Signature - spend_key: KeyPair +class MintReissueRequest(BaseModel): + notes: str -class TieredMulti(RootModel): - root: Dict[int, List[SpendableNote]] +class MintReissueResponse(BaseModel): + amountMsat: int -class OOBNotesData(BaseModel): - Notes: Optional[TieredMulti] - FederationIdPrefix: Optional[FederationIdPrefix] - Default: Optional[Dict[str, Union[int, List[int]]]] - - -class OOBNotes(RootModel): - root: List[OOBNotesData] - - -class ReissueRequest(BaseModel): - notes: OOBNotes - - -class ReissueResponse(BaseModel): - amount_msat: int - - -class SpendRequest(BaseModel): - amount_msat: int - allow_overpay: bool +class MintSpendRequest(BaseModel): + amountMsat: int + allowOverpay: bool timeout: int + includeInvite: bool -class SpendResponse(BaseModel): +class MintSpendResponse(BaseModel): operation: str - notes: OOBNotes + notes: str -class ValidateRequest(BaseModel): - notes: OOBNotes +class MintValidateRequest(BaseModel): + notes: str -class ValidateResponse(BaseModel): - amount_msat: int +class MintValidateResponse(BaseModel): + amountMsat: int -class SplitRequest(BaseModel): - notes: OOBNotes +class MintSplitRequest(BaseModel): + notes: str -class SplitResponse(BaseModel): - notes: Dict[int, OOBNotes] +class MintSplitResponse(BaseModel): + notes: Dict[int, str] -class CombineRequest(BaseModel): - notes: List[OOBNotes] +class MintCombineRequest(BaseModel): + notesVec: List[str] -class CombineResponse(BaseModel): - notes: OOBNotes +class MintCombineResponse(BaseModel): + notes: str diff --git a/wrappers/fedimint-py/models/onchain.py b/wrappers/fedimint-py/models/onchain.py new file mode 100644 index 0000000..53d1482 --- /dev/null +++ b/wrappers/fedimint-py/models/onchain.py @@ -0,0 +1,53 @@ +from typing import Dict, List, Literal, Union +from pydantic import BaseModel + + +class OnchainDepositAddressRequest(BaseModel): + timeout: int + + +class OnchainDepositAddressResponse(BaseModel): + operation_id: str + address: str + + +class OnchainAwaitDepositRequest(BaseModel): + operation_id: str + + +class BTCInput(BaseModel): + previous_output: str + script_sig: str + sequence: int + witness: List[str] + + +class BTCOutput(BaseModel): + value: int + script_pubkey: str + + +class BTCTransaction(BaseModel): + version: int + lock_time: int + input: List[BTCInput] + output: List[BTCOutput] + + +class AwaitDepositResponseConfirmed(BaseModel): + btc_transaction: BTCTransaction + out_idx: int + + +class OnchainAwaitDepositResponse(BaseModel): + status: Union[Dict[str, AwaitDepositResponseConfirmed], Dict[str, str]] + + +class OnchainWithdrawRequest(BaseModel): + address: str + amount_sat: Union[int, Literal["all"]] + + +class OnchainWithdrawResponse(BaseModel): + txid: str + fees_sat: int diff --git a/wrappers/fedimint-py/models/wallet.py b/wrappers/fedimint-py/models/wallet.py deleted file mode 100644 index 83879cb..0000000 --- a/wrappers/fedimint-py/models/wallet.py +++ /dev/null @@ -1,28 +0,0 @@ -from pydantic import BaseModel - - -class DepositAddressRequest(BaseModel): - timeout: int - - -class DepositAddressResponse(BaseModel): - operation_id: str - address: str - - -class AwaitDepositRequest(BaseModel): - operation_id: str - - -class AwaitDepositResponse(BaseModel): - status: str - - -class WithdrawRequest(BaseModel): - address: str - amount_msat: str - - -class WithdrawResponse(BaseModel): - txid: str - fees_sat: int diff --git a/wrappers/fedimint-py/requirements.txt b/wrappers/fedimint-py/requirements.txt index f229360..570fdd0 100644 --- a/wrappers/fedimint-py/requirements.txt +++ b/wrappers/fedimint-py/requirements.txt @@ -1 +1 @@ -requests +requests coincurve diff --git a/wrappers/fedimint-py/test.py b/wrappers/fedimint-py/test.py new file mode 100644 index 0000000..46fcd06 --- /dev/null +++ b/wrappers/fedimint-py/test.py @@ -0,0 +1,158 @@ +import os +from coincurve import PrivateKey +from FedimintClient import FedimintClient + + +def log_method(method: str): + print("--------------------") + print(f"Method: {method}") + + +def log_input_and_output(input_data, output): + print("Input: ", input_data) + print("Output: ", output) + print("--------------------") + + +def new_key_pair(): + private_key = PrivateKey() + public_key = private_key.public_key.format(compressed=False).hex() + return {"privateKey": private_key.to_hex(), "publicKey": public_key} + + +def build_test_client(): + base_url = os.getenv("FEDIMINT_CLIENTD_BASE_URL", "127.0.0.1:3333") + password = os.getenv("FEDIMINT_CLIENTD_PASSWORD", "password") + active_federation_id = os.getenv( + "FEDIMINT_CLIENTD_ACTIVE_FEDERATION_ID", + "15db8cb4f1ec8e484d73b889372bec94812580f929e8148b7437d359af422cd3", + ) + + client = FedimintClient(base_url, password, active_federation_id) + client.use_default_gateway() + print("Default gateway id: ", client.get_active_gateway_id()) + return client + + +def main(): + fedimint_client = build_test_client() + key_pair = new_key_pair() + print("Generated key pair: ", key_pair) + + # ADMIN METHODS + log_method("/v2/admin/config") + data = fedimint_client.config() + log_input_and_output({}, data) + + log_method("/v2/admin/discover-version") + data = fedimint_client.discover_version(1) + log_input_and_output({}, data) + + log_method("/v2/admin/federation-ids") + data = fedimint_client.federation_ids() + log_input_and_output({}, data) + + log_method("/v2/admin/info") + data = fedimint_client.info() + log_input_and_output({}, data) + + invite_code = os.getenv( + "INVITE_CODE", + "fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75", + ) + log_method("/v2/admin/join") + data = fedimint_client.join(invite_code, False) + log_input_and_output({"inviteCode": invite_code}, data) + + log_method("/v2/admin/list-operations") + data = fedimint_client.list_operations({"limit": 10}) + log_input_and_output({"limit": 10}, data) + + # LIGHTNING METHODS + log_method("/v2/ln/list-gateways") + data = fedimint_client.lightning.list_gateways() + log_input_and_output({}, data) + + log_method("/v2/ln/invoice") + data = fedimint_client.lightning.create_invoice(10000, "test") + log_input_and_output({"amountMsat": 10000, "description": "test"}, data) + + log_method("/v2/ln/pay") + pay_response = fedimint_client.lightning.pay(data["invoice"], None, None) + log_input_and_output({"paymentInfo": data["invoice"]}, pay_response) + + log_method("/v2/ln/await-invoice") + pay_data = fedimint_client.lightning.await_invoice(data["operationId"]) + log_input_and_output({"operationId": data["operationId"]}, pay_data) + + log_method("/v2/ln/create-invoice-for-pubkey-tweaked") + data = fedimint_client.lightning.create_invoice_for_pubkey_tweak( + pubkey=key_pair["publicKey"], tweak=1, amount_msat=1000, description="test" + ) + log_input_and_output( + { + "pubkey": key_pair["publicKey"], + "tweak": 1, + "amountMsat": 1000, + "description": "test", + }, + data, + ) + + fedimint_client.lightning.pay(data["invoice"], None, None) + print("Paid locked invoice!") + + log_method("/v2/ln/claim-external-receive-tweaked") + data = fedimint_client.lightning.claim_pubkey_tweak_receives( + private_key=key_pair["privateKey"], + tweaks=[1], + federation_id=fedimint_client.get_active_federation_id(), + ) + log_input_and_output({"privateKey": key_pair["privateKey"], "tweaks": [1]}, data) + + # MINT METHODS + log_method("/v2/mint/spend") + mint_data = fedimint_client.mint.spend(3000, True, 1000, False) + log_input_and_output({"allowOverpay": True, "timeout": 1000}, mint_data) + + log_method("/v2/mint/decode-notes") + data = fedimint_client.mint.decode_notes(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + + log_method("/v2/mint/encode-notes") + data = fedimint_client.mint.encode_notes(data["notesJson"]) + log_input_and_output({"notesJson": data}, data) + + log_method("/v2/mint/validate") + data = fedimint_client.mint.validate(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + + log_method("/v2/mint/reissue") + data = fedimint_client.mint.reissue(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + + log_method("/v2/mint/split") + data = fedimint_client.mint.split(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + + log_method("/v2/mint/combine") + notes_vec = [data["notes"]] + notes_values_vec = [ + value for note_dict in notes_vec for value in note_dict.values() + ] + data = fedimint_client.mint.combine(notes_values_vec) + log_input_and_output({"notesVec": notes_values_vec}, data) + + # ONCHAIN METHODS + log_method("/v2/onchain/deposit-address") + data = fedimint_client.onchain.create_deposit_address(1000) + log_input_and_output({"timeout": 1000}, data) + log_method("/v2/onchain/withdraw") + withdraw_data = fedimint_client.onchain.withdraw(data["address"], 1000) + log_input_and_output({"address": data["address"], "amountSat": 1000}, withdraw_data) + + print("Done: All methods tested successfully!") + + +if __name__ == "__main__": + main() diff --git a/wrappers/fedimint-py/test_async.py b/wrappers/fedimint-py/test_async.py new file mode 100644 index 0000000..cfdbee8 --- /dev/null +++ b/wrappers/fedimint-py/test_async.py @@ -0,0 +1,176 @@ +import asyncio +import os +from coincurve import PrivateKey +from AsyncFedimintClient import AsyncFedimintClient + + +def log_method(method: str): + print("--------------------") + print(f"Method: {method}") + + +def log_input_and_output(input_data, output): + print("Input: ", input_data) + print("Output: ", output) + print("--------------------") + + +def new_key_pair(): + private_key = PrivateKey() + public_key = private_key.public_key.format(compressed=False).hex() + return {"privateKey": private_key.to_hex(), "publicKey": public_key} + + +async def build_test_client(): + base_url = os.getenv("FEDIMINT_CLIENTD_BASE_URL", "127.0.0.1:3333") + password = os.getenv("FEDIMINT_CLIENTD_PASSWORD", "password") + active_federation_id = os.getenv( + "FEDIMINT_CLIENTD_ACTIVE_FEDERATION_ID", + "15db8cb4f1ec8e484d73b889372bec94812580f929e8148b7437d359af422cd3", + ) + + client = AsyncFedimintClient(base_url, password, active_federation_id) + await client.use_default_gateway() + print("Default gateway id: ", client.get_active_gateway_id()) + return client + + +async def main(): + fedimint_client = await build_test_client() + key_pair = new_key_pair() + print("Generated key pair: ", key_pair) + + # ADMIN METHODS + # `/v2/admin/config` + log_method("/v2/admin/config") + data = await fedimint_client.config() + # log_input_and_output({}, data) + # `/v2/admin/discover-version` + log_method("/v2/admin/discover-version") + data = await fedimint_client.discover_version( + 1 + ) # Assuming threshold is required, adjust as needed + log_input_and_output({}, data) + # `/v2/admin/federation-ids` + log_method("/v2/admin/federation-ids") + data = await fedimint_client.federation_ids() + log_input_and_output({}, data) + # `/v2/admin/info` + log_method("/v2/admin/info") + data = await fedimint_client.info() + log_input_and_output({}, data) + # `/v2/admin/join` + invite_code = os.getenv( + "INVITE_CODE", + "fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75", + ) + log_method("/v2/admin/join") + data = await fedimint_client.join(invite_code, False) + log_input_and_output({"inviteCode": invite_code}, data) + # `/v2/admin/list-operations` + log_method("/v2/admin/list-operations") + data = await fedimint_client.list_operations({"limit": 10}) + log_input_and_output({"limit": 10}, data) + + # LIGHTNING METHODS + # `/v2/ln/list-gateways` + log_method("/v2/ln/list-gateways") + data = await fedimint_client.lightning.list_gateways() + log_input_and_output({}, data) + # `/v2/ln/invoice` + log_method("/v2/ln/invoice") + data = await fedimint_client.lightning.create_invoice(10000, "test") + log_input_and_output({"amountMsat": 10000, "description": "test"}, data) + # `/v2/ln/pay` + log_method("/v2/ln/pay") + pay_response = await fedimint_client.lightning.pay(data["invoice"], None, None) + log_input_and_output({"paymentInfo": data["invoice"]}, pay_response) + # `/v2/ln/await-invoice` + log_method("/v2/ln/await-invoice") + pay_data = await fedimint_client.lightning.await_invoice(data["operationId"]) + log_input_and_output({"operationId": data["operationId"]}, pay_data) + + # LIGHTNING METHODS FOR PUBKEY TWEAK + # `/v2/ln/create-invoice-for-pubkey-tweaked` + log_method("/v2/ln/create-invoice-for-pubkey-tweaked") + data = await fedimint_client.lightning.create_invoice_for_pubkey_tweak( + pubkey=key_pair["publicKey"], tweak=1, amount_msat=1000, description="test" + ) + log_input_and_output( + { + "pubkey": key_pair["publicKey"], + "tweak": 1, + "amountMsat": 1000, + "description": "test", + }, + data, + ) + + # Pay the invoice + fedimint_client.lightning.pay(data["invoice"], None, None) + print("Paid locked invoice!") + + # `/v2/ln/claim-external-pubkey-tweaked` + log_method("/v2/ln/claim-external-pubkey-tweaked") + data = await fedimint_client.lightning.claim_pubkey_tweak_receives( + private_key=key_pair["privateKey"], + tweaks=[1], + federation_id=fedimint_client.get_active_federation_id(), + ) + log_input_and_output({"privateKey": key_pair["privateKey"], "tweaks": [1]}, data) + + # MINT METHODS + # `/v2/mint/spend` + log_method("/v2/mint/spend") + mint_data = await fedimint_client.mint.spend(3000, True, 1000, False) + log_input_and_output({"allowOverpay": True, "timeout": 1000}, mint_data) + # `/v2/mint/decode-notes` + log_method("/v2/mint/decode-notes") + data = await fedimint_client.mint.decode_notes(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + # `/v2/mint/encode-notes` + log_method("/v2/mint/encode-notes") + data = await fedimint_client.mint.encode_notes(data["notesJson"]) + log_input_and_output({"notesJson": data}, data) + # `/v2/mint/validate` + log_method("/v2/mint/validate") + data = await fedimint_client.mint.validate(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + # `/v2/mint/reissue` + log_method("/v2/mint/reissue") + data = await fedimint_client.mint.reissue(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + # `/v2/mint/split` + log_method("/v2/mint/split") + data = await fedimint_client.mint.split(mint_data["notes"]) + log_input_and_output({"notes": mint_data["notes"]}, data) + # `/v2/mint/combine` + log_method("/v2/mint/combine") + notes_vec = [data["notes"]] + print("notes_vec: ", notes_vec) + notes_values_vec = [ + value for note_dict in notes_vec for value in note_dict.values() + ] + data = await fedimint_client.mint.combine(notes_values_vec) + log_input_and_output({"notesVec": notes_values_vec}, data) + + # ONCHAIN METHODS + # `/v2/onchain/deposit-address` + log_method("/v2/onchain/deposit-address") + data = await fedimint_client.onchain.create_deposit_address(1000) + print("data: ", data) + log_input_and_output({"timeout": 1000}, data) + # `/v2/onchain/withdraw` + log_method("/v2/onchain/withdraw") + withdraw_data = await fedimint_client.onchain.withdraw(data["address"], 1000) + log_input_and_output({"address": data["address"], "amountSat": 1000}, data) + # `/v2/onchain/await-deposit` + # this blocks for 10 minutes so it's not a good test + + print("Done: All methods tested successfully!") + + await fedimint_client.close() + + +# Run the main function +asyncio.run(main()) diff --git a/wrappers/fedimint-ts/FedimintClient.ts b/wrappers/fedimint-ts/FedimintClient.ts index f84ce4b..da35cd3 100644 --- a/wrappers/fedimint-ts/FedimintClient.ts +++ b/wrappers/fedimint-ts/FedimintClient.ts @@ -7,15 +7,12 @@ import type { DiscoverVersionRequest, DiscoverVersionResponse, JoinRequest, - LnInvoiceExternalPubkeyTweakedRequest, - LnInvoiceExternalPubkeyTweakedResponse, - LnClaimPubkeyReceiveTweakedRequest, - LnAwaitInvoiceRequest, + LightningInvoiceExternalPubkeyTweakedRequest, + LightningClaimPubkeyReceiveTweakedRequest as LightningClaimPubkeyTweakReceivesRequest, + LightningAwaitInvoiceRequest, Gateway, - LnInvoiceRequest, - LnInvoiceResponse, - LnPayRequest, - LnPayResponse, + LightningInvoiceRequest, + LightningPayRequest, MintCombineRequest, MintCombineResponse, MintReissueRequest, @@ -38,6 +35,9 @@ import type { MintEncodeNotesResponse, MintDecodeNotesResponse, JoinResponse, + LightningInvoiceResponse, + LightningInvoiceExternalPubkeyTweakedResponse, + LightningPayResponse, } from "./types"; /** @@ -217,8 +217,6 @@ class FedimintClient { * @param body - The body of the request. */ private async post(endpoint: string, body: any): FedimintResponse { - console.log("body: ", body); - console.log("endpoint: ", this.baseUrl + endpoint); const res = await fetch(`${this.baseUrl}${endpoint}`, { method: "POST", headers: { @@ -228,8 +226,6 @@ class FedimintClient { body: JSON.stringify(body), }); - console.log("res: ", res); - if (!res.ok) { throw new Error( `POST request failed. Status: ${res.status}, Body: ${await res.text()}` @@ -337,7 +333,6 @@ class FedimintClient { threshold?: number ): FedimintResponse { const request: DiscoverVersionRequest = threshold ? { threshold } : {}; - console.log("request", request); return this.post( "/admin/discover-version", @@ -410,10 +405,14 @@ class FedimintClient { expiryTime?: number, gatewayId?: string, federationId?: string - ): FedimintResponse => { - const request: LnInvoiceRequest = { amountMsat, description, expiryTime }; + ): FedimintResponse => { + const request: LightningInvoiceRequest = { + amountMsat, + description, + expiryTime, + }; - return await this.postWithGatewayIdAndFederationId( + return await this.postWithGatewayIdAndFederationId( "/ln/invoice", request, gatewayId, @@ -441,8 +440,8 @@ class FedimintClient { expiryTime?: number, gatewayId?: string, federationId?: string - ): FedimintResponse => { - const request: LnInvoiceExternalPubkeyTweakedRequest = { + ): FedimintResponse => { + const request: LightningInvoiceExternalPubkeyTweakedRequest = { externalPubkey: pubkey, tweak, amountMsat, @@ -450,7 +449,7 @@ class FedimintClient { expiryTime, }; - return await this.postWithGatewayIdAndFederationId( + return await this.postWithGatewayIdAndFederationId( "/ln/invoice-external-pubkey-tweaked", request, gatewayId, @@ -470,7 +469,7 @@ class FedimintClient { tweaks: number[], federationId: string ): FedimintResponse => { - const request: LnClaimPubkeyReceiveTweakedRequest = { + const request: LightningClaimPubkeyTweakReceivesRequest = { privateKey, tweaks, }; @@ -491,7 +490,7 @@ class FedimintClient { operationId: string, federationId?: string ): FedimintResponse => { - const request: LnAwaitInvoiceRequest = { operationId }; + const request: LightningAwaitInvoiceRequest = { operationId }; return await this.postWithFederationId( "/ln/await-invoice", @@ -501,22 +500,22 @@ class FedimintClient { }, /** - * Pays a lightning invoice or lnurl via a gateway + * Pays a lightning invoice or Lightningurl via a gateway */ pay: async ( paymentInfo: string, amountMsat?: number, - lnurlComment?: string, + LightningurlComment?: string, gatewayId?: string, federationId?: string - ): FedimintResponse => { - const request: LnPayRequest = { + ): FedimintResponse => { + const request: LightningPayRequest = { paymentInfo, amountMsat, - lnurlComment, + LightningurlComment, }; - return await this.postWithGatewayIdAndFederationId( + return await this.postWithGatewayIdAndFederationId( "/ln/pay", request, gatewayId, diff --git a/wrappers/fedimint-ts/types.ts b/wrappers/fedimint-ts/types.ts index 0c24d92..09cc701 100644 --- a/wrappers/fedimint-ts/types.ts +++ b/wrappers/fedimint-ts/types.ts @@ -105,30 +105,30 @@ interface OnchainWithdrawResponse { feesSat: number; } -interface LnInvoiceRequest { +interface LightningInvoiceRequest { amountMsat: number; description: string; expiryTime?: number; } -interface LnInvoiceResponse { +interface LightningInvoiceResponse { operationId: string; invoice: string; } -interface LnInvoiceExternalPubkeyRequest { +interface LightningInvoiceExternalPubkeyRequest { amountMsat: number; description: string; externalPubkey: string; expiryTime?: number; } -interface LnInvoiceExternalPubkeyResponse { +interface LightningInvoiceExternalPubkeyResponse { operationId: string; invoice: string; } -interface LnInvoiceExternalPubkeyTweakedRequest { +interface LightningInvoiceExternalPubkeyTweakedRequest { amountMsat: number; description: string; externalPubkey: string; @@ -136,38 +136,38 @@ interface LnInvoiceExternalPubkeyTweakedRequest { expiryTime?: number; } -interface LnInvoiceExternalPubkeyTweakedResponse { +interface LightningInvoiceExternalPubkeyTweakedResponse { operationId: string; invoice: string; } -interface LnClaimPubkeyReceiveRequest { +interface LightningClaimPubkeyReceiveRequest { privateKey: string; } -interface LnClaimPubkeyReceiveTweakedRequest { +interface LightningClaimPubkeyReceiveTweakedRequest { privateKey: string; tweaks: number[]; } -interface LnAwaitInvoiceRequest { +interface LightningAwaitInvoiceRequest { operationId: string; } -interface LnPayRequest { +interface LightningPayRequest { paymentInfo: string; amountMsat?: number; - lnurlComment?: string; + LightningurlComment?: string; } -interface LnPayResponse { +interface LightningPayResponse { operationId: string; paymentType: string; contractId: string; fee: number; } -interface LnAwaitPayRequest { +interface LightningAwaitPayRequest { operationId: string; } @@ -184,8 +184,8 @@ interface GatewayInfo { } interface GatewayFees { - base_msat: number; - proportional_millionths: number; + baseMsat: number; + proportionalMillionths: number; } interface GatewayTTL { @@ -200,16 +200,6 @@ interface Gateway { vetted: boolean; } -interface SwitchGatewayRequest { - gatewayId: string; -} - -type FederationIdPrefix = string; - -interface TieredMulti { - [amount: number]: T[]; -} - interface MintDecodeNotesRequest { notes: string; } @@ -270,13 +260,15 @@ interface MintCombineResponse { notes: string; } +interface Note { + signature: string; + spend_key: string; +} + interface NotesJson { federation_id_prefix: string; notes: { - [denomination: string]: Array<{ - signature: string; - spend_key: string; - }>; + [denomination: string]: Note[]; }; } @@ -298,21 +290,20 @@ export type { OnchainAwaitDepositResponse, OnchainWithdrawRequest, OnchainWithdrawResponse, - LnInvoiceRequest, - LnInvoiceResponse, - LnInvoiceExternalPubkeyRequest, - LnInvoiceExternalPubkeyResponse, - LnInvoiceExternalPubkeyTweakedRequest, - LnInvoiceExternalPubkeyTweakedResponse, - LnClaimPubkeyReceiveRequest, - LnClaimPubkeyReceiveTweakedRequest, - LnAwaitInvoiceRequest, - LnPayRequest, - LnPayResponse, - LnAwaitPayRequest, + LightningInvoiceRequest, + LightningInvoiceResponse, + LightningInvoiceExternalPubkeyRequest, + LightningInvoiceExternalPubkeyResponse, + LightningInvoiceExternalPubkeyTweakedRequest, + LightningInvoiceExternalPubkeyTweakedResponse, + LightningClaimPubkeyReceiveRequest, + LightningClaimPubkeyReceiveTweakedRequest, + LightningAwaitInvoiceRequest, + LightningPayRequest, + LightningPayResponse, + LightningAwaitPayRequest, Gateway, NotesJson, - SwitchGatewayRequest, MintDecodeNotesRequest, MintDecodeNotesResponse, MintEncodeNotesRequest,