From bb00867274aff13a31aca24f27f6f73a6ed50347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 5 Jun 2024 11:03:36 +0300 Subject: [PATCH 01/15] Updates post-meetings. --- facades/main_facade.md | 130 ++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 35 deletions(-) diff --git a/facades/main_facade.md b/facades/main_facade.md index 8708470..5265c4f 100644 --- a/facades/main_facade.md +++ b/facades/main_facade.md @@ -1,51 +1,58 @@ +TODO: find a name (think of space / astronomy stuff). +TODO: have separate classes for most important networks: MainnetEntrypoint, DevnetEntrypoint, TestnetEntrypoint +TODO: add functions for contract upgrade, as well + +## Account + +``` +class Account: + address: IAddress; + nonce: int; + + sign(data: bytes): bytes; + get_nonce_then_increment(): int; +``` + ## MainFacade ``` +// All functions that create transactions receive a sender Account. +// They also receive a nonce, which is optional. If not provided, the nonce is fetched from the network. +// Furthermore, all functions that create transactions return already-signed transactions. class MainFacade: // The constructor is not captured by the specs; it's up to the implementing library to define it. // For example, it can be parametrized with: // - a network provider URL and kind (proxy, API) // - a chain ID - register_secret_key(secret_key: ISecretKey); - register_wallet(wallet: Any); + // TBD + load_account(path: Path, password: Optional[string], address_index: Optional[int]): Account; + - sign_transaction(transaction: Transaction); - sign_message(message: Message): Signature; verify_transaction_signature(transaction: Transaction): bool; verify_message_signature(message: Message): bool; recall_account_nonce(address: IAddress); - // Account is looked up by "transaction.sender". - apply_nonce_on_transaction(transaction: Transaction); - send_transaction(transaction: Transaction); - await_completed_transaction(transaction_hash: string): TransactionOutcome; - create_transaction_for_native_token_transfer({ - sender: IAddress; - receiver: IAddress; - native_amount: Amount; - data: Optional[bytes]; - }): Transaction; + // Generic function to await a transaction on the network. + // This, in contrast to the await* functions specialized for contract deployment and execution, does not parse the transaction. + await_completed_transaction(transaction_hash: string): TransactionOnNetwork; - create_transaction_for_esdt_token_transfer({ - sender: IAddress; + create_transaction_for_transfer({ + sender: Account; + nonce: Optional[int]; receiver: IAddress; + native_amount: Optional[Amount]; + data: Optional[bytes]; token_transfers: TokenTransfer[]; }): Transaction; - // ABI is registered by name. - // When needed (in the contract-related utility functions), ABI is looked up by name. - register_abi({ - contract: IAddress; - abi: Abi; - }): void; - - create_transaction_for_deploy({ - contract_name: string; - sender: IAddress; + create_transaction_for_contract_deploy({ + abi: Optional[Abi]; + sender: Account; + nonce: Optional[int]; bytecode: bytes OR bytecodePath: Path; arguments: List[object] = []; native_transfer_amount: Amount = 0; @@ -57,8 +64,8 @@ class MainFacade: }): Transaction; parse_contract_deploy({ - contract_name: string; - transaction_outcome: TransactionOutcome + abi: Optional[Abi]; + transaction_on_network: TransactionOnNetwork }): { return_code: string; return_message: string; @@ -69,9 +76,10 @@ class MainFacade: }]; }; + // Specialized function to await and parse a contract deployment transaction. // Does "await_completed_transaction" and "parse_contract_deploy" in one go. await_completed_contract_deploy({ - contract_name: string; + abi: Optional[Abi]; transaction_hash: string; }): { return_code: string; @@ -84,8 +92,9 @@ class MainFacade: }; create_transaction_for_contract_execute({ - contract_name: string; - sender: IAddress; + abi: Optional[Abi]; + sender: Account; + nonce: Optional[int]; contract: IAddress; // If "function" is a reserved word in the implementing language, it should be replaced with a different name (e.g. "func" or "functionName"). function: string; @@ -96,8 +105,8 @@ class MainFacade: }): Transaction; parse_contract_execute({ - contract_name: string; - transaction_outcome: TransactionOutcome, + abi: Optional[Abi]; + transaction_on_network: TransactionOnNetwork, function?: string }): { values: List[any]; @@ -105,9 +114,10 @@ class MainFacade: return_message: string; }; + // Specialized function to await and parse a contract execution transaction. // Does "await_completed_transaction" and "parse_contract_execute" in one go. await_completed_contract_execute({ - contract_name: string; + abi: Optional[Abi]; transaction_hash: string; }): { values: List[any]; @@ -116,7 +126,7 @@ class MainFacade: }; query_contract({ - contract_name: string; + abi: Optional[Abi]; contract: IAddress; caller?: IAddress; value?: Amount; @@ -124,4 +134,54 @@ class MainFacade: arguments: List[object]; block_nonce?: int; }): List[any]; + + // Below are functions of the network providers, promoted to the facade. + // get_account() + // TBD + + // Below are the most useful functions of the transactions factories, promoted to the facade. + + create_relayed_transaction({ + relayer: Account; + nonce: Optional[int]; + inner_transactions: List[ITransaction]; + }): Transaction; + + create_transaction_for_issuing_token({ + TBD or more specific functions, as in the factory. + }); + +``` + +## Examples + +### Deploying a contract + +``` +facade = MainFacade(...); +sender = Account(...); +abi = Abi(...); + +transaction = facade.create_transaction_for_deploy({ + abi: abi, + sender: sender, + nonce: sender.get_nonce_then_increment(), + bytecode: bytecode, + arguments: [arg1, arg2], + gasLimit: 1000000 +}); + +transaction_hash = facade.send_transaction(transaction); +parsed_outcome = facade.await_completed_contract_deploy(abi, transaction_hash); +``` + +### Accessing particular transaction factories + +``` +facade = MainFacade(...); +sender = Account(...); + +facade.get_token_management_transactions_factory() +OR +facade.transaction_factories.token_management ``` From 4f14ae39a6e4477512311e87caeb82d487688870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 10:40:22 +0300 Subject: [PATCH 02/15] Contract upgrade. --- facades/main_facade.md | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/facades/main_facade.md b/facades/main_facade.md index 5265c4f..f3ca045 100644 --- a/facades/main_facade.md +++ b/facades/main_facade.md @@ -1,6 +1,5 @@ TODO: find a name (think of space / astronomy stuff). TODO: have separate classes for most important networks: MainnetEntrypoint, DevnetEntrypoint, TestnetEntrypoint -TODO: add functions for contract upgrade, as well ## Account @@ -63,6 +62,7 @@ class MainFacade: gasLimit: uint32; }): Transaction; + // This method is less important (supports an exotic flow). parse_contract_deploy({ abi: Optional[Abi]; transaction_on_network: TransactionOnNetwork @@ -91,6 +91,48 @@ class MainFacade: }]; }; + create_transaction_for_contract_upgrade({ + abi: Optional[Abi]; + sender: Account; + nonce: Optional[int]; + contract: IAddress; + bytecode: bytes OR bytecodePath: Path; + arguments: List[object] = []; + native_transfer_amount: Amount = 0; + isUpgradeable: bool = True; + isReadable: bool = True; + isPayable: bool = False; + isPayableBySC: bool = True; + gasLimit: uint32; + }): Transaction; + + // This method is less important (supports an exotic flow). + parse_contract_upgrade({ + abi: Optional[Abi]; + transaction_on_network: TransactionOnNetwork + }): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + codeHash: bytes; + }]; + }; + + // Specialized function to await and parse a contract upgrade transaction. + // Does "await_completed_transaction" and "parse_contract_upgrade" in one go. + await_completed_contract_upgrade({ + abi: Optional[Abi]; + transaction_hash: string; + }): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + codeHash: bytes; + }]; + }; + create_transaction_for_contract_execute({ abi: Optional[Abi]; sender: Account; @@ -104,6 +146,7 @@ class MainFacade: gasLimit: uint32; }): Transaction; + // This method is less important (supports an exotic flow). parse_contract_execute({ abi: Optional[Abi]; transaction_on_network: TransactionOnNetwork, From 982cd33a5e76a8ab735d6cfdc6054dfc3bad50a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 11:12:38 +0300 Subject: [PATCH 03/15] Additional notes. --- facades/main_facade.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/facades/main_facade.md b/facades/main_facade.md index f3ca045..741df60 100644 --- a/facades/main_facade.md +++ b/facades/main_facade.md @@ -15,9 +15,16 @@ class Account: ## MainFacade ``` -// All functions that create transactions receive a sender Account. -// They also receive a nonce, which is optional. If not provided, the nonce is fetched from the network. -// Furthermore, all functions that create transactions return already-signed transactions. +// (a) All functions that create transactions receive as first parameter the sender Account. +// +// (b) All functions that create transactions receive a nonce, which is optional. +// If not provided, the nonce is fetched from the network. +// +// (c) All functions that create transactions return already-signed transactions. +// +// (d) All functions that create smart-contract transactions receive an optional ABI as a parameter. +// +// (e) All functions that parse the outcome of a transaction receive an optional ABI as a parameter. class MainFacade: // The constructor is not captured by the specs; it's up to the implementing library to define it. // For example, it can be parametrized with: From 7f374e482b6637cd9c3e6da18a3711a5a0026014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 13:14:53 +0300 Subject: [PATCH 04/15] Rename, additional functions etc. --- facades/{main_facade.md => entrypoints.md} | 82 +++++++++++++++------- 1 file changed, 57 insertions(+), 25 deletions(-) rename facades/{main_facade.md => entrypoints.md} (67%) diff --git a/facades/main_facade.md b/facades/entrypoints.md similarity index 67% rename from facades/main_facade.md rename to facades/entrypoints.md index 741df60..0d11183 100644 --- a/facades/main_facade.md +++ b/facades/entrypoints.md @@ -1,5 +1,4 @@ TODO: find a name (think of space / astronomy stuff). -TODO: have separate classes for most important networks: MainnetEntrypoint, DevnetEntrypoint, TestnetEntrypoint ## Account @@ -12,20 +11,36 @@ class Account: get_nonce_then_increment(): int; ``` -## MainFacade +## Pre-defined entrypoints + +Pre-defined entrypoints inherit from `NetworkEntrypoint` and use sensible default values. ``` -// (a) All functions that create transactions receive as first parameter the sender Account. -// -// (b) All functions that create transactions receive a nonce, which is optional. -// If not provided, the nonce is fetched from the network. -// -// (c) All functions that create transactions return already-signed transactions. -// -// (d) All functions that create smart-contract transactions receive an optional ABI as a parameter. -// -// (e) All functions that parse the outcome of a transaction receive an optional ABI as a parameter. -class MainFacade: +class MainnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("api.multiversx.com"), "1"); + +class DevnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("devnet-api.multiversx.com"), "D"); + +class TestnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("testnet-api.multiversx.com"), "T"); +``` + +## NetworkEntrypoint + +The `NetworkEntrypoint` acts as a facade for interacting with the network. It should cover the most common use-cases "out of the box". + +**(a)** All functions that create transactions receive as first parameter the sender Account. + +**(b)** All functions that create transactions receive a nonce, which is optional. If not provided, the nonce is fetched from the network. + +**(c)** All functions that create transactions return already-signed transactions. + +**(d)** All functions that create smart-contract transactions receive an optional ABI as a parameter. + +**(e)** All functions that parse the outcome of a transaction receive an optional ABI as a parameter. + +**(f)** The facade should allow one to access the underlying, more lower-level components (e.g. transaction factories, network provider). + +``` +class NetworkEntrypoint: // The constructor is not captured by the specs; it's up to the implementing library to define it. // For example, it can be parametrized with: // - a network provider URL and kind (proxy, API) @@ -40,7 +55,17 @@ class MainFacade: recall_account_nonce(address: IAddress); - send_transaction(transaction: Transaction); + // Function of the network provider, promoted to the facade. + get_account(address: IAddress): AccountOnNetwork; + + // Function of the network provider, promoted to the facade. + get_transaction(transaction_hash: string): TransactionOnNetwork; + + // Function of the network provider, promoted to the facade. + send_transaction(transaction: Transaction): string; + + // Function of the network provider, promoted to the facade. + send_transactions(transaction: Transaction); Tuple[int, List[string]]; // Generic function to await a transaction on the network. // This, in contrast to the await* functions specialized for contract deployment and execution, does not parse the transaction. @@ -185,10 +210,6 @@ class MainFacade: block_nonce?: int; }): List[any]; - // Below are functions of the network providers, promoted to the facade. - // get_account() - // TBD - // Below are the most useful functions of the transactions factories, promoted to the facade. create_relayed_transaction({ @@ -201,6 +222,17 @@ class MainFacade: TBD or more specific functions, as in the factory. }); + // Access to the underlying components: + get_network_provider(): INetworkProvider; + get_transaction_computer(): TransactionComputer; + get_token_computer(): TokenComputer; + get_address_computer(): AddressComputer; + + get_account_transactions_factory(): AccountTransactionsFactory; + get_delegation_transactions_factory(): DelegationTransactionsFactory; + get_token_management_transactions_factory(): TokenManagementTransactionsFactory; + get_transfer_transactions_factory(): TransferTransactionsFactory; + create_smart_contract_transactions_factory(abi: Optional[Abi]): SmartContractTransactionsFactory; ``` ## Examples @@ -208,11 +240,11 @@ class MainFacade: ### Deploying a contract ``` -facade = MainFacade(...); +entrypoint = MainnetEntrypoint(...); sender = Account(...); abi = Abi(...); -transaction = facade.create_transaction_for_deploy({ +transaction = entrypoint.create_transaction_for_deploy({ abi: abi, sender: sender, nonce: sender.get_nonce_then_increment(), @@ -221,17 +253,17 @@ transaction = facade.create_transaction_for_deploy({ gasLimit: 1000000 }); -transaction_hash = facade.send_transaction(transaction); -parsed_outcome = facade.await_completed_contract_deploy(abi, transaction_hash); +transaction_hash = entrypoint.send_transaction(transaction); +parsed_outcome = entrypoint.await_completed_contract_deploy(abi, transaction_hash); ``` ### Accessing particular transaction factories ``` -facade = MainFacade(...); +entrypoint = MainnetEntrypoint(...); sender = Account(...); -facade.get_token_management_transactions_factory() +entrypoint.get_token_management_transactions_factory() OR -facade.transaction_factories.token_management +entrypoint.transaction_factories.token_management ``` From e2a4a5b9424586a3d166bbafe3f8041540dbfe30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 13:15:54 +0300 Subject: [PATCH 05/15] Remove todo. --- facades/entrypoints.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index 0d11183..7feabf2 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -1,5 +1,3 @@ -TODO: find a name (think of space / astronomy stuff). - ## Account ``` From aa706cea0ecf27d6ede627ae191d0e0b6e897ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 13:58:56 +0300 Subject: [PATCH 06/15] Loading accounts etc. --- facades/entrypoints.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index 7feabf2..e11bbbe 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -3,9 +3,14 @@ ``` class Account: address: IAddress; + + // Local copy of the account nonce. + // Must be explicitly managed by client code. nonce: int; sign(data: bytes): bytes; + + // Gets the nonce (the one from the object's state) and increments it. get_nonce_then_increment(): int; ``` @@ -44,9 +49,18 @@ class NetworkEntrypoint: // - a network provider URL and kind (proxy, API) // - a chain ID - // TBD - load_account(path: Path, password: Optional[string], address_index: Optional[int]): Account; + // Loads a secret key from a PEM file. PEM files may contain multiple accounts - thus, an (optional) "index" is used to select the desired secret key. + // Returns an Account object, initialized with the secret key. + load_account_from_pem(path: Path, index: Optional[int]): Account; + // Loads a secret key from an encrypted keystore file. Handles both keystores that hold a mnemonic and ones that hold a secret key (legacy). + // For keystores that hold an encrypted mnemonic, the optional "address_index" parameter is used to derive the desired secret key. + // Returns an Account object, initialized with the secret key. + load_account_from_keystore(path: Path, password: str, address_index: Optional[int]): Account; + + // Loads (derives) a secret key from a mnemonic. The optional "address_index" parameter is used to guide the derivation. + // Returns an Account object, initialized with the secret key. + load_account_from_mnemonic(mnemonic: str, address_index: Optional[int]): Account; verify_transaction_signature(transaction: Transaction): bool; verify_message_signature(message: Message): bool; @@ -238,9 +252,11 @@ class NetworkEntrypoint: ### Deploying a contract ``` -entrypoint = MainnetEntrypoint(...); -sender = Account(...); -abi = Abi(...); +entrypoint = MainnetEntrypoint(); +sender = entrypoint.load_account_from_pem("alice.pem"); +abi = Abi.load("adder.abi.json"); + +sender.nonce = entrypoint.recall_account_nonce(sender.address); transaction = entrypoint.create_transaction_for_deploy({ abi: abi, @@ -258,10 +274,8 @@ parsed_outcome = entrypoint.await_completed_contract_deploy(abi, transaction_has ### Accessing particular transaction factories ``` -entrypoint = MainnetEntrypoint(...); -sender = Account(...); +entrypoint = MainnetEntrypoint(); -entrypoint.get_token_management_transactions_factory() -OR -entrypoint.transaction_factories.token_management +factory = entrypoint.get_delegation_transactions_factory() +factory = entrypoint.get_token_management_transactions_factory() ``` From 45fb9f21adc75b50f0bc064e34c711a9fdeaba26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 6 Jun 2024 14:22:01 +0300 Subject: [PATCH 07/15] Examples, renaming, additional notes etc. --- facades/entrypoints.md | 68 +++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index e11bbbe..d186087 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -6,12 +6,12 @@ class Account: // Local copy of the account nonce. // Must be explicitly managed by client code. - nonce: int; + nonce: uint64; sign(data: bytes): bytes; // Gets the nonce (the one from the object's state) and increments it. - get_nonce_then_increment(): int; + get_nonce_then_increment(): uint64; ``` ## Pre-defined entrypoints @@ -42,6 +42,10 @@ The `NetworkEntrypoint` acts as a facade for interacting with the network. It sh **(f)** The facade should allow one to access the underlying, more lower-level components (e.g. transaction factories, network provider). +**(g)** The facade should promote the most commonly-used functions of the network provider. + +**(h)** The facade should promote the most commonly-used functions of the transactions factories. + ``` class NetworkEntrypoint: // The constructor is not captured by the specs; it's up to the implementing library to define it. @@ -65,7 +69,11 @@ class NetworkEntrypoint: verify_transaction_signature(transaction: Transaction): bool; verify_message_signature(message: Message): bool; - recall_account_nonce(address: IAddress); + // Fetches the account nonce from the network. + recall_account_nonce(address: IAddress): uint64; + + // Utility function: does account.nonce = recall_account_nonce(account.address). + refresh_account_nonce(account: Account); // Function of the network provider, promoted to the facade. get_account(address: IAddress): AccountOnNetwork; @@ -99,11 +107,11 @@ class NetworkEntrypoint: bytecode: bytes OR bytecodePath: Path; arguments: List[object] = []; native_transfer_amount: Amount = 0; - isUpgradeable: bool = True; - isReadable: bool = True; - isPayable: bool = False; - isPayableBySC: bool = True; - gasLimit: uint32; + is_upgradeable: bool = True; + is_readable: bool = True; + is_payable: bool = False; + is_payable_by_contract_: bool = True; + gas_limit: uint32; }): Transaction; // This method is less important (supports an exotic flow). @@ -143,11 +151,11 @@ class NetworkEntrypoint: bytecode: bytes OR bytecodePath: Path; arguments: List[object] = []; native_transfer_amount: Amount = 0; - isUpgradeable: bool = True; - isReadable: bool = True; - isPayable: bool = False; - isPayableBySC: bool = True; - gasLimit: uint32; + is_upgradeable: bool = True; + is_readable: bool = True; + is_payable: bool = False; + is_payable_by_contract_: bool = True; + gas_limit: uint32; }): Transaction; // This method is less important (supports an exotic flow). @@ -187,7 +195,7 @@ class NetworkEntrypoint: arguments: List[object] = []; native_transfer_amount: Amount = 0; token_transfers: List[TokenTransfer] = []; - gasLimit: uint32; + gas_limit: uint32; }): Transaction; // This method is less important (supports an exotic flow). @@ -222,8 +230,8 @@ class NetworkEntrypoint: block_nonce?: int; }): List[any]; - // Below are the most useful functions of the transactions factories, promoted to the facade. - + // Function of the relayed transactions factory, promoted to the facade. + // Handles relayed V3 transactions. create_relayed_transaction({ relayer: Account; nonce: Optional[int]; @@ -249,14 +257,34 @@ class NetworkEntrypoint: ## Examples -### Deploying a contract +### Transfer native tokens + +``` +entrypoint = MainnetEntrypoint(); +sender = entrypoint.load_account_from_pem("alice.pem"); +receiver = Address.from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + +entrypoint.refresh_account_nonce(sender); + +transaction = entrypoint.create_transaction_for_transfer({ + sender: sender, + nonce: sender.get_nonce_then_increment(), + receiver: receiver, + native_amount: 1000000000000000000, +}); + +transaction_hash = entrypoint.send_transaction(transaction); +transaction_on_network = entrypoint.await_completed_transaction(transaction_hash); +``` + +### Deploy a contract ``` entrypoint = MainnetEntrypoint(); sender = entrypoint.load_account_from_pem("alice.pem"); abi = Abi.load("adder.abi.json"); -sender.nonce = entrypoint.recall_account_nonce(sender.address); +entrypoint.refresh_account_nonce(sender); transaction = entrypoint.create_transaction_for_deploy({ abi: abi, @@ -264,14 +292,14 @@ transaction = entrypoint.create_transaction_for_deploy({ nonce: sender.get_nonce_then_increment(), bytecode: bytecode, arguments: [arg1, arg2], - gasLimit: 1000000 + gas_limit: 10000000 }); transaction_hash = entrypoint.send_transaction(transaction); parsed_outcome = entrypoint.await_completed_contract_deploy(abi, transaction_hash); ``` -### Accessing particular transaction factories +### Access particular transaction factories ``` entrypoint = MainnetEntrypoint(); From 662722b7e725569f0e86350725e1bd1635a00479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 7 Jun 2024 12:13:17 +0300 Subject: [PATCH 08/15] Work in progress, pair design with Alex. --- facades/entrypoints.md | 75 ++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index d186087..d83392c 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -1,7 +1,22 @@ +Questions: + +(1) have load_account as a single method, or separate, explicit functions (3 methods)? + +(2) please confirm that the suggested constructor is all right. Should we or should we not apply heuristics to infer the kind of network provider based on its URL? + +(3) what to do when the developer wants to use a non-promoted function of a transactions factory? + +(4) how about having a higher-level factory for each transactions factory? Pre-construction logic, post-construction logic. + ## Account ``` class Account: + // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Suggestions: + constructor(secret_key: bytes, hrp: Optional[str]); + constructor(user_signer: IUserSigner, hrp: Optional[str]); + address: IAddress; // Local copy of the account nonce. @@ -12,6 +27,22 @@ class Account: // Gets the nonce (the one from the object's state) and increments it. get_nonce_then_increment(): uint64; + + // Named constructor + // Loads a secret key from a PEM file. PEM files may contain multiple accounts - thus, an (optional) "index" is used to select the desired secret key. + // Returns an Account object, initialized with the secret key. + static new_from_pem(path: Path, index: Optional[int], hrp: Optional[str]): Account; + + // Named constructor + // Loads a secret key from an encrypted keystore file. Handles both keystores that hold a mnemonic and ones that hold a secret key (legacy). + // For keystores that hold an encrypted mnemonic, the optional "address_index" parameter is used to derive the desired secret key. + // Returns an Account object, initialized with the secret key. + static new_from_keystore(path: Path, password: str, address_index: Optional[int], hrp: Optional[str]): Account; + + // Named constructor + // Loads (derives) a secret key from a mnemonic. The optional "address_index" parameter is used to guide the derivation. + // Returns an Account object, initialized with the secret key. + static new_from_mnemonic(mnemonic: str, address_index: Optional[int], hrp: Optional[str]): Account; ``` ## Pre-defined entrypoints @@ -19,26 +50,26 @@ class Account: Pre-defined entrypoints inherit from `NetworkEntrypoint` and use sensible default values. ``` -class MainnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("api.multiversx.com"), "1"); +class MainnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://api.multiversx.com")); -class DevnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("devnet-api.multiversx.com"), "D"); +class DevnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://devnet-api.multiversx.com")); -class TestnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("testnet-api.multiversx.com"), "T"); +class TestnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://testnet-api.multiversx.com")); ``` ## NetworkEntrypoint The `NetworkEntrypoint` acts as a facade for interacting with the network. It should cover the most common use-cases "out of the box". -**(a)** All functions that create transactions receive as first parameter the sender Account. +**(a)** All functions that create transactions receive as first parameter the `sender: Account`. And an optional `guardian: Account`. **(b)** All functions that create transactions receive a nonce, which is optional. If not provided, the nonce is fetched from the network. **(c)** All functions that create transactions return already-signed transactions. -**(d)** All functions that create smart-contract transactions receive an optional ABI as a parameter. +**(d)** All functions that create smart-contract transactions receive an optional `Abi` as a parameter. -**(e)** All functions that parse the outcome of a transaction receive an optional ABI as a parameter. +**(e)** All functions that parse the outcome of a transaction receive an optional `Abi` as a parameter. **(f)** The facade should allow one to access the underlying, more lower-level components (e.g. transaction factories, network provider). @@ -46,25 +77,13 @@ The `NetworkEntrypoint` acts as a facade for interacting with the network. It sh **(h)** The facade should promote the most commonly-used functions of the transactions factories. +**(i)** The facade should fetch network parameters (network config) needed for construction transactions (e.g. chain ID) in a lazy manner. For example, it may fetch them when the first transaction is created. This works fine for JavaScript, as well, since transaction-creation functions are async anyway. + ``` class NetworkEntrypoint: // The constructor is not captured by the specs; it's up to the implementing library to define it. - // For example, it can be parametrized with: - // - a network provider URL and kind (proxy, API) - // - a chain ID - - // Loads a secret key from a PEM file. PEM files may contain multiple accounts - thus, an (optional) "index" is used to select the desired secret key. - // Returns an Account object, initialized with the secret key. - load_account_from_pem(path: Path, index: Optional[int]): Account; - - // Loads a secret key from an encrypted keystore file. Handles both keystores that hold a mnemonic and ones that hold a secret key (legacy). - // For keystores that hold an encrypted mnemonic, the optional "address_index" parameter is used to derive the desired secret key. - // Returns an Account object, initialized with the secret key. - load_account_from_keystore(path: Path, password: str, address_index: Optional[int]): Account; - - // Loads (derives) a secret key from a mnemonic. The optional "address_index" parameter is used to guide the derivation. - // Returns an Account object, initialized with the secret key. - load_account_from_mnemonic(mnemonic: str, address_index: Optional[int]): Account; + // For example, it can be parametrized with: a network provider URL and kind (proxy, API) + constructor(url: str, kind: Optional[str]); verify_transaction_signature(transaction: Transaction): bool; verify_message_signature(message: Message): bool; @@ -72,9 +91,6 @@ class NetworkEntrypoint: // Fetches the account nonce from the network. recall_account_nonce(address: IAddress): uint64; - // Utility function: does account.nonce = recall_account_nonce(account.address). - refresh_account_nonce(account: Account); - // Function of the network provider, promoted to the facade. get_account(address: IAddress): AccountOnNetwork; @@ -227,7 +243,6 @@ class NetworkEntrypoint: value?: Amount; function: string; arguments: List[object]; - block_nonce?: int; }): List[any]; // Function of the relayed transactions factory, promoted to the facade. @@ -238,9 +253,11 @@ class NetworkEntrypoint: inner_transactions: List[ITransaction]; }): Transaction; + // create_transaction_for_issuing_token({ - TBD or more specific functions, as in the factory. + // See questions (3) and (4) }); + // // Access to the underlying components: get_network_provider(): INetworkProvider; @@ -264,7 +281,7 @@ entrypoint = MainnetEntrypoint(); sender = entrypoint.load_account_from_pem("alice.pem"); receiver = Address.from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); -entrypoint.refresh_account_nonce(sender); +account.nonce = entrypoint.recall_account_nonce(account.address); transaction = entrypoint.create_transaction_for_transfer({ sender: sender, @@ -284,7 +301,7 @@ entrypoint = MainnetEntrypoint(); sender = entrypoint.load_account_from_pem("alice.pem"); abi = Abi.load("adder.abi.json"); -entrypoint.refresh_account_nonce(sender); +account.nonce = entrypoint.recall_account_nonce(account.address); transaction = entrypoint.create_transaction_for_deploy({ abi: abi, From a272767d1ae43fcc5f65e262fcc624be9c65e94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 10 Jun 2024 15:20:08 +0300 Subject: [PATCH 09/15] Facades, work in progress. Controllers. --- README.md | 12 + .../account_management_controller.md | 8 + core/controllers/delegation_controller.md | 28 ++ core/controllers/relayed_controller.md | 8 + core/controllers/smart_contract_controller.md | 117 +++++++ .../token_management_controller.md | 8 + core/controllers/transfers_controller.md | 10 + .../relayed_transactions_factory.md | 4 +- .../transfer_transactions_factory.md | 11 +- facades/entrypoints.md | 302 ++++++------------ 10 files changed, 307 insertions(+), 201 deletions(-) create mode 100644 core/controllers/account_management_controller.md create mode 100644 core/controllers/delegation_controller.md create mode 100644 core/controllers/relayed_controller.md create mode 100644 core/controllers/smart_contract_controller.md create mode 100644 core/controllers/token_management_controller.md create mode 100644 core/controllers/transfers_controller.md diff --git a/README.md b/README.md index 92a2cb4..0dbbc93 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,18 @@ The methods are named in correspondence with the use cases they implement, e.g. Optionally, the implementing library can choose to return an object that isn't a complete representation of the `Transaction`, if desired. In this case, the library must name the incomplete representation `DraftTransaction`, and also must provide a direct conversion facility from `DraftTransaction` to `Transaction` - for example, a named constructor. See [transaction](core/transaction.md). +### Transactions Controllers + +The transaction controllers are components built upon the lower-level transaction factories and transaction outcome parsers. They are able to create signed transactions and parse the outcome of these transactions. The controllers are specialized for a "family" of transactions (e.g. transfer transactions, delegation transactions, smart contract transactions), just like the factories and the outcome parsers. + +One controller is backed by **one transaction factory and one outcome parser** (paired). + +- All functions that create transactions receive as first parameter the `sender: IAccount`. They also receive an optional `guardian: IAccount`. +- All functions that create transactions receive a nonce, which is optional. If not provided, the nonce is fetched from the network. +- All functions that create transactions return already-signed transactions. +- All functions that parse transactions outcomes receive a `TransactionOnNetwork`. +- All functions that parse transactions outcomes are paired with an additional function that awaits the completion of the transaction on the network (before parsing the outcome). For example, `parse_deploy` is paired with `await_completed_deploy`. + ## Guidelines ### **`in-ifaces-out-concrete-types`** diff --git a/core/controllers/account_management_controller.md b/core/controllers/account_management_controller.md new file mode 100644 index 0000000..569d9d1 --- /dev/null +++ b/core/controllers/account_management_controller.md @@ -0,0 +1,8 @@ +## AccountManagementController + +Promotes all the functionality of `AccountManagementTransactionsFactory` and `AccountManagementTransactionsOutcomeParser`. + +``` +class AccountManagementController: + ... +``` diff --git a/core/controllers/delegation_controller.md b/core/controllers/delegation_controller.md new file mode 100644 index 0000000..83db76b --- /dev/null +++ b/core/controllers/delegation_controller.md @@ -0,0 +1,28 @@ +## DelegationController + +Promotes all the functionality of `DelegationTransactionsFactory` and `DelegationTransactionsOutcomeParser`. + +``` +class DelegationController: + // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Suggestions: + constructor(network_provider: INetworkProvider); + + create_transaction_for_new_delegation_contract({ + sender: IAccount; + nonce: int; + totalDelegationCap: Amount; + service_fee: number; + amount: Amount; + }): Transaction; + + parse_create_new_delegation_contract(transaction_on_network: TransactionOnNetwork): { + contract_address: string; + }[]; + + await_completed_create_new_delegation_contract(transaction_on_network: TransactionOnNetwork): { + contract_address: string; + }[]; + + // ... +``` diff --git a/core/controllers/relayed_controller.md b/core/controllers/relayed_controller.md new file mode 100644 index 0000000..9cf3627 --- /dev/null +++ b/core/controllers/relayed_controller.md @@ -0,0 +1,8 @@ +## RelayedController + +Promotes all the functionality of `RelayedTransactionsFactory` and `RelayedTransactionsOutcomeParser`. + +``` +class RelayedController: + ... +``` diff --git a/core/controllers/smart_contract_controller.md b/core/controllers/smart_contract_controller.md new file mode 100644 index 0000000..d80ca57 --- /dev/null +++ b/core/controllers/smart_contract_controller.md @@ -0,0 +1,117 @@ +## SmartContractController + +Promotes all the functionality of `SmartContractTransactionsFactory`, `SmartContractTransactionsOutcomeParser` and `SmartContractQueriesController`. + +``` +class SmartContractController: + // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Suggestions: + constructor(network_provider: INetworkProvider, abi: Optional[Abi]); + + create_transaction_for_deploy({ + sender: IAccount; + nonce: Optional[int]; + bytecode: bytes OR bytecodePath: Path; + arguments: List[object] = []; + native_transfer_amount: Amount = 0; + is_upgradeable: bool = True; + is_readable: bool = True; + is_payable: bool = False; + is_payable_by_contract: bool = True; + gas_limit: uint32; + }): Transaction; + + parse_deploy(transaction_on_network: TransactionOnNetwork): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + ownerAddress: string; + codeHash: bytes; + }]; + }; + + // Does "await_completed_transaction" and "parse_deploy" in one go. + await_completed_deploy(transaction_hash: string): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + ownerAddress: string; + codeHash: bytes; + }]; + }; + + create_transaction_for_upgrade({ + sender: IAccount; + nonce: Optional[int]; + contract: IAddress; + bytecode: bytes OR bytecodePath: Path; + arguments: List[object] = []; + native_transfer_amount: Amount = 0; + is_upgradeable: bool = True; + is_readable: bool = True; + is_payable: bool = False; + is_payable_by_contract: bool = True; + gas_limit: uint32; + }): Transaction; + + // This method is less important (supports an exotic flow). + parse_upgrade(transaction_on_network: TransactionOnNetwork): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + codeHash: bytes; + }]; + }; + + // Specialized function to await and parse a contract upgrade transaction. + // Does "await_completed_transaction" and "parse_upgrade" in one go. + await_completed_upgrade(transaction_hash: string): { + return_code: string; + return_message: string; + contracts: List[{ + address: string; + codeHash: bytes; + }]; + }; + + create_transaction_for_execute({ + sender: IAccount; + nonce: Optional[int]; + contract: IAddress; + // If "function" is a reserved word in the implementing language, it should be replaced with a different name (e.g. "func" or "functionName"). + function: string; + arguments: List[object] = []; + native_transfer_amount: Amount = 0; + token_transfers: List[TokenTransfer] = []; + gas_limit: uint32; + }): Transaction; + + parse_execute({ + transaction_on_network: TransactionOnNetwork, + function?: string + }): { + values: List[any]; + return_code: string; + return_message: string; + }; + + // Does "await_completed_transaction" and "parse_execute" in one go. + await_completed_execute({ + transaction_hash: string; + }): { + values: List[any]; + return_code: string; + return_message: string; + }; + + query_contract({ + contract: IAddress; + caller?: IAddress; + value?: Amount; + function: string; + arguments: List[object]; + }): List[any]; +``` diff --git a/core/controllers/token_management_controller.md b/core/controllers/token_management_controller.md new file mode 100644 index 0000000..14bcc97 --- /dev/null +++ b/core/controllers/token_management_controller.md @@ -0,0 +1,8 @@ +## TokenManagementController + +Promotes all the functionality of `TokenManagementTransactionsFactory` and `TokenManagementTransactionsOutcomeParser`. + +``` +class TokenManagementController: + ... +``` diff --git a/core/controllers/transfers_controller.md b/core/controllers/transfers_controller.md new file mode 100644 index 0000000..19d08b6 --- /dev/null +++ b/core/controllers/transfers_controller.md @@ -0,0 +1,10 @@ +## TransfersController + +Promotes all the functionality of `TransferTransactionsFactory` and `TransferTransactionsOutcomeParser`. + +``` +class TransfersController: + // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Suggestions: + constructor(network_provider: INetworkProvider); +``` diff --git a/core/transactions-factories/relayed_transactions_factory.md b/core/transactions-factories/relayed_transactions_factory.md index a22a2ce..f676bdf 100644 --- a/core/transactions-factories/relayed_transactions_factory.md +++ b/core/transactions-factories/relayed_transactions_factory.md @@ -1,5 +1,6 @@ ## RelayedTransactionsFactory +``` class RelayedTransactionsFactory: // The constructor is not captured by the specs; it's up to the implementing library to define it. // Generally speaking, the constructor should be parametrized with a configuration object which defines entries such as: @@ -26,5 +27,6 @@ class RelayedTransactionsFactory: // does not set gasLimit create_relayed_v3_transaction({ relayer_addresss: IAddress; - innerTransactions: List[ITransaction]; + inner_transactions: List[ITransaction]; }): Transaction; +``` diff --git a/core/transactions-factories/transfer_transactions_factory.md b/core/transactions-factories/transfer_transactions_factory.md index f59db97..2c6c3ca 100644 --- a/core/transactions-factories/transfer_transactions_factory.md +++ b/core/transactions-factories/transfer_transactions_factory.md @@ -7,7 +7,16 @@ class TransferTransactionsFactory: // The constructor is not captured by the specs; it's up to the implementing library to define it. // Generally speaking, the constructor should be parametrized with a configuration object which defines entries such as: // "minGasLimit", "gasLimitPerByte", "issueCost", gas limit for specific operations etc. (e.g. "gasLimitForESDTTransfer"). - + + // If multiple transfers are specified, or if a native amount and a token transfer are specified, then a multi-ESDT transfer transaction should be created. + create_transaction_for_transfer({ + sender: IAddress; + receiver: IAddress; + native_amount: Optional[Amount]; + data: Optional[bytes]; + token_transfers: TokenTransfer[]; + }): Transaction; + create_transaction_for_native_token_transfer({ sender: IAddress; receiver: IAddress; diff --git a/facades/entrypoints.md b/facades/entrypoints.md index d83392c..c0740fc 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -4,9 +4,7 @@ Questions: (2) please confirm that the suggested constructor is all right. Should we or should we not apply heuristics to infer the kind of network provider based on its URL? -(3) what to do when the developer wants to use a non-promoted function of a transactions factory? - -(4) how about having a higher-level factory for each transactions factory? Pre-construction logic, post-construction logic. +(3) even if creation (and signing) of transactions is done by controllers, broadcasting isn't (see examples). All good? ## Account @@ -61,23 +59,11 @@ class TestnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://te The `NetworkEntrypoint` acts as a facade for interacting with the network. It should cover the most common use-cases "out of the box". -**(a)** All functions that create transactions receive as first parameter the `sender: Account`. And an optional `guardian: Account`. - -**(b)** All functions that create transactions receive a nonce, which is optional. If not provided, the nonce is fetched from the network. - -**(c)** All functions that create transactions return already-signed transactions. - -**(d)** All functions that create smart-contract transactions receive an optional `Abi` as a parameter. +**(a)** The facade should allow one to access the underlying, more lower-level components (e.g. transaction controllers, network provider). -**(e)** All functions that parse the outcome of a transaction receive an optional `Abi` as a parameter. +**(b)** The facade should promote the most commonly-used functions of the network provider. -**(f)** The facade should allow one to access the underlying, more lower-level components (e.g. transaction factories, network provider). - -**(g)** The facade should promote the most commonly-used functions of the network provider. - -**(h)** The facade should promote the most commonly-used functions of the transactions factories. - -**(i)** The facade should fetch network parameters (network config) needed for construction transactions (e.g. chain ID) in a lazy manner. For example, it may fetch them when the first transaction is created. This works fine for JavaScript, as well, since transaction-creation functions are async anyway. +**(c)** The facade should fetch network parameters (network config) needed for construction transactions (e.g. chain ID) in a lazy manner. For example, it may fetch them when the first transaction is created. This works fine for JavaScript, as well, since transaction-creation functions are async anyway. ``` class NetworkEntrypoint: @@ -91,12 +77,6 @@ class NetworkEntrypoint: // Fetches the account nonce from the network. recall_account_nonce(address: IAddress): uint64; - // Function of the network provider, promoted to the facade. - get_account(address: IAddress): AccountOnNetwork; - - // Function of the network provider, promoted to the facade. - get_transaction(transaction_hash: string): TransactionOnNetwork; - // Function of the network provider, promoted to the facade. send_transaction(transaction: Transaction): string; @@ -104,193 +84,47 @@ class NetworkEntrypoint: send_transactions(transaction: Transaction); Tuple[int, List[string]]; // Generic function to await a transaction on the network. - // This, in contrast to the await* functions specialized for contract deployment and execution, does not parse the transaction. await_completed_transaction(transaction_hash: string): TransactionOnNetwork; - create_transaction_for_transfer({ - sender: Account; - nonce: Optional[int]; - receiver: IAddress; - native_amount: Optional[Amount]; - data: Optional[bytes]; - token_transfers: TokenTransfer[]; - }): Transaction; - - create_transaction_for_contract_deploy({ - abi: Optional[Abi]; - sender: Account; - nonce: Optional[int]; - bytecode: bytes OR bytecodePath: Path; - arguments: List[object] = []; - native_transfer_amount: Amount = 0; - is_upgradeable: bool = True; - is_readable: bool = True; - is_payable: bool = False; - is_payable_by_contract_: bool = True; - gas_limit: uint32; - }): Transaction; - - // This method is less important (supports an exotic flow). - parse_contract_deploy({ - abi: Optional[Abi]; - transaction_on_network: TransactionOnNetwork - }): { - return_code: string; - return_message: string; - contracts: List[{ - address: string; - ownerAddress: string; - codeHash: bytes; - }]; - }; - - // Specialized function to await and parse a contract deployment transaction. - // Does "await_completed_transaction" and "parse_contract_deploy" in one go. - await_completed_contract_deploy({ - abi: Optional[Abi]; - transaction_hash: string; - }): { - return_code: string; - return_message: string; - contracts: List[{ - address: string; - ownerAddress: string; - codeHash: bytes; - }]; - }; - - create_transaction_for_contract_upgrade({ - abi: Optional[Abi]; - sender: Account; - nonce: Optional[int]; - contract: IAddress; - bytecode: bytes OR bytecodePath: Path; - arguments: List[object] = []; - native_transfer_amount: Amount = 0; - is_upgradeable: bool = True; - is_readable: bool = True; - is_payable: bool = False; - is_payable_by_contract_: bool = True; - gas_limit: uint32; - }): Transaction; - - // This method is less important (supports an exotic flow). - parse_contract_upgrade({ - abi: Optional[Abi]; - transaction_on_network: TransactionOnNetwork - }): { - return_code: string; - return_message: string; - contracts: List[{ - address: string; - codeHash: bytes; - }]; - }; - - // Specialized function to await and parse a contract upgrade transaction. - // Does "await_completed_transaction" and "parse_contract_upgrade" in one go. - await_completed_contract_upgrade({ - abi: Optional[Abi]; - transaction_hash: string; - }): { - return_code: string; - return_message: string; - contracts: List[{ - address: string; - codeHash: bytes; - }]; - }; - - create_transaction_for_contract_execute({ - abi: Optional[Abi]; - sender: Account; - nonce: Optional[int]; - contract: IAddress; - // If "function" is a reserved word in the implementing language, it should be replaced with a different name (e.g. "func" or "functionName"). - function: string; - arguments: List[object] = []; - native_transfer_amount: Amount = 0; - token_transfers: List[TokenTransfer] = []; - gas_limit: uint32; - }): Transaction; - - // This method is less important (supports an exotic flow). - parse_contract_execute({ - abi: Optional[Abi]; - transaction_on_network: TransactionOnNetwork, - function?: string - }): { - values: List[any]; - return_code: string; - return_message: string; - }; - - // Specialized function to await and parse a contract execution transaction. - // Does "await_completed_transaction" and "parse_contract_execute" in one go. - await_completed_contract_execute({ - abi: Optional[Abi]; - transaction_hash: string; - }): { - values: List[any]; - return_code: string; - return_message: string; - }; - - query_contract({ - abi: Optional[Abi]; - contract: IAddress; - caller?: IAddress; - value?: Amount; - function: string; - arguments: List[object]; - }): List[any]; - - // Function of the relayed transactions factory, promoted to the facade. - // Handles relayed V3 transactions. - create_relayed_transaction({ - relayer: Account; - nonce: Optional[int]; - inner_transactions: List[ITransaction]; - }): Transaction; - - // - create_transaction_for_issuing_token({ - // See questions (3) and (4) - }); - // - - // Access to the underlying components: + // Access to the underlying network provider. get_network_provider(): INetworkProvider; - get_transaction_computer(): TransactionComputer; - get_token_computer(): TokenComputer; - get_address_computer(): AddressComputer; - - get_account_transactions_factory(): AccountTransactionsFactory; - get_delegation_transactions_factory(): DelegationTransactionsFactory; - get_token_management_transactions_factory(): TokenManagementTransactionsFactory; - get_transfer_transactions_factory(): TransferTransactionsFactory; - create_smart_contract_transactions_factory(abi: Optional[Abi]): SmartContractTransactionsFactory; + + // Access to the individual controllers. + create_smart_contract_controller(abi: Optional[Abi]): SmartContractController; + get_transfers_controller(): TransfersController; + get_token_management_controller(): TokenManagementController; + get_delegation_controller(): DelegationController; + get_relayed_controller(): RelayedController; + get_account_management__controller(): AccountManagementController; ``` ## Examples -### Transfer native tokens +### Transfer native amounts and / or custom tokens ``` entrypoint = MainnetEntrypoint(); -sender = entrypoint.load_account_from_pem("alice.pem"); -receiver = Address.from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); +controller = entrypoint.get_transfer_transactions_controller() -account.nonce = entrypoint.recall_account_nonce(account.address); +sender = entrypoint.load_account_from_pem("alice.pem"); +sender.nonce = entrypoint.recall_account_nonce(sender.address); -transaction = entrypoint.create_transaction_for_transfer({ +transaction = controller.create_transaction_for_transfer({ sender: sender, nonce: sender.get_nonce_then_increment(), - receiver: receiver, + receiver: Address.from_bech32("erd1..."), native_amount: 1000000000000000000, + token_transfers: []; }); +// Broadcast transaction (option 1): transaction_hash = entrypoint.send_transaction(transaction); +// Broadcast transaction (option 2): +transaction_hash = entrypoint.get_network_provider().send_transaction(transaction); + +// Await transaction completion (option 1, await and specialized outcome parsing): +parsed_outcome = controller.await_completed_transfer(transaction_hash); +// Await transaction completion (option 2, simple await): transaction_on_network = entrypoint.await_completed_transaction(transaction_hash); ``` @@ -298,13 +132,13 @@ transaction_on_network = entrypoint.await_completed_transaction(transaction_hash ``` entrypoint = MainnetEntrypoint(); -sender = entrypoint.load_account_from_pem("alice.pem"); abi = Abi.load("adder.abi.json"); +controller = entrypoint.create_smart_contract_controller(abi); -account.nonce = entrypoint.recall_account_nonce(account.address); +sender = entrypoint.load_account_from_pem("alice.pem"); +sender.nonce = entrypoint.recall_account_nonce(sender.address); transaction = entrypoint.create_transaction_for_deploy({ - abi: abi, sender: sender, nonce: sender.get_nonce_then_increment(), bytecode: bytecode, @@ -316,11 +150,81 @@ transaction_hash = entrypoint.send_transaction(transaction); parsed_outcome = entrypoint.await_completed_contract_deploy(abi, transaction_hash); ``` -### Access particular transaction factories +### Call a contract + +``` +entrypoint = MainnetEntrypoint(); +abi = Abi.load("adder.abi.json"); +controller = entrypoint.create_smart_contract_controller(abi); + +sender = entrypoint.load_account_from_pem("alice.pem"); +sender.nonce = entrypoint.recall_account_nonce(sender.address); + +transaction = controller.create_transaction_for_execute({ + sender: sender, + nonce: sender.get_nonce_then_increment(), + contract: Address.from_bech32("erd1..."), + function: "add", + arguments: [arg1, arg2], + gas_limit: 10000000 +}); + +transaction_hash = entrypoint.send_transaction(transaction); +parsed_outcome = controller.await_completed_execute(transaction_hash); +``` + +### Query a contract + +``` +entrypoint = MainnetEntrypoint(); +abi = Abi.load("adder.abi.json"); +controller = entrypoint.create_smart_contract_controller(abi); + +parsed_outcome = controller.query({ + contract: Address.from_bech32("erd1..."), + function: "get", + arguments: [arg1, arg2], +}); +``` + +### Create a relayed contract call + +``` +entrypoint = MainnetEntrypoint(); +abi = Abi.load("adder.abi.json"); +contract_controller = entrypoint.create_smart_contract_controller(abi); +relayed_controller = entrypoint.get_relayed_controller(); + +sender = entrypoint.load_account_from_pem("alice.pem"); +relayer = entrypoint.load_account_from_pem("carol.pem"); +sender.nonce = entrypoint.recall_account_nonce(sender.address); +relayer.nonce = entrypoint.recall_account_nonce(relayer.address); + +user_transaction = controller.create_transaction_for_execute({ + sender: sender, + nonce: sender.get_nonce_then_increment(), + contract: Address.from_bech32("erd1..."), + function: "add", + arguments: [arg1, arg2], + gas_limit: 10000000 +}); + +transaction = relayed_controller.create_relayed_transaction({ + relayer: relayer, + nonce: relayer.get_nonce_then_increment(), + inner_transactions: [user_transaction] +}); + +transaction_hash = entrypoint.send_transaction(transaction); +outcome = entrypoint.await_completed_transaction(transaction_hash); +``` + +### Access underlying components ``` entrypoint = MainnetEntrypoint(); -factory = entrypoint.get_delegation_transactions_factory() -factory = entrypoint.get_token_management_transactions_factory() +controller = entrypoint.get_delegation_controller() +controller = entrypoint.get_token_management_controller() +network_provider = entrypoint.get_network_provider() ``` From e584aab11279bb7aea2e118c25ec286cec3879a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 11 Jun 2024 12:55:44 +0300 Subject: [PATCH 10/15] Split into two files. Questions will be added in the PR on GH. --- facades/account.md | 36 +++++++++++++++++++++++++++++++++ facades/entrypoints.md | 45 ------------------------------------------ 2 files changed, 36 insertions(+), 45 deletions(-) create mode 100644 facades/account.md diff --git a/facades/account.md b/facades/account.md new file mode 100644 index 0000000..40702a8 --- /dev/null +++ b/facades/account.md @@ -0,0 +1,36 @@ +## Account + +``` +class Account: + // The constructor is not captured by the specs; it's up to the implementing library to define it. + // Suggestions: + constructor(secret_key: bytes, hrp: Optional[str]); + constructor(user_signer: IUserSigner, hrp: Optional[str]); + + address: IAddress; + + // Local copy of the account nonce. + // Must be explicitly managed by client code. + nonce: uint64; + + sign(data: bytes): bytes; + + // Gets the nonce (the one from the object's state) and increments it. + get_nonce_then_increment(): uint64; + + // Named constructor + // Loads a secret key from a PEM file. PEM files may contain multiple accounts - thus, an (optional) "index" is used to select the desired secret key. + // Returns an Account object, initialized with the secret key. + static new_from_pem(path: Path, index: Optional[int], hrp: Optional[str]): Account; + + // Named constructor + // Loads a secret key from an encrypted keystore file. Handles both keystores that hold a mnemonic and ones that hold a secret key (legacy). + // For keystores that hold an encrypted mnemonic, the optional "address_index" parameter is used to derive the desired secret key. + // Returns an Account object, initialized with the secret key. + static new_from_keystore(path: Path, password: str, address_index: Optional[int], hrp: Optional[str]): Account; + + // Named constructor + // Loads (derives) a secret key from a mnemonic. The optional "address_index" parameter is used to guide the derivation. + // Returns an Account object, initialized with the secret key. + static new_from_mnemonic(mnemonic: str, address_index: Optional[int], hrp: Optional[str]): Account; +``` diff --git a/facades/entrypoints.md b/facades/entrypoints.md index c0740fc..4c685ba 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -1,48 +1,3 @@ -Questions: - -(1) have load_account as a single method, or separate, explicit functions (3 methods)? - -(2) please confirm that the suggested constructor is all right. Should we or should we not apply heuristics to infer the kind of network provider based on its URL? - -(3) even if creation (and signing) of transactions is done by controllers, broadcasting isn't (see examples). All good? - -## Account - -``` -class Account: - // The constructor is not captured by the specs; it's up to the implementing library to define it. - // Suggestions: - constructor(secret_key: bytes, hrp: Optional[str]); - constructor(user_signer: IUserSigner, hrp: Optional[str]); - - address: IAddress; - - // Local copy of the account nonce. - // Must be explicitly managed by client code. - nonce: uint64; - - sign(data: bytes): bytes; - - // Gets the nonce (the one from the object's state) and increments it. - get_nonce_then_increment(): uint64; - - // Named constructor - // Loads a secret key from a PEM file. PEM files may contain multiple accounts - thus, an (optional) "index" is used to select the desired secret key. - // Returns an Account object, initialized with the secret key. - static new_from_pem(path: Path, index: Optional[int], hrp: Optional[str]): Account; - - // Named constructor - // Loads a secret key from an encrypted keystore file. Handles both keystores that hold a mnemonic and ones that hold a secret key (legacy). - // For keystores that hold an encrypted mnemonic, the optional "address_index" parameter is used to derive the desired secret key. - // Returns an Account object, initialized with the secret key. - static new_from_keystore(path: Path, password: str, address_index: Optional[int], hrp: Optional[str]): Account; - - // Named constructor - // Loads (derives) a secret key from a mnemonic. The optional "address_index" parameter is used to guide the derivation. - // Returns an Account object, initialized with the secret key. - static new_from_mnemonic(mnemonic: str, address_index: Optional[int], hrp: Optional[str]): Account; -``` - ## Pre-defined entrypoints Pre-defined entrypoints inherit from `NetworkEntrypoint` and use sensible default values. From a20b37c628bd244dd8abec8cbb899b28d96c021c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 11 Jun 2024 15:25:55 +0300 Subject: [PATCH 11/15] Update new account. --- facades/entrypoints.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index 4c685ba..d4cf5cc 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -90,7 +90,7 @@ entrypoint = MainnetEntrypoint(); abi = Abi.load("adder.abi.json"); controller = entrypoint.create_smart_contract_controller(abi); -sender = entrypoint.load_account_from_pem("alice.pem"); +sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); transaction = entrypoint.create_transaction_for_deploy({ @@ -112,7 +112,7 @@ entrypoint = MainnetEntrypoint(); abi = Abi.load("adder.abi.json"); controller = entrypoint.create_smart_contract_controller(abi); -sender = entrypoint.load_account_from_pem("alice.pem"); +sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); transaction = controller.create_transaction_for_execute({ @@ -150,8 +150,8 @@ abi = Abi.load("adder.abi.json"); contract_controller = entrypoint.create_smart_contract_controller(abi); relayed_controller = entrypoint.get_relayed_controller(); -sender = entrypoint.load_account_from_pem("alice.pem"); -relayer = entrypoint.load_account_from_pem("carol.pem"); +sender = Account.new_from_pem("alice.pem"); +relayer = Account.new_from_pem("carol.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); relayer.nonce = entrypoint.recall_account_nonce(relayer.address); From b5b78f656871972b0dbde12f45a12be51bebd388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 11 Jun 2024 15:26:46 +0300 Subject: [PATCH 12/15] Fix new account. --- facades/entrypoints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index d4cf5cc..e379157 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -61,7 +61,7 @@ class NetworkEntrypoint: entrypoint = MainnetEntrypoint(); controller = entrypoint.get_transfer_transactions_controller() -sender = entrypoint.load_account_from_pem("alice.pem"); +sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); transaction = controller.create_transaction_for_transfer({ From 58790ab8c87755e127f19d4b422435b6bc4ae8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 12 Jun 2024 14:43:46 +0300 Subject: [PATCH 13/15] Additional example: issue a fungible token, do a quick airdrop. --- facades/entrypoints.md | 56 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index e379157..e645565 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -59,7 +59,7 @@ class NetworkEntrypoint: ``` entrypoint = MainnetEntrypoint(); -controller = entrypoint.get_transfer_transactions_controller() +controller = entrypoint.get_transfers_controller() sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); @@ -174,6 +174,60 @@ transaction_hash = entrypoint.send_transaction(transaction); outcome = entrypoint.await_completed_transaction(transaction_hash); ``` +### Issue a fungible token, do a quick airdrop + +``` +entrypoint = MainnetEntrypoint(); +tokens_controller = entrypoint.get_token_management_controller(); +transfers_controller = entrypoint.get_transfers_controller(); + +sender = Account.new_from_pem("alice.pem"); +sender.nonce = entrypoint.recall_account_nonce(sender.address); + +transaction = tokens_controller.create_transaction_for_issuing_fungible({ + sender: sender, + nonce: sender.get_nonce_then_increment(), + token_name: "EXAMPLE", + token_ticker: "EXAMPLE", + initial_supply: 1000000000000000000000, + num_decimals: 18, + can_freeze: true, + can_wipe: true, + can_pause: true, + can_transfer_nft_create_role: true, + can_change_owner: true, + can_upgrade: true, + can_add_special_roles: true +}); + +transaction_hash = entrypoint.send_transaction(transaction); +parsed_outcome = tokens_controller.await_completed_issue_fungible(transaction_hash); +token_identifier = parsed_outcome.token_identifier; + +receivers = [ + Address.from_bech32("erd1..."), + Address.from_bech32("erd1..."), + Address.from_bech32("erd1...") +]; + +transactions = []; + +for receiver in receivers: + transaction = transfers_controller.create_transaction_for_transfer({ + sender: sender, + nonce: sender.get_nonce_then_increment(), + receiver: receiver, + token_transfers: [ + token: Token(token_identifier), + amount: 1000000000000000000 + ] + }); + + transactions.append(transaction); + +entrypoint.send_transactions(transactions); +``` + ### Access underlying components ``` From b61c1ca287a9780df4426dd10b7c45ef6a0a78ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 15 Jul 2024 13:39:02 +0300 Subject: [PATCH 14/15] Using create_* for controllers. --- facades/entrypoints.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index e645565..ac27b8c 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -46,11 +46,11 @@ class NetworkEntrypoint: // Access to the individual controllers. create_smart_contract_controller(abi: Optional[Abi]): SmartContractController; - get_transfers_controller(): TransfersController; - get_token_management_controller(): TokenManagementController; - get_delegation_controller(): DelegationController; - get_relayed_controller(): RelayedController; - get_account_management__controller(): AccountManagementController; + create_transfers_controller(): TransfersController; + create_token_management_controller(): TokenManagementController; + create_delegation_controller(): DelegationController; + create_relayed_controller(): RelayedController; + create_account_management__controller(): AccountManagementController; ``` ## Examples @@ -59,7 +59,7 @@ class NetworkEntrypoint: ``` entrypoint = MainnetEntrypoint(); -controller = entrypoint.get_transfers_controller() +controller = entrypoint.create_transfers_controller() sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); @@ -148,7 +148,7 @@ parsed_outcome = controller.query({ entrypoint = MainnetEntrypoint(); abi = Abi.load("adder.abi.json"); contract_controller = entrypoint.create_smart_contract_controller(abi); -relayed_controller = entrypoint.get_relayed_controller(); +relayed_controller = entrypoint.create_relayed_controller(); sender = Account.new_from_pem("alice.pem"); relayer = Account.new_from_pem("carol.pem"); @@ -178,8 +178,8 @@ outcome = entrypoint.await_completed_transaction(transaction_hash); ``` entrypoint = MainnetEntrypoint(); -tokens_controller = entrypoint.get_token_management_controller(); -transfers_controller = entrypoint.get_transfers_controller(); +tokens_controller = entrypoint.create_token_management_controller(); +transfers_controller = entrypoint.create_transfers_controller(); sender = Account.new_from_pem("alice.pem"); sender.nonce = entrypoint.recall_account_nonce(sender.address); @@ -233,7 +233,7 @@ entrypoint.send_transactions(transactions); ``` entrypoint = MainnetEntrypoint(); -controller = entrypoint.get_delegation_controller() -controller = entrypoint.get_token_management_controller() +controller = entrypoint.create_delegation_controller() +controller = entrypoint.create_token_management_controller() network_provider = entrypoint.get_network_provider() ``` From 1def751a44e5ad8ceecad114e8d5de9445ee4265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 18 Jul 2024 16:50:03 +0300 Subject: [PATCH 15/15] Rename wrt. network providers. --- facades/entrypoints.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/facades/entrypoints.md b/facades/entrypoints.md index ac27b8c..cb28ead 100644 --- a/facades/entrypoints.md +++ b/facades/entrypoints.md @@ -3,11 +3,11 @@ Pre-defined entrypoints inherit from `NetworkEntrypoint` and use sensible default values. ``` -class MainnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://api.multiversx.com")); +class MainnetEntrypoint extends NetworkEntrypoint(BasicApiNetworkProvider("https://api.multiversx.com")); -class DevnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://devnet-api.multiversx.com")); +class DevnetEntrypoint extends NetworkEntrypoint(BasicApiNetworkProvider("https://devnet-api.multiversx.com")); -class TestnetEntrypoint extends NetworkEntrypoint(ApiNetworkProvider("https://testnet-api.multiversx.com")); +class TestnetEntrypoint extends NetworkEntrypoint(BasicApiNetworkProvider("https://testnet-api.multiversx.com")); ``` ## NetworkEntrypoint @@ -42,7 +42,7 @@ class NetworkEntrypoint: await_completed_transaction(transaction_hash: string): TransactionOnNetwork; // Access to the underlying network provider. - get_network_provider(): INetworkProvider; + get_network_provider(): IBasicNetworkProvider; // Access to the individual controllers. create_smart_contract_controller(abi: Optional[Abi]): SmartContractController;