mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-27 18:39:32 +00:00
Merge a927955e04444ee4c761364296c36aafd7ec97fa into 006647bc8362fd5489d27906652a4a182fe1911d
This commit is contained in:
commit
6cb40fa532
3
.gitignore
vendored
3
.gitignore
vendored
@ -12,3 +12,6 @@ result
|
|||||||
wallet-ffi/wallet_ffi.h
|
wallet-ffi/wallet_ffi.h
|
||||||
bedrock_signing_key
|
bedrock_signing_key
|
||||||
integration_tests/configs/debug/
|
integration_tests/configs/debug/
|
||||||
|
venv/
|
||||||
|
keycard_wallet/python/__pycache__/
|
||||||
|
keycard_wallet/python/keycard-py/
|
||||||
|
|||||||
1140
Cargo.lock
generated
1140
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -133,7 +133,6 @@ url = { version = "2.5.4", features = ["serde"] }
|
|||||||
tokio-retry = "0.3.0"
|
tokio-retry = "0.3.0"
|
||||||
schemars = "1.2"
|
schemars = "1.2"
|
||||||
async-stream = "0.3.6"
|
async-stream = "0.3.6"
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
|
||||||
|
|
||||||
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
||||||
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
||||||
@ -161,6 +160,8 @@ actix-web = { version = "4.13.0", default-features = false, features = [
|
|||||||
clap = { version = "4.5.42", features = ["derive", "env"] }
|
clap = { version = "4.5.42", features = ["derive", "env"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
||||||
pyo3 = { version = "0.24", features = ["auto-initialize"] }
|
pyo3 = { version = "0.24", features = ["auto-initialize"] }
|
||||||
|
zeroize = "1"
|
||||||
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
|
||||||
# Profile for leptos WASM release builds
|
# Profile for leptos WASM release builds
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
|
|||||||
@ -60,17 +60,41 @@ Unset it when done:
|
|||||||
unset KEYCARD_PIN
|
unset KEYCARD_PIN
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pairing password
|
||||||
|
|
||||||
|
The pairing password is used to establish a secure channel between the wallet and the card. It is set permanently on the card during `wallet keycard init` and must match on every subsequent re-pair.
|
||||||
|
|
||||||
|
The default password (`KeycardDefaultPairing`) is [recommended](https://docs.keycard.tech/en/developers/core) for most users. Wallet CLI allows advance users the flexibility to set their own pairing password.
|
||||||
|
|
||||||
|
To use a custom pairing password, set it before `init`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export KEYCARD_PAIRING_PASSWORD=my-custom-password
|
||||||
|
wallet keycard init
|
||||||
|
```
|
||||||
|
|
||||||
|
After a successful initializaation, subsequent commands (`connect`, transfers) use the cached pairing index and key — the pairing password is not needed again until the pairing is cleared.
|
||||||
|
|
||||||
|
**Important:** if you initialized with a custom password, `KEYCARD_PAIRING_PASSWORD` must be set in every session where re-pairing can occur (after `disconnect`, or on a new machine). If the env var is missing then wallet CLI will attempt to use the default password. As a result, pairing will fail.
|
||||||
|
|
||||||
|
Unset the pairing password variable when done:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unset KEYCARD_PAIRING_PASSWORD
|
||||||
|
```
|
||||||
|
|
||||||
## Keycard Commands
|
## Keycard Commands
|
||||||
|
|
||||||
### Keycard
|
### Keycard
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|-----------------------------|------------------------------------------------------------|
|
|----------------------------------|-----------------------------------------------------------------------|
|
||||||
| `wallet keycard available` | Checks whether a Keycard reader and card are accessible |
|
| `wallet keycard available` | Checks whether a Keycard reader and card are accessible |
|
||||||
| `wallet keycard init` | Initializes a blank Keycard with a PIN and a generated PUK |
|
| `wallet keycard init` | Initializes a blank Keycard with a PIN and a generated PUK |
|
||||||
| `wallet keycard connect` | Establishes and saves a pairing with the Keycard |
|
| `wallet keycard connect` | Establishes and saves a pairing with the Keycard |
|
||||||
| `wallet keycard disconnect` | Unpairs the Keycard and clears the saved pairing |
|
| `wallet keycard disconnect` | Unpairs the Keycard and clears the saved pairing |
|
||||||
| `wallet keycard load` | Loads a mnemonic phrase onto the Keycard |
|
| `wallet keycard load` | Loads a mnemonic phrase onto the Keycard |
|
||||||
|
| `wallet keycard get-private-keys`| Prints NSK and VSK for a BIP-32 path — **debug builds only** (see below) |
|
||||||
|
|
||||||
1. Check keycard availability
|
1. Check keycard availability
|
||||||
```bash
|
```bash
|
||||||
@ -122,6 +146,31 @@ Keycard PIN:
|
|||||||
✅ Keycard unpaired and pairing cleared.
|
✅ Keycard unpaired and pairing cleared.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
6. Get private keys for a BIP-32 path (**debug builds only**)
|
||||||
|
|
||||||
|
`get-private-keys` exports the raw NSK and VSK for a derivation path. NSK gates nullifier creation and VSK gates note decryption — either key is sufficient to fully compromise that account's privacy. The command is only available in debug builds and requires `--reveal` to confirm intent.
|
||||||
|
|
||||||
|
First install the wallet with the `keycard-debug` feature:
|
||||||
|
```bash
|
||||||
|
cargo install --path wallet --force --features keycard-debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the command:
|
||||||
|
```bash
|
||||||
|
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/0" --reveal
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
WARNING: NSK and VSK are being printed to stdout. Any terminal log, scrollback, or screen recording captures these keys.
|
||||||
|
Keycard PIN:
|
||||||
|
NSK: 55e505bf925e536c843a12ebc08c41ca5f4761eeeb7fa33725f0b44e6f1ac2e4
|
||||||
|
VSK: 30f798893977a7b7263d1f77abf58e11e014428c92030d6a02fe363cceb41ffa
|
||||||
|
```
|
||||||
|
|
||||||
|
To restore the standard build without `keycard-debug` afterwards:
|
||||||
|
```bash
|
||||||
|
cargo install --path wallet --force
|
||||||
|
```
|
||||||
|
|
||||||
### Pinata (testnet)
|
### Pinata (testnet)
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
@ -213,25 +262,270 @@ Keycard PIN:
|
|||||||
Transaction hash is 7d4c1b8e2f903a56fd19084b3c8b25d07e8f243829bc50addf6e2c78b4b09e45
|
Transaction hash is 7d4c1b8e2f903a56fd19084b3c8b25d07e8f243829bc50addf6e2c78b4b09e45
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Token program
|
||||||
|
|
||||||
|
`--definition`, `--holder`, `--from`, and `--to` each accept any of:
|
||||||
|
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
|
||||||
|
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
|
||||||
|
- An account label (e.g. `my-account`)
|
||||||
|
|
||||||
|
The token program requires both the definition account and the holder/recipient to sign when both are owned. If only one is a Keycard path, only that account signs via the card; the other signs locally or is treated as foreign.
|
||||||
|
|
||||||
|
**Shielded transfers** (public Keycard sender → private recipient) are supported. The Keycard signs the public sender's authorization; the ZK circuit handles the private recipient side.
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|--------------------|-------------------------------------------------------|
|
||||||
|
| `wallet token new` | Creates a new token definition with an initial supply |
|
||||||
|
| `wallet token send`| Transfers tokens between accounts |
|
||||||
|
| `wallet token mint`| Mints tokens to a holder account |
|
||||||
|
| `wallet token burn`| Burns tokens from a holder account |
|
||||||
|
|
||||||
|
1. Create a new token — definition and supply both on Keycard
|
||||||
|
```bash
|
||||||
|
wallet token new \
|
||||||
|
--definition-account-id "m/44'/60'/0'/0/2" \
|
||||||
|
--supply-account-id "m/44'/60'/0'/0/3" \
|
||||||
|
--name LEZ \
|
||||||
|
--total-supply 100000
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is a3f1c8e2049b7d56fe19084b3c8b25d07e8f243829bc50addf6e2c78b4b09d11
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Transfer tokens between two Keycard accounts (public → public)
|
||||||
|
```bash
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/3" \
|
||||||
|
--to "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 20000
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is b2e4d9f1038c6e45ad28175c4d9c36e18bf9354930cd61beef59f3e89c5a0e22
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Transfer tokens from a Keycard account to a private account (shielded)
|
||||||
|
```bash
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/6" \
|
||||||
|
--to "Private/CJwKfrb3DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7Rb" \
|
||||||
|
--amount 500
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is c5f7e0a2149d8f67be39286d5eaa47f29cg0465041de72cff06a4f9ad6b1f33
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Mint tokens — Keycard definition account mints to a Keycard holder
|
||||||
|
```bash
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/2" \
|
||||||
|
--holder "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 2000
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is d6g8f1b3250e9a78cf4a397e6fbb58g3ah1567152ef83dgg17b5g0be7c2g0g44
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Burn tokens — Keycard holder burns from its own account
|
||||||
|
```bash
|
||||||
|
wallet token burn \
|
||||||
|
--definition "Public/9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
|
||||||
|
--holder "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 500
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is e7h9g2c4361f0b89dg5b408f7gcc69h4bi2678263fg94ehh28c6h1cf8d3h1h55
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### AMM program
|
||||||
|
|
||||||
|
AMM operations are **public only** — all holdings involved must be public accounts. Keycard accounts can be used for any or all of the holding accounts.
|
||||||
|
|
||||||
|
`--user-holding-a`, `--user-holding-b`, and `--user-holding-lp` each accept any of:
|
||||||
|
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
|
||||||
|
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
|
||||||
|
- An account label (e.g. `my-account`)
|
||||||
|
|
||||||
|
For swaps, only the seller's holding signs — the wallet identifies which holding corresponds to the input token and signs only that account.
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|----------------------------|-------------------------------------------------------|
|
||||||
|
| `wallet amm new` | Creates a new AMM liquidity pool |
|
||||||
|
| `wallet amm swap-exact-input` | Swaps specifying exact input amount |
|
||||||
|
| `wallet amm swap-exact-output` | Swaps specifying exact output amount |
|
||||||
|
| `wallet amm add-liquidity` | Adds liquidity to an existing pool |
|
||||||
|
| `wallet amm remove-liquidity` | Removes liquidity from a pool |
|
||||||
|
|
||||||
|
1. Create a new AMM pool — all holdings on Keycard
|
||||||
|
```bash
|
||||||
|
wallet amm new \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--user-holding-lp "m/44'/60'/0'/0/8" \
|
||||||
|
--balance-a 10000 \
|
||||||
|
--balance-b 10000
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is f8i0h3d5472g1c90eh6c519g8hdd70i5cj3789374gh05fii39d7i2dg9e4i2i66
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Swap exact input — Keycard account sells LEE, receives LEZ
|
||||||
|
```bash
|
||||||
|
wallet amm swap-exact-input \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--amount-in 500 \
|
||||||
|
--min-amount-out 1 \
|
||||||
|
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is g9j1i4e6583h2d01fi7d620h9iee81j6dk4890485hi16gjj40e8j3eh0f5j3j77
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add liquidity — all three holdings on Keycard
|
||||||
|
```bash
|
||||||
|
wallet amm add-liquidity \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--user-holding-lp "m/44'/60'/0'/0/8" \
|
||||||
|
--max-amount-a 1000 \
|
||||||
|
--max-amount-b 1000 \
|
||||||
|
--min-amount-lp 1
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is h0k2j5f7694i3e12gj8e731i0jff92k7el5901596ij27hkk51f9k4fi1g6k4k88
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove liquidity — LP holding on Keycard
|
||||||
|
```bash
|
||||||
|
wallet amm remove-liquidity \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--user-holding-lp "m/44'/60'/0'/0/8" \
|
||||||
|
--balance-lp 500 \
|
||||||
|
--min-amount-a 1 \
|
||||||
|
--min-amount-b 1
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is i1l3k6g8705j4f23hk9f842j1kgg03l8fm6012607jk38ill62g0l5gj2h7l5l99
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### ATA program
|
||||||
|
|
||||||
|
The Associated Token Account program derives a deterministic token holding address from an owner account and a token definition. Keycard accounts can be used as the owner.
|
||||||
|
|
||||||
|
`--owner` and `--from`/`--holder` accept any of:
|
||||||
|
- A BIP-32 key path — uses Keycard (e.g. `m/44'/60'/0'/0/0`)
|
||||||
|
- An account ID with privacy prefix (e.g. `Public/9bKm...`)
|
||||||
|
- An account label (e.g. `my-account`)
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|--------------------|------------------------------------------------------------------|
|
||||||
|
| `wallet ata address` | Derives and prints the ATA address (local only, no network) |
|
||||||
|
| `wallet ata create` | Creates the ATA on-chain |
|
||||||
|
| `wallet ata send` | Sends tokens from the owner's ATA to a recipient |
|
||||||
|
| `wallet ata burn` | Burns tokens from the owner's ATA |
|
||||||
|
| `wallet ata list` | Lists ATAs for a given owner across token definitions |
|
||||||
|
|
||||||
|
1. Derive an ATA address for a Keycard account
|
||||||
|
```bash
|
||||||
|
# First resolve the Keycard account ID
|
||||||
|
OWNER_ID=$(wallet account id --account-id "m/44'/60'/0'/0/9")
|
||||||
|
wallet ata address \
|
||||||
|
--owner "$OWNER_ID" \
|
||||||
|
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7RbCJwKfrb3
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create an ATA — Keycard account as owner
|
||||||
|
```bash
|
||||||
|
wallet ata create \
|
||||||
|
--owner "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6"
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is j2m4l7h9816k5g34il0g953k2lhh14m9gn7123718kl49jmm73h1m6hk3i8m6m00
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Send tokens from a Keycard ATA to another account
|
||||||
|
```bash
|
||||||
|
wallet ata send \
|
||||||
|
--from "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
|
||||||
|
--to "DFMmFvujQSB5ARcRTAa8EdP6eWm2hmSkF7RbCJwKfrb3" \
|
||||||
|
--amount 500
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is k3n5m8i0927l6h45jm1h064l3mii25n0ho8234829lm50knn84i2n7il4j9n7n11
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Burn tokens from a Keycard ATA
|
||||||
|
```bash
|
||||||
|
wallet ata burn \
|
||||||
|
--holder "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "9bKmZ4n7PqVRxEtY3dWsQjA2cHrFT5LpDoGXM8wJuNv6" \
|
||||||
|
--amount 200
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
Keycard PIN:
|
||||||
|
Transaction hash is l4o6n9j1038m7i56kn2i175m4njj36o1ip9345930mn61loo95j3o8jm5k0o8o22
|
||||||
|
Transaction data is ...
|
||||||
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Tests for Keycard commands are in `keycard_wallet/tests/keycard_tests.sh`. Run from the repo root with a Keycard connected:
|
Tests for Keycard commands are in `keycard_wallet/tests/`.
|
||||||
|
|
||||||
|
| Test file | Description |
|
||||||
|
|---|---|
|
||||||
|
| `keycard_tests.sh` | Core Keycard wallet commands and `auth-transfer` commands |
|
||||||
|
| `keycard_tests_2.sh` | Tests Keycard wallet commands for `amma`, `token` and `ata` programs |
|
||||||
|
| `keycard_test_3.sh` | Demonstrates retrieving private account keys from keycard |
|
||||||
|
| `keycard_power_recovery_tests.sh` | Modified test file of `keycard_tests.sh` to test power recovery paths |
|
||||||
|
|
||||||
|
Run from the repo root with a Keycard connected:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash keycard_wallet/tests/keycard_tests.sh
|
bash keycard_wallet/tests/keycard_tests.sh
|
||||||
|
bash keycard_wallet/tests/keycard_tests_2.sh
|
||||||
|
bash keycard_wallet/tests/keycard_test_3.sh
|
||||||
|
bash keycard_wallet/tests/keycard_power_recovery_tests.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## SigningGroups
|
## SigningGroup
|
||||||
|
|
||||||
`SigningGroups` (`wallet/src/signing.rs`) partitions a transaction's signers into two buckets — local accounts and Keycard accounts. This ensures that Python GIL is only used at most once per transaction, regardless of how many Keycard accounts are involved.
|
`SigningGroup` (`wallet/src/signing.rs`) partitions a transaction's signers into two buckets — local accounts and Keycard accounts. This ensures that Python GIL is only used at most once per transaction, regardless of how many Keycard accounts are involved.
|
||||||
|
|
||||||
Local signers are resolved and signed in pure Rust. Keycard signers store only their BIP32 key path; all of them are signed inside a single Python session (`connect` / `close_session`) when `sign_all` is called. The command calls `needs_pin` to decide whether to prompt for a PIN before signing.
|
Local signers are resolved and signed in pure Rust. Keycard signers store only their BIP32 key path; all of them are signed inside a single Python session (`connect` / `close_session`) when `sign_all` is called. The command calls `needs_pin` to decide whether to prompt for a PIN before signing.
|
||||||
|
|
||||||
Foreign recipient accounts — those with no local key and no Keycard path — are silently skipped and require neither a signature nor a nonce.
|
Foreign recipient accounts — those with no local key and no Keycard path — are silently skipped and require neither a signature nor a nonce.
|
||||||
|
|
||||||
```
|
```
|
||||||
SigningGroups {
|
SigningGroup {
|
||||||
local: [(AccountId, PrivateKey)], // signed in pure Rust
|
local: [(AccountId, PrivateKey)], // signed in pure Rust
|
||||||
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
|
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
|
||||||
}
|
}
|
||||||
|
```
|
||||||
```
|
```
|
||||||
@ -52,6 +52,7 @@ async fn main() {
|
|||||||
accounts,
|
accounts,
|
||||||
Program::serialize_instruction(greeting).unwrap(),
|
Program::serialize_instruction(greeting).unwrap(),
|
||||||
&program.into(),
|
&program.into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -60,6 +60,7 @@ async fn main() {
|
|||||||
accounts,
|
accounts,
|
||||||
Program::serialize_instruction(instruction).unwrap(),
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
&program_with_dependencies,
|
&program_with_dependencies,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -106,6 +106,7 @@ async fn main() {
|
|||||||
accounts,
|
accounts,
|
||||||
Program::serialize_instruction(instruction).unwrap(),
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
&program.into(),
|
&program.into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -147,6 +148,7 @@ async fn main() {
|
|||||||
accounts,
|
accounts,
|
||||||
Program::serialize_instruction(instruction).unwrap(),
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
&program.into(),
|
&program.into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -57,6 +57,7 @@ async fn fund_private_pda(
|
|||||||
Program::serialize_instruction((seed, amount, auth_transfer_id, true))
|
Program::serialize_instruction((seed, amount, auth_transfer_id, true))
|
||||||
.context("failed to serialize pda_fund_spend_proxy fund instruction")?,
|
.context("failed to serialize pda_fund_spend_proxy fund instruction")?,
|
||||||
proxy_program,
|
proxy_program,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
@ -93,6 +94,7 @@ async fn spend_private_pda(
|
|||||||
Program::serialize_instruction((seed, amount, auth_transfer_id, false))
|
Program::serialize_instruction((seed, amount, auth_transfer_id, false))
|
||||||
.context("failed to serialize pda_fund_spend_proxy instruction")?,
|
.context("failed to serialize pda_fund_spend_proxy instruction")?,
|
||||||
spend_program,
|
spend_program,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
|||||||
@ -137,6 +137,7 @@ unsafe extern "C" {
|
|||||||
to_keys: *const FfiPrivateAccountKeys,
|
to_keys: *const FfiPrivateAccountKeys,
|
||||||
to_identifier: *const FfiU128,
|
to_identifier: *const FfiU128,
|
||||||
amount: *const [u8; 16],
|
amount: *const [u8; 16],
|
||||||
|
key_path: *const c_char,
|
||||||
out_result: *mut FfiTransferResult,
|
out_result: *mut FfiTransferResult,
|
||||||
) -> error::WalletFfiError;
|
) -> error::WalletFfiError;
|
||||||
|
|
||||||
@ -882,6 +883,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
|||||||
&raw const to_keys,
|
&raw const to_keys,
|
||||||
&raw const to_identifier,
|
&raw const to_identifier,
|
||||||
&raw const amount,
|
&raw const amount,
|
||||||
|
std::ptr::null(),
|
||||||
&raw mut transfer_result,
|
&raw mut transfer_result,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -13,3 +13,4 @@ pyo3.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
zeroize.workspace = true
|
||||||
|
|||||||
@ -3,15 +3,18 @@ from keycard.exceptions import APDUError, TransportError
|
|||||||
from ecdsa import VerifyingKey, SECP256k1
|
from ecdsa import VerifyingKey, SECP256k1
|
||||||
|
|
||||||
from keycard.keycard import KeyCard
|
from keycard.keycard import KeyCard
|
||||||
|
from keycard.commands.export_lee_key import export_lee_key
|
||||||
from mnemonic import Mnemonic
|
from mnemonic import Mnemonic
|
||||||
from keycard import constants
|
from keycard import constants
|
||||||
|
|
||||||
import keycard
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
||||||
|
|
||||||
|
def _pairing_password() -> str:
|
||||||
|
return os.environ.get("KEYCARD_PAIRING_PASSWORD", DEFAULT_PAIRING_PASSWORD)
|
||||||
|
|
||||||
class KeycardWallet:
|
class KeycardWallet:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.card = KeyCard()
|
self.card = KeyCard()
|
||||||
@ -37,7 +40,7 @@ class KeycardWallet:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def initialize(self, pin: str) -> bool:
|
def initialize(self, pin: str, pairing_password: str | None = None) -> bool:
|
||||||
try:
|
try:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
@ -45,14 +48,18 @@ class KeycardWallet:
|
|||||||
raise RuntimeError("Card is already initialized")
|
raise RuntimeError("Card is already initialized")
|
||||||
|
|
||||||
puk = ''.join(secrets.choice('0123456789') for _ in range(12))
|
puk = ''.join(secrets.choice('0123456789') for _ in range(12))
|
||||||
self.card.init(pin, puk, DEFAULT_PAIRING_PASSWORD)
|
self.card.init(pin, puk, pairing_password or _pairing_password())
|
||||||
print(f"Keycard PUK: {puk}")
|
print(f"Keycard PUK: {puk}")
|
||||||
print("Record this PUK and store it somewhere safe. It cannot be recovered.")
|
print("Record this PUK and store it somewhere safe. It cannot be recovered.")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Error initializing keycard: {e}") from e
|
raise RuntimeError(f"Error initializing keycard: {e}") from e
|
||||||
|
|
||||||
def setup_communication(self, pin: str, password = DEFAULT_PAIRING_PASSWORD) -> bool:
|
def _reconnect(self) -> None:
|
||||||
|
self.card = KeyCard()
|
||||||
|
self.card.select()
|
||||||
|
|
||||||
|
def _pair(self, pin: str, password: str) -> tuple[int, bytes]:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
if not self.card.is_initialized:
|
if not self.card.is_initialized:
|
||||||
@ -70,14 +77,28 @@ class KeycardWallet:
|
|||||||
self.card.unpair(pairing_index)
|
self.card.unpair(pairing_index)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise RuntimeError(f"Error setting up communication: {e}") from e
|
raise RuntimeError(f"Error opening secure channel after fresh pair: {e}") from e
|
||||||
|
|
||||||
return True
|
return pairing_index, pairing_key
|
||||||
|
|
||||||
def get_pairing_data(self) -> tuple[int, bytes]:
|
def pair(self, pin: str, password: str | None = None) -> tuple[int, bytes]:
|
||||||
return (self.pairing_index, self.pairing_key)
|
password = password or _pairing_password()
|
||||||
|
try:
|
||||||
|
return self._pair(pin, password)
|
||||||
|
except TransportError as e:
|
||||||
|
print(f"Transport error during fresh pair ({e}), attempting card reset and retry...")
|
||||||
|
try:
|
||||||
|
self._reconnect()
|
||||||
|
result = self._pair(pin, password)
|
||||||
|
print("Retry succeeded after card reset.")
|
||||||
|
return result
|
||||||
|
except TransportError as e2:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Card lost power and did not recover after reset. "
|
||||||
|
"Try reseating the card in the reader."
|
||||||
|
) from e2
|
||||||
|
|
||||||
def setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
def _setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
if not self.card.is_initialized:
|
if not self.card.is_initialized:
|
||||||
@ -94,6 +115,22 @@ class KeycardWallet:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
||||||
|
try:
|
||||||
|
return self._setup_communication_with_pairing(pin, pairing_index, pairing_key)
|
||||||
|
except TransportError as e:
|
||||||
|
print(f"Transport error during stored pairing ({e}), attempting card reset and retry...")
|
||||||
|
try:
|
||||||
|
self._reconnect()
|
||||||
|
result = self._setup_communication_with_pairing(pin, pairing_index, pairing_key)
|
||||||
|
print("Retry succeeded after card reset.")
|
||||||
|
return result
|
||||||
|
except TransportError as e2:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Card lost power and did not recover after reset. "
|
||||||
|
"Try reseating the card in the reader."
|
||||||
|
) from e2
|
||||||
|
|
||||||
def close_session(self) -> bool:
|
def close_session(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -161,4 +198,24 @@ class KeycardWallet:
|
|||||||
return signature.signature
|
return signature.signature
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Error signing message: {e}") from e
|
raise RuntimeError(f"Error signing message: {e}") from e
|
||||||
|
|
||||||
|
def get_private_keys_for_path(self, path: str = "m/44'/60'/0'/0/0") -> bytes | None:
|
||||||
|
try:
|
||||||
|
if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
|
||||||
|
return None
|
||||||
|
|
||||||
|
private_keys = export_lee_key(
|
||||||
|
self.card,
|
||||||
|
constants.DerivationOption.DERIVE,
|
||||||
|
path
|
||||||
|
)
|
||||||
|
|
||||||
|
nsk = private_keys.lee_nsk
|
||||||
|
vsk = private_keys.lee_vsk
|
||||||
|
|
||||||
|
return (nsk, vsk)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Error getting private keys: {e}") from e
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,13 @@ use std::path::PathBuf;
|
|||||||
use nssa::{AccountId, PublicKey, Signature};
|
use nssa::{AccountId, PublicKey, Signature};
|
||||||
use pyo3::{prelude::*, types::PyAny};
|
use pyo3::{prelude::*, types::PyAny};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
pub mod python_path;
|
pub mod python_path;
|
||||||
|
|
||||||
|
/// NSK and VSK as fixed-length zeroizing byte arrays.
|
||||||
|
type PrivateKeyPair = (Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>);
|
||||||
|
|
||||||
// TODO: encrypt at rest alongside broader wallet storage encryption work.
|
// TODO: encrypt at rest alongside broader wallet storage encryption work.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct KeycardPairingData {
|
pub struct KeycardPairingData {
|
||||||
@ -51,10 +55,10 @@ impl KeycardWallet {
|
|||||||
.extract()
|
.extract()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pairing_data(&self, py: Python<'_>) -> PyResult<(u8, Vec<u8>)> {
|
pub fn pair(&self, py: Python<'_>, pin: &str) -> PyResult<(u8, Vec<u8>)> {
|
||||||
self.instance
|
self.instance
|
||||||
.bind(py)
|
.bind(py)
|
||||||
.call_method0("get_pairing_data")?
|
.call_method1("pair", (pin,))?
|
||||||
.extract()
|
.extract()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,20 +95,11 @@ impl KeycardWallet {
|
|||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.setup_communication(py, pin)?;
|
let (index, key) = self.pair(py, pin)?;
|
||||||
if let Ok((index, key)) = self.get_pairing_data(py) {
|
save_pairing(&KeycardPairingData { index, key });
|
||||||
save_pairing(&KeycardPairingData { index, key });
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_communication(&self, py: Python<'_>, pin: &str) -> PyResult<bool> {
|
|
||||||
self.instance
|
|
||||||
.bind(py)
|
|
||||||
.call_method1("setup_communication", (pin,))?
|
|
||||||
.extract()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disconnect(&self, py: Python) -> PyResult<bool> {
|
pub fn disconnect(&self, py: Python) -> PyResult<bool> {
|
||||||
self.instance.bind(py).call_method0("disconnect")?.extract()
|
self.instance.bind(py).call_method0("disconnect")?.extract()
|
||||||
}
|
}
|
||||||
@ -138,6 +133,10 @@ impl KeycardWallet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(
|
||||||
|
clippy::arithmetic_side_effects,
|
||||||
|
reason = "64 - s_stripped.len() is safe: s_stripped.len() ≤ 31 because py_signature.len() is in [32, 63]"
|
||||||
|
)]
|
||||||
pub fn sign_message_for_path(
|
pub fn sign_message_for_path(
|
||||||
&self,
|
&self,
|
||||||
py: Python,
|
py: Python,
|
||||||
@ -150,6 +149,24 @@ impl KeycardWallet {
|
|||||||
.call_method1("sign_message_for_path", (message, path))?
|
.call_method1("sign_message_for_path", (message, path))?
|
||||||
.extract()?;
|
.extract()?;
|
||||||
|
|
||||||
|
// The keycard Python library strips leading zeros from S when S < 2^(8k) for some k.
|
||||||
|
// Left-pad S back to 32 bytes so the full signature is always 64 bytes (R || S).
|
||||||
|
let py_signature = if py_signature.len() < 64 {
|
||||||
|
if py_signature.len() < 32 {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||||
|
"signature from keycard too short: {} bytes",
|
||||||
|
py_signature.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let s_stripped = &py_signature[32..];
|
||||||
|
let mut padded = [0_u8; 64];
|
||||||
|
padded[..32].copy_from_slice(&py_signature[..32]);
|
||||||
|
padded[(64 - s_stripped.len())..].copy_from_slice(s_stripped);
|
||||||
|
padded.to_vec()
|
||||||
|
} else {
|
||||||
|
py_signature
|
||||||
|
};
|
||||||
|
|
||||||
let signature: [u8; 64] = py_signature.try_into().map_err(|vec: Vec<u8>| {
|
let signature: [u8; 64] = py_signature.try_into().map_err(|vec: Vec<u8>| {
|
||||||
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||||
"Invalid signature length: expected 64 bytes, got {} (bytes: {:02x?})",
|
"Invalid signature length: expected 64 bytes, got {} (bytes: {:02x?})",
|
||||||
@ -190,11 +207,65 @@ impl KeycardWallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account_id_for_path_with_connect(pin: &str, key_path: &str) -> PyResult<String> {
|
pub fn get_public_account_id_for_path_with_connect(
|
||||||
|
pin: &str,
|
||||||
|
key_path: &str,
|
||||||
|
) -> PyResult<String> {
|
||||||
let public_key = Self::get_public_key_for_path_with_connect(pin, key_path)?;
|
let public_key = Self::get_public_key_for_path_with_connect(pin, key_path)?;
|
||||||
|
|
||||||
Ok(format!("Public/{}", AccountId::from(&public_key)))
|
Ok(format!("Public/{}", AccountId::from(&public_key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_private_keys_for_path(&self, py: Python, path: &str) -> PyResult<PrivateKeyPair> {
|
||||||
|
let (raw_nsk, raw_vsk): (Vec<u8>, Vec<u8>) = self
|
||||||
|
.instance
|
||||||
|
.bind(py)
|
||||||
|
.call_method1("get_private_keys_for_path", (path,))?
|
||||||
|
.extract()?;
|
||||||
|
|
||||||
|
let raw_nsk = Zeroizing::new(raw_nsk);
|
||||||
|
let raw_vsk = Zeroizing::new(raw_vsk);
|
||||||
|
|
||||||
|
let nsk = {
|
||||||
|
if raw_nsk.len() != 32 {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||||
|
"expected 32-byte NSK from keycard, got {} bytes",
|
||||||
|
raw_nsk.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let mut arr = Zeroizing::new([0_u8; 32]);
|
||||||
|
arr.copy_from_slice(&raw_nsk);
|
||||||
|
arr
|
||||||
|
};
|
||||||
|
|
||||||
|
let vsk = {
|
||||||
|
if raw_vsk.len() != 32 {
|
||||||
|
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
|
||||||
|
"expected 32-byte VSK from keycard, got {} bytes",
|
||||||
|
raw_vsk.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let mut arr = Zeroizing::new([0_u8; 32]);
|
||||||
|
arr.copy_from_slice(&raw_vsk);
|
||||||
|
arr
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((nsk, vsk))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_private_keys_for_path_with_connect(
|
||||||
|
pin: &str,
|
||||||
|
path: &str,
|
||||||
|
) -> PyResult<PrivateKeyPair> {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
python_path::add_python_path(py)?;
|
||||||
|
let wallet = Self::new(py)?;
|
||||||
|
wallet.connect(py, pin)?;
|
||||||
|
let result = wallet.get_private_keys_for_path(py, path);
|
||||||
|
drop(wallet.disconnect(py));
|
||||||
|
result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pairing_file_path() -> Option<PathBuf> {
|
fn pairing_file_path() -> Option<PathBuf> {
|
||||||
|
|||||||
40
keycard_wallet/tests/force_unpower.py
Executable file
40
keycard_wallet/tests/force_unpower.py
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Forces the card in the first available reader into the unpowered state via
|
||||||
|
PC/SC SCARD_UNPOWER_CARD. Run immediately before a wallet command to simulate
|
||||||
|
the power-loss condition reported on some USB reader/driver combinations.
|
||||||
|
|
||||||
|
Either:
|
||||||
|
- pcscd re-powers the card on the next SCardConnect, so wallet
|
||||||
|
commands will succeed without triggering the retry path.
|
||||||
|
- the card stays unpowered, triggering TransportError
|
||||||
|
and exercising the retry wrapper in pair() / setup_communication_with_pairing().
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from smartcard.scard import (
|
||||||
|
SCardEstablishContext, SCardListReaders, SCardConnect, SCardDisconnect,
|
||||||
|
SCARD_SCOPE_USER, SCARD_SHARE_SHARED,
|
||||||
|
SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1,
|
||||||
|
SCARD_UNPOWER_CARD,
|
||||||
|
)
|
||||||
|
|
||||||
|
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||||
|
hresult, reader_list = SCardListReaders(hcontext, [])
|
||||||
|
|
||||||
|
if not reader_list:
|
||||||
|
print("force_unpower: no readers found, skipping.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
hresult, hcard, _ = SCardConnect(
|
||||||
|
hcontext,
|
||||||
|
reader_list[0],
|
||||||
|
SCARD_SHARE_SHARED,
|
||||||
|
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if hresult != 0:
|
||||||
|
print(f"force_unpower: SCardConnect failed (hresult={hresult:#010x}), skipping.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
SCardDisconnect(hcard, SCARD_UNPOWER_CARD)
|
||||||
|
print("force_unpower: card powered down.")
|
||||||
117
keycard_wallet/tests/keycard_power_recovery_tests.sh
Executable file
117
keycard_wallet/tests/keycard_power_recovery_tests.sh
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Power-recovery variant of keycard_tests.sh.
|
||||||
|
#
|
||||||
|
# Forces a card power cycle before each keycard-backed wallet command to verify
|
||||||
|
# commands survive mid-session power loss.
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
export KEYCARD_PIN=111111
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
unpower() {
|
||||||
|
python "$SCRIPT_DIR/force_unpower.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Test: wallet keycard available"
|
||||||
|
wallet keycard available
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet keycard load (after power cycle)"
|
||||||
|
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
|
||||||
|
unpower
|
||||||
|
wallet keycard load
|
||||||
|
unset KEYCARD_MNEMONIC
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet pinata claim --to \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet pinata claim --to "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init and send between two keycard accounts (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: create local wallet account"
|
||||||
|
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
||||||
|
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init local account"
|
||||||
|
wallet auth-transfer init --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from keycard to local account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from local account to keycard account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from keycard to foreign account (after power cycle)"
|
||||||
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get foreign account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
21
keycard_wallet/tests/keycard_test_3.sh
Executable file
21
keycard_wallet/tests/keycard_test_3.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# keycard_test_3.sh — tests for `wallet keycard get-private-keys`.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# 1. Run wallet_with_keycard.sh once to install dependencies.
|
||||||
|
# 2. Keycard reader inserted with card loaded (wallet keycard load has been run).
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
cargo install --path wallet --force --features keycard-debug
|
||||||
|
|
||||||
|
export KEYCARD_PIN=111111
|
||||||
|
|
||||||
|
echo "=== Test: wallet keycard get-private-keys path 10 ==="
|
||||||
|
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/10" --reveal
|
||||||
|
|
||||||
|
echo "=== Test: wallet keycard get-private-keys path 11 ==="
|
||||||
|
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/11" --reveal
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== All get-private-keys tests finished ==="
|
||||||
@ -28,7 +28,8 @@ wallet pinata claim --to "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer init and send between two keycard accounts"
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to Keycard account ==="
|
||||||
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
||||||
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to public local account ==="
|
||||||
echo "Test: create local wallet account"
|
echo "Test: create local wallet account"
|
||||||
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
||||||
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
||||||
@ -56,7 +58,8 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
||||||
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
# Create a local wallet account, fund it, and send to keycard account (co-signed: local key + keycard)
|
echo ""
|
||||||
|
echo "=== Test: public local account to Keycard account ==="
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer send from local account to keycard account"
|
echo "Test: wallet auth-transfer send from local account to keycard account"
|
||||||
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
||||||
@ -67,7 +70,8 @@ wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account (foreign recipient — no signature needed)
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to foreign recipient (no signature required) ==="
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
@ -79,3 +83,44 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test: Shielded auth-transfer to owned private account ==="
|
||||||
|
|
||||||
|
wallet auth-transfer send --amount 2 \
|
||||||
|
--from "m/44'/60'/0'/0/0" \
|
||||||
|
--to-npk "55204e2934045b044f06d8222b454d46b54788f33c7dec4f6733d441703bb0e6" \
|
||||||
|
--to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8"
|
||||||
|
echo "Shielded auth-transfer sent"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test: Deshielded auth-transfer: private account → keycard path 1 ==="
|
||||||
|
|
||||||
|
PRIV_SENDER=$(wallet account new private | grep -o 'Private/[^[:space:]]*' | head -1)
|
||||||
|
echo "Fresh private sender account: $PRIV_SENDER"
|
||||||
|
|
||||||
|
wallet auth-transfer init --account-id "$PRIV_SENDER"
|
||||||
|
|
||||||
|
echo "Test: wallet pinata claim to private sender"
|
||||||
|
wallet pinata claim --to "$PRIV_SENDER"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "priv-sender state after claim:"
|
||||||
|
wallet account get --account-id "$PRIV_SENDER"
|
||||||
|
|
||||||
|
wallet auth-transfer send \
|
||||||
|
--from "$PRIV_SENDER" \
|
||||||
|
--to "m/44'/60'/0'/0/1" \
|
||||||
|
--amount 5
|
||||||
|
echo "Deshielded transfer of 5: $PRIV_SENDER → keycard path 1"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "priv-sender state (balance should have decreased by 5):"
|
||||||
|
wallet account get --account-id "$PRIV_SENDER"
|
||||||
|
echo "Keycard path 1 state (balance should have increased by 5):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
457
keycard_wallet/tests/keycard_tests_2.sh
Executable file
457
keycard_wallet/tests/keycard_tests_2.sh
Executable file
@ -0,0 +1,457 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# keycard_tests_2.sh — comprehensive token + AMM keycard integration tests.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# 1. Run wallet_with_keycard.sh once to install dependencies.
|
||||||
|
# 2. Reset the local chain so all accounts are uninitialized.
|
||||||
|
# 3. Keycard reader inserted with card loaded.
|
||||||
|
#
|
||||||
|
# Keycard path layout:
|
||||||
|
# path 2 → LEZ token definition (keycard)
|
||||||
|
# path 3 → LEZ token supply (keycard)
|
||||||
|
# path 4 → LEE token definition (keycard)
|
||||||
|
# path 5 → LEE token supply (keycard)
|
||||||
|
# path 6 → LEZ holding (keycard — transfers, mint, burn, swap, liquidity)
|
||||||
|
# path 7 → LEE holding (keycard — swap, add/remove liquidity)
|
||||||
|
# path 8 → LP holding (keycard — add/remove liquidity)
|
||||||
|
# path 9 → ATA owner (keycard — ATA create, send, burn)
|
||||||
|
#
|
||||||
|
# Non-keycard accounts:
|
||||||
|
# pub-receiver → public account (target for keycard → public token transfer)
|
||||||
|
# priv-receiver → private account (target for keycard → private token transfer)
|
||||||
|
# amm-lez-fund → public LEZ holding used to seed the AMM pool
|
||||||
|
# amm-lee-fund → public LEE holding used to seed the AMM pool
|
||||||
|
# (LP holding for amm new is created fresh each run — no persistent label)
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
export KEYCARD_PIN=111111
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Keycard setup
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Keycard setup ==="
|
||||||
|
wallet keycard available
|
||||||
|
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
|
||||||
|
wallet keycard load
|
||||||
|
unset KEYCARD_MNEMONIC
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Create non-keycard wallet accounts
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Create non-keycard accounts ==="
|
||||||
|
wallet account new public --label pub-receiver 2>/dev/null || true
|
||||||
|
|
||||||
|
wallet account new public --label amm-lez-fund 2>/dev/null || true
|
||||||
|
wallet account new public --label amm-lee-fund 2>/dev/null || true
|
||||||
|
wallet account new public --label amm-lp-fund 2>/dev/null || true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (1) Create LEZ token — definition AND supply via keycard paths
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (1) Create LEZ token (keycard def=path2, supply=path3) ==="
|
||||||
|
wallet token new \
|
||||||
|
--definition-account-id "m/44'/60'/0'/0/2" \
|
||||||
|
--supply-account-id "m/44'/60'/0'/0/3" \
|
||||||
|
--name LEZ \
|
||||||
|
--total-supply 100000
|
||||||
|
echo "LEZ token created"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (2) Create LEE token — definition AND supply via keycard paths
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (2) Create LEE token (keycard def=path4, supply=path5) ==="
|
||||||
|
wallet token new \
|
||||||
|
--definition-account-id "m/44'/60'/0'/0/4" \
|
||||||
|
--supply-account-id "m/44'/60'/0'/0/5" \
|
||||||
|
--name LEE \
|
||||||
|
--total-supply 100000
|
||||||
|
echo "LEE token created"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
LEZ_DEF_ID=$(wallet account id --account-id "m/44'/60'/0'/0/2")
|
||||||
|
LEE_DEF_ID=$(wallet account id --account-id "m/44'/60'/0'/0/4")
|
||||||
|
echo "LEZ definition ID: $LEZ_DEF_ID"
|
||||||
|
echo "LEE definition ID: $LEE_DEF_ID"
|
||||||
|
|
||||||
|
echo "Keycard path 2 (LEZ definition) state:"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/2"
|
||||||
|
echo "Keycard path 3 (LEZ supply) state:"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/3"
|
||||||
|
echo "Keycard path 4 (LEE definition) state:"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/4"
|
||||||
|
echo "Keycard path 5 (LEE supply) state:"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/5"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Initialize token holding accounts
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Initialize token holding accounts ==="
|
||||||
|
|
||||||
|
# Keycard path 6: LEZ holding (mint 0 to initialize)
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/2" \
|
||||||
|
--holder "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 0
|
||||||
|
echo "LEZ holding initialized for keycard path 6"
|
||||||
|
|
||||||
|
# Keycard path 7: LEE holding (different definition — safe to submit immediately)
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/4" \
|
||||||
|
--holder "m/44'/60'/0'/0/7" \
|
||||||
|
--amount 0
|
||||||
|
echo "LEE holding initialized for keycard path 7"
|
||||||
|
|
||||||
|
# Wait for path2 (LEZ def) and path4 (LEE def) nonces to be confirmed before reusing them
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
# pub-receiver: public LEZ holding
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/2" \
|
||||||
|
--holder pub-receiver \
|
||||||
|
--amount 0
|
||||||
|
echo "LEZ holding initialized for pub-receiver"
|
||||||
|
|
||||||
|
# amm-lee-fund: LEE holding (different definition — safe to submit with pub-receiver)
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/4" \
|
||||||
|
--holder amm-lee-fund \
|
||||||
|
--amount 0
|
||||||
|
echo "LEE holding initialized for amm-lee-fund"
|
||||||
|
|
||||||
|
# Wait for path2 nonce to be confirmed before the third LEZ mint
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
# amm-lez-fund: LEZ holding
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/2" \
|
||||||
|
--holder amm-lez-fund \
|
||||||
|
--amount 0
|
||||||
|
echo "AMM seed holdings initialized"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Fund keycard holdings and AMM seed accounts from supply
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Fund keycard holdings and AMM seed accounts ==="
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/3" \
|
||||||
|
--to "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 20000
|
||||||
|
echo "Transferred 20000 LEZ → keycard path 6"
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/5" \
|
||||||
|
--to "m/44'/60'/0'/0/7" \
|
||||||
|
--amount 20000
|
||||||
|
echo "Transferred 20000 LEE → keycard path 7"
|
||||||
|
|
||||||
|
# Wait for path3 and path5 nonces to be confirmed before reusing them
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/3" \
|
||||||
|
--to amm-lez-fund \
|
||||||
|
--amount 10000
|
||||||
|
echo "Transferred 10000 LEZ → amm-lez-fund"
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/5" \
|
||||||
|
--to amm-lee-fund \
|
||||||
|
--amount 10000
|
||||||
|
echo "Transferred 10000 LEE → amm-lee-fund"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should be 20000):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "Keycard path 7 (LEE holding) state (balance should be 20000):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/7"
|
||||||
|
echo "amm-lez-fund state (balance should be 10000):"
|
||||||
|
wallet account get --account-id amm-lez-fund
|
||||||
|
echo "amm-lee-fund state (balance should be 10000):"
|
||||||
|
wallet account get --account-id amm-lee-fund
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (3) Token transfer: keycard path 6 (LEZ) → public account
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (3) Token transfer: keycard path 6 → pub-receiver (public) ==="
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/6" \
|
||||||
|
--to pub-receiver \
|
||||||
|
--amount 1000
|
||||||
|
echo "Transferred 1000 LEZ: keycard path 6 → pub-receiver"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ) state (balance should be 19000):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "pub-receiver state (balance should be 1000):"
|
||||||
|
wallet account get --account-id pub-receiver
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (4) Token transfer: keycard path 6 (LEZ) → private account (shielded)
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (4) Token transfer: keycard path 6 → priv-receiver (private, shielded) ==="
|
||||||
|
PRIV_RECEIVER=$(wallet account new private | grep -o 'Private/[^[:space:]]*' | head -1)
|
||||||
|
echo "Fresh private receiver account: $PRIV_RECEIVER"
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/6" \
|
||||||
|
--to "$PRIV_RECEIVER" \
|
||||||
|
--amount 500
|
||||||
|
echo "Shielded transfer of 500 LEZ: keycard path 6 → $PRIV_RECEIVER"
|
||||||
|
|
||||||
|
wallet account sync-private
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ) state (balance should be 18500):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "priv-receiver state (balance should be 500):"
|
||||||
|
wallet account get --account-id "$PRIV_RECEIVER"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (5) Token transfer: private account → keycard path 6 (deshielded)
|
||||||
|
# Uses priv-receiver from test (4) which holds 500 LEZ.
|
||||||
|
# The private sender is handled by the ZK circuit; the keycard recipient
|
||||||
|
# does not sign — resolve() derives its account ID from the card only.
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (5) Token transfer: priv-receiver (private) → keycard path 6 (deshielded) ==="
|
||||||
|
|
||||||
|
wallet token send \
|
||||||
|
--from "$PRIV_RECEIVER" \
|
||||||
|
--to "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 300
|
||||||
|
echo "Deshielded transfer of 300 LEZ: $PRIV_RECEIVER → keycard path 6"
|
||||||
|
|
||||||
|
wallet account sync-private
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "priv-receiver state (balance should be 200):"
|
||||||
|
wallet account get --account-id "$PRIV_RECEIVER"
|
||||||
|
echo "Keycard path 6 (LEZ) state (balance should be 18800):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (6) Token mint with keycard — definition signed by keycard path 2
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (6) Token mint: keycard def path 2 mints 2000 LEZ to keycard path 6 ==="
|
||||||
|
wallet token mint \
|
||||||
|
--definition "m/44'/60'/0'/0/2" \
|
||||||
|
--holder "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 2000
|
||||||
|
echo "Minted 2000 LEZ to keycard path 6"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 2 (LEZ definition) state (total supply should have increased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/2"
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should be 20500):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (7) Token burn with keycard — holder is keycard path 6
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (7) Token burn: keycard path 6 burns 500 LEZ ==="
|
||||||
|
wallet token burn \
|
||||||
|
--definition "Public/$LEZ_DEF_ID" \
|
||||||
|
--holder "m/44'/60'/0'/0/6" \
|
||||||
|
--amount 500
|
||||||
|
echo "Burned 500 LEZ from keycard path 6"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 2 (LEZ definition) state (total supply should reflect burn):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/2"
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should be 20000):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (8) Create AMM pool for LEZ/LEE — without keycard
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (8) Create AMM pool for LEZ/LEE (without keycard) ==="
|
||||||
|
|
||||||
|
wallet amm new \
|
||||||
|
--user-holding-a amm-lez-fund \
|
||||||
|
--user-holding-b amm-lee-fund \
|
||||||
|
--user-holding-lp amm-lp-fund \
|
||||||
|
--balance-a 10000 \
|
||||||
|
--balance-b 10000
|
||||||
|
echo "AMM pool created for LEZ/LEE"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "amm-lez-fund state (balance should be 0 — contributed to pool):"
|
||||||
|
wallet account get --account-id amm-lez-fund
|
||||||
|
echo "amm-lee-fund state (balance should be 0 — contributed to pool):"
|
||||||
|
wallet account get --account-id amm-lee-fund
|
||||||
|
echo "Initial LP holding state (should hold initial LP tokens):"
|
||||||
|
wallet account get --account-id amm-lp-fund
|
||||||
|
LP_DEF_ID=$(wallet account get --account-id amm-lp-fund | grep -o '"definition_id":"[^"]*"' | awk -F'"' '{print $4}')
|
||||||
|
echo "LP token definition ID: $LP_DEF_ID"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (9) Swap tokens owned by keycard accounts
|
||||||
|
# keycard path 7 (LEE) sells 500 LEE; keycard path 6 (LEZ) receives LEZ
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (9) Swap: keycard path 7 sells 500 LEE, keycard path 6 receives LEZ ==="
|
||||||
|
wallet amm swap-exact-input \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--amount-in 500 \
|
||||||
|
--min-amount-out 1 \
|
||||||
|
--token-definition "$LEE_DEF_ID"
|
||||||
|
echo "Swap LEE → LEZ complete via keycard"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should have increased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "Keycard path 7 (LEE holding) state (balance should have decreased by 500):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/7"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (10) Add liquidity — keycard accounts for holding A (path 6), B (path 7), LP (path 8)
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (10) Add liquidity (keycard path 6=LEZ, path 7=LEE, path 8=LP) ==="
|
||||||
|
wallet amm add-liquidity \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--user-holding-lp "m/44'/60'/0'/0/8" \
|
||||||
|
--max-amount-a 1000 \
|
||||||
|
--max-amount-b 1000 \
|
||||||
|
--min-amount-lp 1
|
||||||
|
echo "Add liquidity complete via keycard"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should have decreased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "Keycard path 7 (LEE holding) state (balance should have decreased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/7"
|
||||||
|
echo "Keycard path 8 (LP holding) state (should have received LP tokens):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/8"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (11) Remove liquidity — keycard accounts for holding A (path 6), B (path 7), LP (path 8)
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (11) Remove liquidity (keycard path 6=LEZ, path 7=LEE, path 8=LP) ==="
|
||||||
|
wallet amm remove-liquidity \
|
||||||
|
--user-holding-a "m/44'/60'/0'/0/6" \
|
||||||
|
--user-holding-b "m/44'/60'/0'/0/7" \
|
||||||
|
--user-holding-lp "m/44'/60'/0'/0/8" \
|
||||||
|
--balance-lp 500 \
|
||||||
|
--min-amount-a 1 \
|
||||||
|
--min-amount-b 1
|
||||||
|
echo "Remove liquidity complete via keycard"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 6 (LEZ holding) state (balance should have increased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/6"
|
||||||
|
echo "Keycard path 7 (LEE holding) state (balance should have increased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/7"
|
||||||
|
echo "Keycard path 8 (LP holding) state (balance should have decreased):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/8"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (12) ATA create — keycard path 9 as owner for LEZ
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (12) ATA create: keycard path 9 as owner, LEZ token ==="
|
||||||
|
ATA_OWNER_ID=$(wallet account id --account-id "m/44'/60'/0'/0/9")
|
||||||
|
echo "ATA owner (keycard path 9): $ATA_OWNER_ID"
|
||||||
|
|
||||||
|
wallet ata create \
|
||||||
|
--owner "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "$LEZ_DEF_ID"
|
||||||
|
echo "ATA created for keycard path 9 / LEZ"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
LEZ_ATA_ID=$(wallet ata address --owner "$ATA_OWNER_ID" --token-definition "$LEZ_DEF_ID")
|
||||||
|
echo "Keycard path 9 LEZ ATA ID: $LEZ_ATA_ID"
|
||||||
|
echo "ATA state (should be initialized with zero balance):"
|
||||||
|
wallet account get --account-id "Public/$LEZ_ATA_ID"
|
||||||
|
|
||||||
|
# Fund the ATA from LEZ supply (path 3) — setup for tests 12 and 13
|
||||||
|
wallet token send \
|
||||||
|
--from "m/44'/60'/0'/0/3" \
|
||||||
|
--to "Public/$LEZ_ATA_ID" \
|
||||||
|
--amount 3000
|
||||||
|
echo "Funded keycard path 9 ATA with 3000 LEZ"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "ATA state after funding (balance should be 3000):"
|
||||||
|
wallet account get --account-id "Public/$LEZ_ATA_ID"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (13) ATA send — keycard path 9's ATA → pub-receiver's ATA
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (13) ATA send: keycard path 9's ATA → pub-receiver's ATA ==="
|
||||||
|
PUB_RECEIVER_ID=$(wallet account id --account-id pub-receiver)
|
||||||
|
wallet ata create \
|
||||||
|
--owner "Public/$PUB_RECEIVER_ID" \
|
||||||
|
--token-definition "$LEZ_DEF_ID"
|
||||||
|
echo "ATA created for pub-receiver / LEZ"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
PUB_RECEIVER_ATA_ID=$(wallet ata address --owner "$PUB_RECEIVER_ID" --token-definition "$LEZ_DEF_ID")
|
||||||
|
echo "pub-receiver LEZ ATA ID: $PUB_RECEIVER_ATA_ID"
|
||||||
|
echo "pub-receiver ATA state (should be initialized with zero balance):"
|
||||||
|
wallet account get --account-id "Public/$PUB_RECEIVER_ATA_ID"
|
||||||
|
|
||||||
|
wallet ata send \
|
||||||
|
--from "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "$LEZ_DEF_ID" \
|
||||||
|
--to "$PUB_RECEIVER_ATA_ID" \
|
||||||
|
--amount 500
|
||||||
|
echo "Sent 500 LEZ: keycard path 9 ATA → pub-receiver ATA"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 9 ATA state (balance should be 2500):"
|
||||||
|
wallet account get --account-id "Public/$LEZ_ATA_ID"
|
||||||
|
echo "pub-receiver ATA state (balance should be 500):"
|
||||||
|
wallet account get --account-id "Public/$PUB_RECEIVER_ATA_ID"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# (14) ATA burn — keycard path 9's ATA burns 200 LEZ
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== (14) ATA burn: keycard path 9's ATA burns 200 LEZ ==="
|
||||||
|
wallet ata burn \
|
||||||
|
--holder "m/44'/60'/0'/0/9" \
|
||||||
|
--token-definition "$LEZ_DEF_ID" \
|
||||||
|
--amount 200
|
||||||
|
echo "Burned 200 LEZ from keycard path 9 ATA"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "Keycard path 9 ATA state (balance should be 2300):"
|
||||||
|
wallet account get --account-id "Public/$LEZ_ATA_ID"
|
||||||
|
echo "LEZ definition state (total supply should reflect burn):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/2"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== All keycard token + AMM + ATA tests finished ==="
|
||||||
@ -298,6 +298,7 @@ async fn claim_funds_from_vault_to_private(
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&program_with_dependencies,
|
&program_with_dependencies,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to submit private vault claim transaction")?;
|
.context("Failed to submit private vault claim transaction")?;
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
//! Token transfer functions.
|
//! Token transfer functions.
|
||||||
|
|
||||||
use std::{ffi::CString, ptr};
|
use std::{
|
||||||
|
ffi::{c_char, CStr, CString},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
use nssa::AccountId;
|
use nssa::AccountId;
|
||||||
use wallet::{
|
use wallet::{
|
||||||
@ -17,6 +20,14 @@ use crate::{
|
|||||||
FfiPrivateAccountKeys,
|
FfiPrivateAccountKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn optional_c_str(ptr: *const c_char) -> Option<String> {
|
||||||
|
if ptr.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let c_str = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
c_str.to_str().ok().map(str::to_owned)
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a public token transfer.
|
/// Send a public token transfer.
|
||||||
///
|
///
|
||||||
/// Transfers tokens from one public account to another on the network.
|
/// Transfers tokens from one public account to another on the network.
|
||||||
@ -140,6 +151,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
|||||||
to_keys: *const FfiPrivateAccountKeys,
|
to_keys: *const FfiPrivateAccountKeys,
|
||||||
to_identifier: *const FfiU128,
|
to_identifier: *const FfiU128,
|
||||||
amount: *const [u8; 16],
|
amount: *const [u8; 16],
|
||||||
|
key_path: *const c_char,
|
||||||
out_result: *mut FfiTransferResult,
|
out_result: *mut FfiTransferResult,
|
||||||
) -> WalletFfiError {
|
) -> WalletFfiError {
|
||||||
let wrapper = match get_wallet(handle) {
|
let wrapper = match get_wallet(handle) {
|
||||||
@ -176,6 +188,10 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
|||||||
};
|
};
|
||||||
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
||||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
let from_mention = optional_c_str(key_path).map_or_else(
|
||||||
|
|| CliAccountMention::Id(AccountIdWithPrivacy::Public(from_id)),
|
||||||
|
CliAccountMention::KeyPath,
|
||||||
|
);
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
@ -185,6 +201,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
|||||||
to_vpk,
|
to_vpk,
|
||||||
to_identifier,
|
to_identifier,
|
||||||
amount,
|
amount,
|
||||||
|
&from_mention,
|
||||||
)) {
|
)) {
|
||||||
Ok((tx_hash, _shared_key)) => {
|
Ok((tx_hash, _shared_key)) => {
|
||||||
let tx_hash = CString::new(tx_hash.to_string())
|
let tx_hash = CString::new(tx_hash.to_string())
|
||||||
@ -262,7 +279,6 @@ pub unsafe extern "C" fn wallet_ffi_transfer_deshielded(
|
|||||||
let from_id = AccountId::new(unsafe { (*from).data });
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
let to_id = AccountId::new(unsafe { (*to).data });
|
let to_id = AccountId::new(unsafe { (*to).data });
|
||||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
match block_on(transfer.send_deshielded_transfer(from_id, to_id, amount)) {
|
match block_on(transfer.send_deshielded_transfer(from_id, to_id, amount)) {
|
||||||
@ -357,7 +373,6 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private(
|
|||||||
};
|
};
|
||||||
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
let to_identifier = u128::from_le_bytes(unsafe { (*to_identifier).data });
|
||||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
match block_on(transfer.send_private_transfer_to_outer_account(
|
match block_on(transfer.send_private_transfer_to_outer_account(
|
||||||
@ -423,6 +438,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned(
|
|||||||
from: *const FfiBytes32,
|
from: *const FfiBytes32,
|
||||||
to: *const FfiBytes32,
|
to: *const FfiBytes32,
|
||||||
amount: *const [u8; 16],
|
amount: *const [u8; 16],
|
||||||
|
key_path: *const c_char,
|
||||||
out_result: *mut FfiTransferResult,
|
out_result: *mut FfiTransferResult,
|
||||||
) -> WalletFfiError {
|
) -> WalletFfiError {
|
||||||
let wrapper = match get_wallet(handle) {
|
let wrapper = match get_wallet(handle) {
|
||||||
@ -446,10 +462,14 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned(
|
|||||||
let from_id = AccountId::new(unsafe { (*from).data });
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
let to_id = AccountId::new(unsafe { (*to).data });
|
let to_id = AccountId::new(unsafe { (*to).data });
|
||||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
let from_mention = optional_c_str(key_path).map_or_else(
|
||||||
|
|| CliAccountMention::Id(AccountIdWithPrivacy::Public(from_id)),
|
||||||
|
CliAccountMention::KeyPath,
|
||||||
|
);
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
match block_on(transfer.send_shielded_transfer(from_id, to_id, amount)) {
|
match block_on(transfer.send_shielded_transfer(from_id, to_id, amount, &from_mention)) {
|
||||||
Ok((tx_hash, _shared_key)) => {
|
Ok((tx_hash, _shared_key)) => {
|
||||||
let tx_hash = CString::new(tx_hash.to_string())
|
let tx_hash = CString::new(tx_hash.to_string())
|
||||||
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);
|
.map_or(ptr::null_mut(), std::ffi::CString::into_raw);
|
||||||
@ -529,7 +549,6 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private_owned(
|
|||||||
let from_id = AccountId::new(unsafe { (*from).data });
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
let to_id = AccountId::new(unsafe { (*to).data });
|
let to_id = AccountId::new(unsafe { (*to).data });
|
||||||
let amount = u128::from_le_bytes(unsafe { *amount });
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
match block_on(transfer.send_private_transfer_to_owned_account(from_id, to_id, amount)) {
|
match block_on(transfer.send_private_transfer_to_owned_account(from_id, to_id, amount)) {
|
||||||
@ -673,7 +692,6 @@ pub unsafe extern "C" fn wallet_ffi_register_private_account(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let account_id = AccountId::new(unsafe { (*account_id).data });
|
let account_id = AccountId::new(unsafe { (*account_id).data });
|
||||||
|
|
||||||
let transfer = NativeTokenTransfer(&wallet);
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
match block_on(transfer.register_account_private(account_id)) {
|
match block_on(transfer.register_account_private(account_id)) {
|
||||||
|
|||||||
@ -787,6 +787,7 @@ enum WalletFfiError wallet_ffi_transfer_shielded(struct WalletHandle *handle,
|
|||||||
const struct FfiPrivateAccountKeys *to_keys,
|
const struct FfiPrivateAccountKeys *to_keys,
|
||||||
const struct FfiU128 *to_identifier,
|
const struct FfiU128 *to_identifier,
|
||||||
const uint8_t (*amount)[16],
|
const uint8_t (*amount)[16],
|
||||||
|
const char *key_path,
|
||||||
struct FfiTransferResult *out_result);
|
struct FfiTransferResult *out_result);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -894,6 +895,7 @@ enum WalletFfiError wallet_ffi_transfer_shielded_owned(struct WalletHandle *hand
|
|||||||
const struct FfiBytes32 *from,
|
const struct FfiBytes32 *from,
|
||||||
const struct FfiBytes32 *to,
|
const struct FfiBytes32 *to,
|
||||||
const uint8_t (*amount)[16],
|
const uint8_t (*amount)[16],
|
||||||
|
const char *key_path,
|
||||||
struct FfiTransferResult *out_result);
|
struct FfiTransferResult *out_result);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -21,7 +21,7 @@ ata_core.workspace = true
|
|||||||
bip39.workspace = true
|
bip39.workspace = true
|
||||||
pyo3.workspace = true
|
pyo3.workspace = true
|
||||||
rpassword = "7"
|
rpassword = "7"
|
||||||
zeroize = "1"
|
zeroize.workspace = true
|
||||||
keycard_wallet.workspace = true
|
keycard_wallet.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@ -46,6 +46,9 @@ optfield = "0.4.0"
|
|||||||
url.workspace = true
|
url.workspace = true
|
||||||
derive_more = { workspace = true, features = ["display"] }
|
derive_more = { workspace = true, features = ["display"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
keycard-debug = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
key_protocol = { workspace = true, features = ["test_utils"] }
|
key_protocol = { workspace = true, features = ["test_utils"] }
|
||||||
|
|||||||
@ -15,6 +15,12 @@ use crate::{
|
|||||||
/// Represents generic chain CLI subcommand.
|
/// Represents generic chain CLI subcommand.
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
pub enum AccountSubcommand {
|
pub enum AccountSubcommand {
|
||||||
|
/// Resolve an account mention and print just the account ID (no privacy prefix).
|
||||||
|
Id {
|
||||||
|
/// Account id with privacy prefix, label, or BIP-32 key path.
|
||||||
|
#[arg(long)]
|
||||||
|
account_id: CliAccountMention,
|
||||||
|
},
|
||||||
/// Get account data.
|
/// Get account data.
|
||||||
Get {
|
Get {
|
||||||
/// Flag to get raw account data.
|
/// Flag to get raw account data.
|
||||||
@ -261,6 +267,14 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
wallet_core: &mut WalletCore,
|
wallet_core: &mut WalletCore,
|
||||||
) -> Result<SubcommandReturnValue> {
|
) -> Result<SubcommandReturnValue> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Id { account_id } => {
|
||||||
|
let resolved = account_id.resolve(wallet_core.storage())?;
|
||||||
|
let id = match resolved {
|
||||||
|
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
|
||||||
|
};
|
||||||
|
println!("{id}");
|
||||||
|
Ok(SubcommandReturnValue::Empty)
|
||||||
|
}
|
||||||
Self::Get {
|
Self::Get {
|
||||||
raw,
|
raw,
|
||||||
keys,
|
keys,
|
||||||
|
|||||||
@ -16,6 +16,20 @@ pub enum KeycardSubcommand {
|
|||||||
Disconnect,
|
Disconnect,
|
||||||
Init,
|
Init,
|
||||||
Load,
|
Load,
|
||||||
|
/// Retrieve the private keys (NSK, VSK) for a given BIP-32 key path.
|
||||||
|
///
|
||||||
|
/// Prints raw key material to stdout — intended for debugging only.
|
||||||
|
/// Requires --reveal to confirm intent.
|
||||||
|
/// Only available when built with the `keycard-debug` feature.
|
||||||
|
#[cfg(feature = "keycard-debug")]
|
||||||
|
GetPrivateKeys {
|
||||||
|
/// BIP-32 derivation path, e.g. `m/44'/60'/0'/0/0`.
|
||||||
|
#[arg(long)]
|
||||||
|
key_path: String,
|
||||||
|
/// Confirm that raw NSK and VSK should be disclosed on stdout.
|
||||||
|
#[arg(long)]
|
||||||
|
reveal: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalletSubcommand for KeycardSubcommand {
|
impl WalletSubcommand for KeycardSubcommand {
|
||||||
@ -26,7 +40,8 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
match self {
|
match self {
|
||||||
Self::Available => {
|
Self::Available => {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py)
|
||||||
|
.expect("`wallet::keycard::available`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::available`: invalid data received for pin");
|
.expect("`wallet::keycard::available`: invalid data received for pin");
|
||||||
@ -47,7 +62,8 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py)
|
||||||
|
.expect("`wallet::keycard::connect`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::connect`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::connect`: invalid keycard wallet provided");
|
||||||
@ -66,7 +82,8 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py)
|
||||||
|
.expect("`wallet::keycard::disconnect`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::disconnect`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::disconnect`: invalid keycard wallet provided");
|
||||||
@ -89,7 +106,8 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py)
|
||||||
|
.expect("`wallet::keycard::init`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::init`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::init`: invalid keycard wallet provided");
|
||||||
@ -111,7 +129,8 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let mnemonic = read_mnemonic()?;
|
let mnemonic = read_mnemonic()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py)
|
||||||
|
.expect("`wallet::keycard::load`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::load`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::load`: invalid keycard wallet provided");
|
||||||
@ -131,6 +150,27 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
|
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "keycard-debug")]
|
||||||
|
Self::GetPrivateKeys { key_path, reveal } => {
|
||||||
|
if !reveal {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: pass --reveal to print NSK and VSK. \
|
||||||
|
Disclosing either key fully compromises the account's privacy."
|
||||||
|
);
|
||||||
|
return Ok(SubcommandReturnValue::Empty);
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: NSK and VSK are being printed to stdout. \
|
||||||
|
Any terminal log, scrollback, or screen recording captures these keys."
|
||||||
|
);
|
||||||
|
let pin = read_pin()?;
|
||||||
|
let (nsk, vsk) =
|
||||||
|
KeycardWallet::get_private_keys_for_path_with_connect(&pin, &key_path)
|
||||||
|
.map_err(anyhow::Error::from)?;
|
||||||
|
println!("NSK: {}", hex::encode(*nsk));
|
||||||
|
println!("VSK: {}", hex::encode(*vsk));
|
||||||
|
Ok(SubcommandReturnValue::Empty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -138,13 +138,23 @@ impl CliAccountMention {
|
|||||||
Self::KeyPath(path) => {
|
Self::KeyPath(path) => {
|
||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
let id_str =
|
let id_str =
|
||||||
keycard_wallet::KeycardWallet::get_account_id_for_path_with_connect(&pin, path)
|
keycard_wallet::KeycardWallet::get_public_account_id_for_path_with_connect(
|
||||||
.map_err(anyhow::Error::from)?;
|
&pin, path,
|
||||||
|
)
|
||||||
|
.map_err(anyhow::Error::from)?;
|
||||||
AccountIdWithPrivacy::from_str(&id_str)
|
AccountIdWithPrivacy::from_str(&id_str)
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid account id from keycard: {e}"))
|
.map_err(|e| anyhow::anyhow!("Invalid account id from keycard: {e}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn key_path(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::KeyPath(path) => Some(path),
|
||||||
|
Self::Id(_) | Self::Label(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for CliAccountMention {
|
impl FromStr for CliAccountMention {
|
||||||
|
|||||||
@ -131,25 +131,31 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
|||||||
balance_a,
|
balance_a,
|
||||||
balance_b,
|
balance_b,
|
||||||
} => {
|
} => {
|
||||||
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
|
let a_id = user_holding_a.resolve(wallet_core.storage())?;
|
||||||
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
|
let b_id = user_holding_b.resolve(wallet_core.storage())?;
|
||||||
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
|
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
|
||||||
match (user_holding_a, user_holding_b, user_holding_lp) {
|
match (a_id, b_id, lp_id) {
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Public(user_holding_a),
|
AccountIdWithPrivacy::Public(a),
|
||||||
AccountIdWithPrivacy::Public(user_holding_b),
|
AccountIdWithPrivacy::Public(b),
|
||||||
AccountIdWithPrivacy::Public(user_holding_lp),
|
AccountIdWithPrivacy::Public(lp),
|
||||||
) => {
|
) => {
|
||||||
Amm(wallet_core)
|
let tx_hash = Amm(wallet_core)
|
||||||
.send_new_definition(
|
.send_new_definition(
|
||||||
user_holding_a,
|
a,
|
||||||
user_holding_b,
|
b,
|
||||||
user_holding_lp,
|
lp,
|
||||||
balance_a,
|
balance_a,
|
||||||
balance_b,
|
balance_b,
|
||||||
|
&user_holding_a,
|
||||||
|
&user_holding_b,
|
||||||
|
&user_holding_lp,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -165,23 +171,25 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
|||||||
min_amount_out,
|
min_amount_out,
|
||||||
token_definition,
|
token_definition,
|
||||||
} => {
|
} => {
|
||||||
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
|
let a_id = user_holding_a.resolve(wallet_core.storage())?;
|
||||||
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
|
let b_id = user_holding_b.resolve(wallet_core.storage())?;
|
||||||
match (user_holding_a, user_holding_b) {
|
match (a_id, b_id) {
|
||||||
(
|
(AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => {
|
||||||
AccountIdWithPrivacy::Public(user_holding_a),
|
let tx_hash = Amm(wallet_core)
|
||||||
AccountIdWithPrivacy::Public(user_holding_b),
|
|
||||||
) => {
|
|
||||||
Amm(wallet_core)
|
|
||||||
.send_swap_exact_input(
|
.send_swap_exact_input(
|
||||||
user_holding_a,
|
a,
|
||||||
user_holding_b,
|
b,
|
||||||
amount_in,
|
amount_in,
|
||||||
min_amount_out,
|
min_amount_out,
|
||||||
token_definition,
|
token_definition,
|
||||||
|
&user_holding_a,
|
||||||
|
&user_holding_b,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -197,23 +205,25 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
|||||||
max_amount_in,
|
max_amount_in,
|
||||||
token_definition,
|
token_definition,
|
||||||
} => {
|
} => {
|
||||||
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
|
let a_id = user_holding_a.resolve(wallet_core.storage())?;
|
||||||
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
|
let b_id = user_holding_b.resolve(wallet_core.storage())?;
|
||||||
match (user_holding_a, user_holding_b) {
|
match (a_id, b_id) {
|
||||||
(
|
(AccountIdWithPrivacy::Public(a), AccountIdWithPrivacy::Public(b)) => {
|
||||||
AccountIdWithPrivacy::Public(user_holding_a),
|
let tx_hash = Amm(wallet_core)
|
||||||
AccountIdWithPrivacy::Public(user_holding_b),
|
|
||||||
) => {
|
|
||||||
Amm(wallet_core)
|
|
||||||
.send_swap_exact_output(
|
.send_swap_exact_output(
|
||||||
user_holding_a,
|
a,
|
||||||
user_holding_b,
|
b,
|
||||||
exact_amount_out,
|
exact_amount_out,
|
||||||
max_amount_in,
|
max_amount_in,
|
||||||
token_definition,
|
token_definition,
|
||||||
|
&user_holding_a,
|
||||||
|
&user_holding_b,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -230,26 +240,32 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
|||||||
max_amount_a,
|
max_amount_a,
|
||||||
max_amount_b,
|
max_amount_b,
|
||||||
} => {
|
} => {
|
||||||
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
|
let a_id = user_holding_a.resolve(wallet_core.storage())?;
|
||||||
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
|
let b_id = user_holding_b.resolve(wallet_core.storage())?;
|
||||||
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
|
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
|
||||||
match (user_holding_a, user_holding_b, user_holding_lp) {
|
match (a_id, b_id, lp_id) {
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Public(user_holding_a),
|
AccountIdWithPrivacy::Public(a),
|
||||||
AccountIdWithPrivacy::Public(user_holding_b),
|
AccountIdWithPrivacy::Public(b),
|
||||||
AccountIdWithPrivacy::Public(user_holding_lp),
|
AccountIdWithPrivacy::Public(lp),
|
||||||
) => {
|
) => {
|
||||||
Amm(wallet_core)
|
let tx_hash = Amm(wallet_core)
|
||||||
.send_add_liquidity(
|
.send_add_liquidity(
|
||||||
user_holding_a,
|
a,
|
||||||
user_holding_b,
|
b,
|
||||||
user_holding_lp,
|
lp,
|
||||||
min_amount_lp,
|
min_amount_lp,
|
||||||
max_amount_a,
|
max_amount_a,
|
||||||
max_amount_b,
|
max_amount_b,
|
||||||
|
&user_holding_a,
|
||||||
|
&user_holding_b,
|
||||||
|
&user_holding_lp,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -266,26 +282,30 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
|
|||||||
min_amount_a,
|
min_amount_a,
|
||||||
min_amount_b,
|
min_amount_b,
|
||||||
} => {
|
} => {
|
||||||
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
|
let a_id = user_holding_a.resolve(wallet_core.storage())?;
|
||||||
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
|
let b_id = user_holding_b.resolve(wallet_core.storage())?;
|
||||||
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
|
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
|
||||||
match (user_holding_a, user_holding_b, user_holding_lp) {
|
match (a_id, b_id, lp_id) {
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Public(user_holding_a),
|
AccountIdWithPrivacy::Public(a),
|
||||||
AccountIdWithPrivacy::Public(user_holding_b),
|
AccountIdWithPrivacy::Public(b),
|
||||||
AccountIdWithPrivacy::Public(user_holding_lp),
|
AccountIdWithPrivacy::Public(lp),
|
||||||
) => {
|
) => {
|
||||||
Amm(wallet_core)
|
let tx_hash = Amm(wallet_core)
|
||||||
.send_remove_liquidity(
|
.send_remove_liquidity(
|
||||||
user_holding_a,
|
a,
|
||||||
user_holding_b,
|
b,
|
||||||
user_holding_lp,
|
lp,
|
||||||
balance_lp,
|
balance_lp,
|
||||||
min_amount_a,
|
min_amount_a,
|
||||||
min_amount_b,
|
min_amount_b,
|
||||||
|
&user_holding_lp,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@ -91,14 +91,18 @@ impl WalletSubcommand for AtaSubcommand {
|
|||||||
owner,
|
owner,
|
||||||
token_definition,
|
token_definition,
|
||||||
} => {
|
} => {
|
||||||
let owner = owner.resolve(wallet_core.storage())?;
|
let owner_resolved = owner.resolve(wallet_core.storage())?;
|
||||||
let definition_id = token_definition;
|
let definition_id = token_definition;
|
||||||
|
|
||||||
match owner {
|
match owner_resolved {
|
||||||
AccountIdWithPrivacy::Public(owner_id) => {
|
AccountIdWithPrivacy::Public(owner_id) => {
|
||||||
Ata(wallet_core)
|
let tx_hash = Ata(wallet_core)
|
||||||
.send_create(owner_id, definition_id)
|
.send_create(owner_id, definition_id, &owner)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
AccountIdWithPrivacy::Private(owner_id) => {
|
AccountIdWithPrivacy::Private(owner_id) => {
|
||||||
@ -127,15 +131,19 @@ impl WalletSubcommand for AtaSubcommand {
|
|||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
let from = from.resolve(wallet_core.storage())?;
|
let from_resolved = from.resolve(wallet_core.storage())?;
|
||||||
let definition_id = token_definition;
|
let definition_id = token_definition;
|
||||||
let to_id = to;
|
let to_id = to;
|
||||||
|
|
||||||
match from {
|
match from_resolved {
|
||||||
AccountIdWithPrivacy::Public(from_id) => {
|
AccountIdWithPrivacy::Public(from_id) => {
|
||||||
Ata(wallet_core)
|
let tx_hash = Ata(wallet_core)
|
||||||
.send_transfer(from_id, definition_id, to_id, amount)
|
.send_transfer(from_id, definition_id, to_id, amount, &from)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
AccountIdWithPrivacy::Private(from_id) => {
|
AccountIdWithPrivacy::Private(from_id) => {
|
||||||
@ -163,14 +171,18 @@ impl WalletSubcommand for AtaSubcommand {
|
|||||||
token_definition,
|
token_definition,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
let holder = holder.resolve(wallet_core.storage())?;
|
let holder_resolved = holder.resolve(wallet_core.storage())?;
|
||||||
let definition_id = token_definition;
|
let definition_id = token_definition;
|
||||||
|
|
||||||
match holder {
|
match holder_resolved {
|
||||||
AccountIdWithPrivacy::Public(holder_id) => {
|
AccountIdWithPrivacy::Public(holder_id) => {
|
||||||
Ata(wallet_core)
|
let tx_hash = Ata(wallet_core)
|
||||||
.send_burn(holder_id, definition_id, amount)
|
.send_burn(holder_id, definition_id, amount, &holder)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
AccountIdWithPrivacy::Private(holder_id) => {
|
AccountIdWithPrivacy::Private(holder_id) => {
|
||||||
|
|||||||
@ -151,6 +151,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
|||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
|
from_mention: from_account,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -175,6 +176,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
|
|||||||
to_vpk,
|
to_vpk,
|
||||||
to_identifier,
|
to_identifier,
|
||||||
amount,
|
amount,
|
||||||
|
from_mention: from_account,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -247,6 +249,8 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
|
|||||||
/// amount - amount of balance to move.
|
/// amount - amount of balance to move.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
#[arg(skip)]
|
||||||
|
from_mention: CliAccountMention,
|
||||||
},
|
},
|
||||||
/// Send native token transfer from `from` to `to` for `amount`.
|
/// Send native token transfer from `from` to `to` for `amount`.
|
||||||
///
|
///
|
||||||
@ -267,6 +271,8 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
|
|||||||
/// amount - amount of balance to move.
|
/// amount - amount of balance to move.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
#[arg(skip)]
|
||||||
|
from_mention: CliAccountMention,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,9 +399,14 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
|||||||
wallet_core: &mut WalletCore,
|
wallet_core: &mut WalletCore,
|
||||||
) -> Result<SubcommandReturnValue> {
|
) -> Result<SubcommandReturnValue> {
|
||||||
match self {
|
match self {
|
||||||
Self::ShieldedOwned { from, to, amount } => {
|
Self::ShieldedOwned {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
amount,
|
||||||
|
from_mention,
|
||||||
|
} => {
|
||||||
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
|
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
|
||||||
.send_shielded_transfer(from, to, amount)
|
.send_shielded_transfer(from, to, amount, &from_mention)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("Transaction hash is {tx_hash}");
|
println!("Transaction hash is {tx_hash}");
|
||||||
@ -421,6 +432,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
|||||||
to_vpk,
|
to_vpk,
|
||||||
to_identifier,
|
to_identifier,
|
||||||
amount,
|
amount,
|
||||||
|
from_mention,
|
||||||
} => {
|
} => {
|
||||||
let to_npk_res = hex::decode(to_npk)?;
|
let to_npk_res = hex::decode(to_npk)?;
|
||||||
let mut to_npk = [0; 32];
|
let mut to_npk = [0; 32];
|
||||||
@ -440,6 +452,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
|
|||||||
to_vpk,
|
to_vpk,
|
||||||
to_identifier.unwrap_or_else(rand::random),
|
to_identifier.unwrap_or_else(rand::random),
|
||||||
amount,
|
amount,
|
||||||
|
&from_mention,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@ -114,20 +114,21 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
name,
|
name,
|
||||||
total_supply,
|
total_supply,
|
||||||
} => {
|
} => {
|
||||||
|
let def_mention = definition_account_id.clone();
|
||||||
|
let sup_mention = supply_account_id.clone();
|
||||||
let definition_account_id = definition_account_id.resolve(wallet_core.storage())?;
|
let definition_account_id = definition_account_id.resolve(wallet_core.storage())?;
|
||||||
let supply_account_id = supply_account_id.resolve(wallet_core.storage())?;
|
let supply_account_id = supply_account_id.resolve(wallet_core.storage())?;
|
||||||
let underlying_subcommand = match (definition_account_id, supply_account_id) {
|
let underlying_subcommand = match (definition_account_id, supply_account_id) {
|
||||||
(
|
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
|
||||||
AccountIdWithPrivacy::Public(definition_account_id),
|
TokenProgramSubcommand::Create(
|
||||||
AccountIdWithPrivacy::Public(supply_account_id),
|
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
|
||||||
) => TokenProgramSubcommand::Create(
|
definition_account_id: def_mention,
|
||||||
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
|
supply_account_id: sup_mention,
|
||||||
definition_account_id,
|
name,
|
||||||
supply_account_id,
|
total_supply,
|
||||||
name,
|
},
|
||||||
total_supply,
|
)
|
||||||
},
|
}
|
||||||
),
|
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Public(definition_account_id),
|
AccountIdWithPrivacy::Public(definition_account_id),
|
||||||
AccountIdWithPrivacy::Private(supply_account_id),
|
AccountIdWithPrivacy::Private(supply_account_id),
|
||||||
@ -173,6 +174,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
to_identifier,
|
to_identifier,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
|
let from_mention = from.clone();
|
||||||
|
let to_mention = to.clone();
|
||||||
let from = from.resolve(wallet_core.storage())?;
|
let from = from.resolve(wallet_core.storage())?;
|
||||||
let to = to
|
let to = to
|
||||||
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
||||||
@ -192,11 +195,11 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
anyhow::bail!("List of public keys is uncomplete");
|
anyhow::bail!("List of public keys is uncomplete");
|
||||||
}
|
}
|
||||||
(Some(to), None, None) => match (from, to) {
|
(Some(to), None, None) => match (from, to) {
|
||||||
(AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Public(to)) => {
|
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
|
||||||
TokenProgramSubcommand::Public(
|
TokenProgramSubcommand::Public(
|
||||||
TokenProgramSubcommandPublic::TransferToken {
|
TokenProgramSubcommandPublic::TransferToken {
|
||||||
sender_account_id: from,
|
sender_account_id: from_mention,
|
||||||
recipient_account_id: to,
|
recipient_account_id: to_mention.expect("`wallet::cli::programs::token::Send`: Invalid to_mention account provided"),
|
||||||
balance_to_move: amount,
|
balance_to_move: amount,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -226,6 +229,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
sender_account_id: from,
|
sender_account_id: from,
|
||||||
recipient_account_id: to,
|
recipient_account_id: to,
|
||||||
balance_to_move: amount,
|
balance_to_move: amount,
|
||||||
|
sender_mention: from_mention,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -247,6 +251,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
recipient_vpk: to_vpk,
|
recipient_vpk: to_vpk,
|
||||||
recipient_identifier: to_identifier,
|
recipient_identifier: to_identifier,
|
||||||
balance_to_move: amount,
|
balance_to_move: amount,
|
||||||
|
sender_mention: from_mention,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -259,17 +264,17 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
holder,
|
holder,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
|
let holder_mention = holder.clone();
|
||||||
let definition = definition.resolve(wallet_core.storage())?;
|
let definition = definition.resolve(wallet_core.storage())?;
|
||||||
let holder = holder.resolve(wallet_core.storage())?;
|
let holder = holder.resolve(wallet_core.storage())?;
|
||||||
let underlying_subcommand = match (definition, holder) {
|
let underlying_subcommand = match (definition, holder) {
|
||||||
(
|
(AccountIdWithPrivacy::Public(definition), AccountIdWithPrivacy::Public(_)) => {
|
||||||
AccountIdWithPrivacy::Public(definition),
|
TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::BurnToken {
|
||||||
AccountIdWithPrivacy::Public(holder),
|
definition_account_id: definition,
|
||||||
) => TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::BurnToken {
|
holder_account_id: holder_mention,
|
||||||
definition_account_id: definition,
|
amount,
|
||||||
holder_account_id: holder,
|
})
|
||||||
amount,
|
}
|
||||||
}),
|
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Private(definition),
|
AccountIdWithPrivacy::Private(definition),
|
||||||
AccountIdWithPrivacy::Private(holder),
|
AccountIdWithPrivacy::Private(holder),
|
||||||
@ -312,6 +317,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
holder_identifier,
|
holder_identifier,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
|
let def_mention = definition.clone();
|
||||||
|
let holder_mention = holder.clone();
|
||||||
let definition = definition.resolve(wallet_core.storage())?;
|
let definition = definition.resolve(wallet_core.storage())?;
|
||||||
let holder = holder
|
let holder = holder
|
||||||
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
||||||
@ -331,16 +338,15 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
anyhow::bail!("List of public keys is uncomplete");
|
anyhow::bail!("List of public keys is uncomplete");
|
||||||
}
|
}
|
||||||
(Some(holder), None, None) => match (definition, holder) {
|
(Some(holder), None, None) => match (definition, holder) {
|
||||||
(
|
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
|
||||||
AccountIdWithPrivacy::Public(definition),
|
TokenProgramSubcommand::Public(
|
||||||
AccountIdWithPrivacy::Public(holder),
|
TokenProgramSubcommandPublic::MintToken {
|
||||||
) => TokenProgramSubcommand::Public(
|
definition_account_id: def_mention,
|
||||||
TokenProgramSubcommandPublic::MintToken {
|
holder_account_id: holder_mention.expect("`wallet::cli::programs::token::Mint`: Invalid holder_mention account provided"),
|
||||||
definition_account_id: definition,
|
amount,
|
||||||
holder_account_id: holder,
|
},
|
||||||
amount,
|
)
|
||||||
},
|
}
|
||||||
),
|
|
||||||
(
|
(
|
||||||
AccountIdWithPrivacy::Private(definition),
|
AccountIdWithPrivacy::Private(definition),
|
||||||
AccountIdWithPrivacy::Private(holder),
|
AccountIdWithPrivacy::Private(holder),
|
||||||
@ -430,9 +436,9 @@ pub enum TokenProgramSubcommandPublic {
|
|||||||
// Transfer tokens using the token program
|
// Transfer tokens using the token program
|
||||||
TransferToken {
|
TransferToken {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
sender_account_id: AccountId,
|
sender_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
recipient_account_id: AccountId,
|
recipient_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
},
|
},
|
||||||
@ -441,16 +447,16 @@ pub enum TokenProgramSubcommandPublic {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
definition_account_id: AccountId,
|
definition_account_id: AccountId,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
holder_account_id: AccountId,
|
holder_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
amount: u128,
|
amount: u128,
|
||||||
},
|
},
|
||||||
// Transfer tokens using the token program
|
// Transfer tokens using the token program
|
||||||
MintToken {
|
MintToken {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
definition_account_id: AccountId,
|
definition_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
holder_account_id: AccountId,
|
holder_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
amount: u128,
|
amount: u128,
|
||||||
},
|
},
|
||||||
@ -561,6 +567,8 @@ pub enum TokenProgramSubcommandShielded {
|
|||||||
recipient_account_id: AccountId,
|
recipient_account_id: AccountId,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
|
#[arg(skip)]
|
||||||
|
sender_mention: CliAccountMention,
|
||||||
},
|
},
|
||||||
// Transfer tokens using the token program
|
// Transfer tokens using the token program
|
||||||
TransferTokenShieldedForeign {
|
TransferTokenShieldedForeign {
|
||||||
@ -577,6 +585,8 @@ pub enum TokenProgramSubcommandShielded {
|
|||||||
recipient_identifier: Option<u128>,
|
recipient_identifier: Option<u128>,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
|
#[arg(skip)]
|
||||||
|
sender_mention: CliAccountMention,
|
||||||
},
|
},
|
||||||
// Burn tokens using the token program
|
// Burn tokens using the token program
|
||||||
BurnTokenShielded {
|
BurnTokenShielded {
|
||||||
@ -620,9 +630,9 @@ pub enum CreateNewTokenProgramSubcommand {
|
|||||||
/// Definition - public, supply - public.
|
/// Definition - public, supply - public.
|
||||||
NewPublicDefPublicSupp {
|
NewPublicDefPublicSupp {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
definition_account_id: AccountId,
|
definition_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
supply_account_id: AccountId,
|
supply_account_id: CliAccountMention,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
name: String,
|
name: String,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
@ -680,13 +690,28 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
|
|||||||
recipient_account_id,
|
recipient_account_id,
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
} => {
|
} => {
|
||||||
Token(wallet_core)
|
let sender = sender_account_id.resolve(wallet_core.storage())?;
|
||||||
|
let recipient = recipient_account_id.resolve(wallet_core.storage())?;
|
||||||
|
let (
|
||||||
|
AccountIdWithPrivacy::Public(sender_id),
|
||||||
|
AccountIdWithPrivacy::Public(recipient_id),
|
||||||
|
) = (sender, recipient)
|
||||||
|
else {
|
||||||
|
anyhow::bail!("Only public accounts supported for token transfer");
|
||||||
|
};
|
||||||
|
let tx_hash = Token(wallet_core)
|
||||||
.send_transfer_transaction(
|
.send_transfer_transaction(
|
||||||
sender_account_id,
|
sender_id,
|
||||||
recipient_account_id,
|
recipient_id,
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
|
&sender_account_id,
|
||||||
|
&recipient_account_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
Self::BurnToken {
|
Self::BurnToken {
|
||||||
@ -694,9 +719,22 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
|
|||||||
holder_account_id,
|
holder_account_id,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
Token(wallet_core)
|
let holder = holder_account_id.resolve(wallet_core.storage())?;
|
||||||
.send_burn_transaction(definition_account_id, holder_account_id, amount)
|
let AccountIdWithPrivacy::Public(holder_id) = holder else {
|
||||||
|
anyhow::bail!("Only public holder account supported for token burn");
|
||||||
|
};
|
||||||
|
let tx_hash = Token(wallet_core)
|
||||||
|
.send_burn_transaction(
|
||||||
|
definition_account_id,
|
||||||
|
holder_id,
|
||||||
|
amount,
|
||||||
|
&holder_account_id,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
Self::MintToken {
|
Self::MintToken {
|
||||||
@ -704,9 +742,26 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
|
|||||||
holder_account_id,
|
holder_account_id,
|
||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
Token(wallet_core)
|
let definition = definition_account_id.resolve(wallet_core.storage())?;
|
||||||
.send_mint_transaction(definition_account_id, holder_account_id, amount)
|
let holder = holder_account_id.resolve(wallet_core.storage())?;
|
||||||
|
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(holder_id)) =
|
||||||
|
(definition, holder)
|
||||||
|
else {
|
||||||
|
anyhow::bail!("Only public accounts supported for token mint");
|
||||||
|
};
|
||||||
|
let tx_hash = Token(wallet_core)
|
||||||
|
.send_mint_transaction(
|
||||||
|
def_id,
|
||||||
|
holder_id,
|
||||||
|
amount,
|
||||||
|
&definition_account_id,
|
||||||
|
&holder_account_id,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1026,6 +1081,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
|||||||
recipient_vpk,
|
recipient_vpk,
|
||||||
recipient_identifier,
|
recipient_identifier,
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
|
sender_mention,
|
||||||
} => {
|
} => {
|
||||||
let recipient_npk_res = hex::decode(recipient_npk)?;
|
let recipient_npk_res = hex::decode(recipient_npk)?;
|
||||||
let mut recipient_npk = [0; 32];
|
let mut recipient_npk = [0; 32];
|
||||||
@ -1046,6 +1102,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
|||||||
recipient_vpk,
|
recipient_vpk,
|
||||||
recipient_identifier.unwrap_or_else(rand::random),
|
recipient_identifier.unwrap_or_else(rand::random),
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
|
&sender_mention,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -1065,12 +1122,14 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
|
|||||||
sender_account_id,
|
sender_account_id,
|
||||||
recipient_account_id,
|
recipient_account_id,
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
|
sender_mention,
|
||||||
} => {
|
} => {
|
||||||
let (tx_hash, secret_recipient) = Token(wallet_core)
|
let (tx_hash, secret_recipient) = Token(wallet_core)
|
||||||
.send_transfer_transaction_shielded_owned_account(
|
.send_transfer_transaction_shielded_owned_account(
|
||||||
sender_account_id,
|
sender_account_id,
|
||||||
recipient_account_id,
|
recipient_account_id,
|
||||||
balance_to_move,
|
balance_to_move,
|
||||||
|
&sender_mention,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -1307,14 +1366,27 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
|
|||||||
name,
|
name,
|
||||||
total_supply,
|
total_supply,
|
||||||
} => {
|
} => {
|
||||||
Token(wallet_core)
|
let definition = definition_account_id.resolve(wallet_core.storage())?;
|
||||||
|
let supply = supply_account_id.resolve(wallet_core.storage())?;
|
||||||
|
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(sup_id)) =
|
||||||
|
(definition, supply)
|
||||||
|
else {
|
||||||
|
anyhow::bail!("Only public accounts supported for new token definition");
|
||||||
|
};
|
||||||
|
let tx_hash = Token(wallet_core)
|
||||||
.send_new_definition(
|
.send_new_definition(
|
||||||
definition_account_id,
|
def_id,
|
||||||
supply_account_id,
|
sup_id,
|
||||||
name,
|
name,
|
||||||
total_supply,
|
total_supply,
|
||||||
|
&definition_account_id,
|
||||||
|
&supply_account_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
println!("Transaction hash is {tx_hash}");
|
||||||
|
let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?;
|
||||||
|
println!("Transaction data is {transfer_tx:?}");
|
||||||
|
wallet_core.store_persistent_data()?;
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#![expect(
|
#![expect(
|
||||||
clippy::print_stdout,
|
clippy::print_stdout,
|
||||||
|
clippy::print_stderr,
|
||||||
reason = "This is a CLI application, printing to stdout and stderr is expected and convenient"
|
reason = "This is a CLI application, printing to stdout and stderr is expected and convenient"
|
||||||
)]
|
)]
|
||||||
#![expect(
|
#![expect(
|
||||||
@ -14,15 +15,20 @@ use bip39::Mnemonic;
|
|||||||
use common::{HashType, transaction::NSSATransaction};
|
use common::{HashType, transaction::NSSATransaction};
|
||||||
use config::WalletConfig;
|
use config::WalletConfig;
|
||||||
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
|
||||||
|
use keycard_wallet::KeycardWallet;
|
||||||
use log::info;
|
use log::info;
|
||||||
use nssa::{
|
use nssa::{
|
||||||
Account, AccountId, PrivacyPreservingTransaction,
|
Account, AccountId, PrivacyPreservingTransaction, PublicKey, PublicTransaction, Signature,
|
||||||
privacy_preserving_transaction::{
|
privacy_preserving_transaction::{
|
||||||
circuit::ProgramWithDependencies, message::EncryptedAccountData,
|
circuit::ProgramWithDependencies, message::EncryptedAccountData,
|
||||||
},
|
},
|
||||||
|
program::Program,
|
||||||
|
public_transaction::WitnessSet as PublicWitnessSet,
|
||||||
};
|
};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData,
|
Commitment, MembershipProof, SharedSecretKey,
|
||||||
|
account::{AccountWithMetadata, Nonce},
|
||||||
|
program::InstructionData,
|
||||||
};
|
};
|
||||||
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
pub use privacy_preserving_tx::PrivacyPreservingAccount;
|
||||||
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
|
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
|
||||||
@ -31,8 +37,10 @@ use tokio::io::AsyncWriteExt as _;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::{AccountIdWithPrivacy, Label},
|
account::{AccountIdWithPrivacy, Label},
|
||||||
|
cli::CliAccountMention,
|
||||||
config::WalletConfigOverrides,
|
config::WalletConfigOverrides,
|
||||||
poller::TxPoller,
|
poller::TxPoller,
|
||||||
|
signing::SigningGroup,
|
||||||
storage::key_chain::SharedAccountEntry,
|
storage::key_chain::SharedAccountEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,6 +88,20 @@ pub enum ExecutionFailureKind {
|
|||||||
KeycardError(#[from] pyo3::PyErr),
|
KeycardError(#[from] pyo3::PyErr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExecutionFailureKind {
|
||||||
|
/// Convert an [`anyhow::Error`] (e.g. from [`SigningGroup`]) into a keycard error.
|
||||||
|
#[must_use]
|
||||||
|
#[expect(
|
||||||
|
clippy::needless_pass_by_value,
|
||||||
|
reason = "used as a method reference in map_err"
|
||||||
|
)]
|
||||||
|
pub fn from_anyhow(e: anyhow::Error) -> Self {
|
||||||
|
Self::KeycardError(pyo3::PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")]
|
#[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")]
|
||||||
pub struct WalletCore {
|
pub struct WalletCore {
|
||||||
config_path: PathBuf,
|
config_path: PathBuf,
|
||||||
@ -542,15 +564,76 @@ impl WalletCore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a public transaction, fetching nonces automatically from
|
||||||
|
/// [`SigningGroup::signing_ids`].
|
||||||
|
pub async fn send_public_tx<T: serde::Serialize>(
|
||||||
|
&self,
|
||||||
|
program: &Program,
|
||||||
|
account_ids: Vec<AccountId>,
|
||||||
|
instruction: T,
|
||||||
|
groups: SigningGroup,
|
||||||
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
|
let nonces = self
|
||||||
|
.get_accounts_nonces(groups.signing_ids())
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
self.send_public_tx_with_nonces(program, account_ids, nonces, instruction, groups)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a public transaction with caller-supplied nonces.
|
||||||
|
///
|
||||||
|
/// Use this when the caller needs to assemble or augment nonces before submission
|
||||||
|
/// (e.g. injecting a keycard account nonce that was fetched separately).
|
||||||
|
pub async fn send_public_tx_with_nonces<T: serde::Serialize>(
|
||||||
|
&self,
|
||||||
|
program: &Program,
|
||||||
|
account_ids: Vec<AccountId>,
|
||||||
|
nonces: Vec<Nonce>,
|
||||||
|
instruction: T,
|
||||||
|
groups: SigningGroup,
|
||||||
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
|
program.id(),
|
||||||
|
account_ids,
|
||||||
|
nonces,
|
||||||
|
instruction,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pin = if groups.needs_pin() {
|
||||||
|
crate::helperfunctions::read_pin()
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?
|
||||||
|
.as_str()
|
||||||
|
.to_owned()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sigs = groups
|
||||||
|
.sign_all(&message.hash(), &pin)
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
let tx = PublicTransaction::new(message, PublicWitnessSet::from_raw_parts(sigs));
|
||||||
|
Ok(self
|
||||||
|
.sequencer_client
|
||||||
|
.send_transaction(NSSATransaction::Public(tx))
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_privacy_preserving_tx(
|
pub async fn send_privacy_preserving_tx(
|
||||||
&self,
|
&self,
|
||||||
accounts: Vec<PrivacyPreservingAccount>,
|
accounts: Vec<PrivacyPreservingAccount>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
program: &ProgramWithDependencies,
|
program: &ProgramWithDependencies,
|
||||||
|
mention: Option<&CliAccountMention>,
|
||||||
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||||
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
self.send_privacy_preserving_tx_with_pre_check(
|
||||||
Ok(())
|
accounts,
|
||||||
})
|
instruction_data,
|
||||||
|
program,
|
||||||
|
|_| Ok(()),
|
||||||
|
mention,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,10 +643,67 @@ impl WalletCore {
|
|||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
program: &ProgramWithDependencies,
|
program: &ProgramWithDependencies,
|
||||||
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
||||||
|
mention: Option<&CliAccountMention>,
|
||||||
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||||
let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?;
|
let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?;
|
||||||
|
|
||||||
let pre_states = acc_manager.pre_states();
|
let mut pre_states = acc_manager.pre_states();
|
||||||
|
|
||||||
|
let (keycard_account, keycard_pin, keycard_path) = if let Some(key_path_str) =
|
||||||
|
mention.and_then(CliAccountMention::key_path)
|
||||||
|
{
|
||||||
|
let pin = crate::helperfunctions::read_pin().map_err(|e| {
|
||||||
|
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
|
||||||
|
pyo3::exceptions::PyRuntimeError,
|
||||||
|
_,
|
||||||
|
>(e.to_string()))
|
||||||
|
})?;
|
||||||
|
let account_id_str =
|
||||||
|
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
|
||||||
|
let account_id: AccountId = match account_id_str
|
||||||
|
.parse::<AccountIdWithPrivacy>()
|
||||||
|
.expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: invalid account id parsed")
|
||||||
|
{
|
||||||
|
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
|
||||||
|
};
|
||||||
|
let account = self
|
||||||
|
.get_account_public(account_id)
|
||||||
|
.await
|
||||||
|
.expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: unable to retrieve public account");
|
||||||
|
let pin_str = pin.as_str().to_owned();
|
||||||
|
(
|
||||||
|
Some(AccountWithMetadata {
|
||||||
|
account,
|
||||||
|
is_authorized: true,
|
||||||
|
account_id,
|
||||||
|
}),
|
||||||
|
Some(pin_str),
|
||||||
|
Some(key_path_str.to_owned()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut nonces: Vec<Nonce> = acc_manager.public_account_nonces().into_iter().collect();
|
||||||
|
|
||||||
|
let mut account_ids: Vec<AccountId> = acc_manager.public_account_ids();
|
||||||
|
|
||||||
|
if let Some(acc) = keycard_account.as_ref() {
|
||||||
|
if acc_manager.public_account_ids().contains(&acc.account_id) {
|
||||||
|
if let Some(pre) = pre_states
|
||||||
|
.iter_mut()
|
||||||
|
.find(|p| p.account_id == acc.account_id)
|
||||||
|
{
|
||||||
|
pre.is_authorized = true;
|
||||||
|
}
|
||||||
|
nonces.push(acc.account.nonce);
|
||||||
|
} else {
|
||||||
|
nonces.push(acc.account.nonce);
|
||||||
|
account_ids.push(acc.account_id);
|
||||||
|
pre_states.push(acc.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tx_pre_check(
|
tx_pre_check(
|
||||||
&pre_states
|
&pre_states
|
||||||
.iter()
|
.iter()
|
||||||
@ -582,8 +722,8 @@ impl WalletCore {
|
|||||||
|
|
||||||
let message =
|
let message =
|
||||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||||
acc_manager.public_account_ids(),
|
account_ids,
|
||||||
Vec::from_iter(acc_manager.public_account_nonces()),
|
nonces,
|
||||||
private_account_keys
|
private_account_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|keys| (keys.npk, keys.vpk.clone(), keys.epk.clone()))
|
.map(|keys| (keys.npk, keys.vpk.clone(), keys.epk.clone()))
|
||||||
@ -593,11 +733,38 @@ impl WalletCore {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set =
|
let witness_set =
|
||||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
if let (Some(pin), Some(path)) = (keycard_pin.as_deref(), keycard_path.as_deref()) {
|
||||||
&message,
|
let hash = message.hash();
|
||||||
proof,
|
let local_auth = acc_manager.public_account_auth();
|
||||||
&acc_manager.public_account_auth(),
|
let mut sigs: Vec<(Signature, PublicKey)> = local_auth
|
||||||
);
|
.iter()
|
||||||
|
.map(|&key| {
|
||||||
|
(
|
||||||
|
Signature::new(key, &hash),
|
||||||
|
PublicKey::new_from_private_key(key),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let keycard_sig = pyo3::Python::with_gil(|py| {
|
||||||
|
let mut ctx = crate::signing::KeycardSessionContext::new(pin);
|
||||||
|
let result = ctx
|
||||||
|
.get_or_connect(py)
|
||||||
|
.and_then(|w| w.sign_message_for_path(py, path, &hash));
|
||||||
|
ctx.close(py);
|
||||||
|
result
|
||||||
|
})
|
||||||
|
.map_err(ExecutionFailureKind::KeycardError)?;
|
||||||
|
sigs.push(keycard_sig);
|
||||||
|
nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts(
|
||||||
|
sigs, proof,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||||
|
&message,
|
||||||
|
proof,
|
||||||
|
&acc_manager.public_account_auth(),
|
||||||
|
)
|
||||||
|
};
|
||||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let shared_secrets: Vec<_> = private_account_keys
|
let shared_secrets: Vec<_> = private_account_keys
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
use amm_core::{compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda};
|
use amm_core::{compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda};
|
||||||
use common::{HashType, transaction::NSSATransaction};
|
use common::HashType;
|
||||||
use nssa::{AccountId, program::Program};
|
use nssa::{AccountId, program::Program};
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
|
||||||
use token_core::TokenHolding;
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
use crate::{ExecutionFailureKind, WalletCore};
|
use crate::{ExecutionFailureKind, WalletCore, cli::CliAccountMention, signing::SigningGroup};
|
||||||
pub struct Amm<'wallet>(pub &'wallet WalletCore);
|
pub struct Amm<'wallet>(pub &'wallet WalletCore);
|
||||||
|
|
||||||
impl Amm<'_> {
|
impl Amm<'_> {
|
||||||
|
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
|
||||||
pub async fn send_new_definition(
|
pub async fn send_new_definition(
|
||||||
&self,
|
&self,
|
||||||
user_holding_a: AccountId,
|
user_holding_a: AccountId,
|
||||||
@ -15,6 +15,9 @@ impl Amm<'_> {
|
|||||||
user_holding_lp: AccountId,
|
user_holding_lp: AccountId,
|
||||||
balance_a: u128,
|
balance_a: u128,
|
||||||
balance_b: u128,
|
balance_b: u128,
|
||||||
|
a_mention: &CliAccountMention,
|
||||||
|
b_mention: &CliAccountMention,
|
||||||
|
lp_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let program = Program::amm();
|
let program = Program::amm();
|
||||||
let amm_program_id = Program::amm().id();
|
let amm_program_id = Program::amm().id();
|
||||||
@ -58,69 +61,19 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
.add_required(a_mention, user_holding_a, self.0)
|
||||||
|
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
||||||
|
.and_then(|()| groups.add_optional(lp_mention, user_holding_lp, self.0))
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let mut private_keys = Vec::new();
|
|
||||||
|
|
||||||
let signing_key_a = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_a)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
private_keys.push(signing_key_a);
|
|
||||||
|
|
||||||
let signing_key_b = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_b)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
private_keys.push(signing_key_b);
|
|
||||||
|
|
||||||
if let Some(signing_key_lp) = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_lp)
|
|
||||||
{
|
|
||||||
private_keys.push(signing_key_lp);
|
|
||||||
let lp_nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![user_holding_lp])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
nonces.extend(lp_nonces);
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"Liquidity pool tokens receiver's account ({user_holding_lp}) private key not found in wallet. Proceeding with only liquidity provider's keys."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program.id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
|
||||||
pub async fn send_swap_exact_input(
|
pub async fn send_swap_exact_input(
|
||||||
&self,
|
&self,
|
||||||
user_holding_a: AccountId,
|
user_holding_a: AccountId,
|
||||||
@ -128,6 +81,8 @@ impl Amm<'_> {
|
|||||||
swap_amount_in: u128,
|
swap_amount_in: u128,
|
||||||
min_amount_out: u128,
|
min_amount_out: u128,
|
||||||
token_definition_id_in: AccountId,
|
token_definition_id_in: AccountId,
|
||||||
|
a_mention: &CliAccountMention,
|
||||||
|
b_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let instruction = amm_core::Instruction::SwapExactInput {
|
let instruction = amm_core::Instruction::SwapExactInput {
|
||||||
swap_amount_in,
|
swap_amount_in,
|
||||||
@ -168,49 +123,26 @@ impl Amm<'_> {
|
|||||||
user_holding_b,
|
user_holding_b,
|
||||||
];
|
];
|
||||||
|
|
||||||
let account_id_auth = if definition_token_a_id == token_definition_id_in {
|
let (account_id_auth, seller_mention) = if definition_token_a_id == token_definition_id_in {
|
||||||
user_holding_a
|
(user_holding_a, a_mention)
|
||||||
} else if definition_token_b_id == token_definition_id_in {
|
} else if definition_token_b_id == token_definition_id_in {
|
||||||
user_holding_b
|
(user_holding_b, b_mention)
|
||||||
} else {
|
} else {
|
||||||
return Err(ExecutionFailureKind::AccountDataError(
|
return Err(ExecutionFailureKind::AccountDataError(
|
||||||
token_definition_id_in,
|
token_definition_id_in,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![account_id_auth])
|
.add_required(seller_mention, account_id_auth, self.0)
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let signing_key = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(account_id_auth)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program.id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
|
||||||
pub async fn send_swap_exact_output(
|
pub async fn send_swap_exact_output(
|
||||||
&self,
|
&self,
|
||||||
user_holding_a: AccountId,
|
user_holding_a: AccountId,
|
||||||
@ -218,6 +150,8 @@ impl Amm<'_> {
|
|||||||
exact_amount_out: u128,
|
exact_amount_out: u128,
|
||||||
max_amount_in: u128,
|
max_amount_in: u128,
|
||||||
token_definition_id_in: AccountId,
|
token_definition_id_in: AccountId,
|
||||||
|
a_mention: &CliAccountMention,
|
||||||
|
b_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let instruction = amm_core::Instruction::SwapExactOutput {
|
let instruction = amm_core::Instruction::SwapExactOutput {
|
||||||
exact_amount_out,
|
exact_amount_out,
|
||||||
@ -258,49 +192,26 @@ impl Amm<'_> {
|
|||||||
user_holding_b,
|
user_holding_b,
|
||||||
];
|
];
|
||||||
|
|
||||||
let account_id_auth = if definition_token_a_id == token_definition_id_in {
|
let (account_id_auth, seller_mention) = if definition_token_a_id == token_definition_id_in {
|
||||||
user_holding_a
|
(user_holding_a, a_mention)
|
||||||
} else if definition_token_b_id == token_definition_id_in {
|
} else if definition_token_b_id == token_definition_id_in {
|
||||||
user_holding_b
|
(user_holding_b, b_mention)
|
||||||
} else {
|
} else {
|
||||||
return Err(ExecutionFailureKind::AccountDataError(
|
return Err(ExecutionFailureKind::AccountDataError(
|
||||||
token_definition_id_in,
|
token_definition_id_in,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![account_id_auth])
|
.add_required(seller_mention, account_id_auth, self.0)
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let signing_key = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(account_id_auth)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program.id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
|
||||||
pub async fn send_add_liquidity(
|
pub async fn send_add_liquidity(
|
||||||
&self,
|
&self,
|
||||||
user_holding_a: AccountId,
|
user_holding_a: AccountId,
|
||||||
@ -309,6 +220,9 @@ impl Amm<'_> {
|
|||||||
min_amount_liquidity: u128,
|
min_amount_liquidity: u128,
|
||||||
max_amount_to_add_token_a: u128,
|
max_amount_to_add_token_a: u128,
|
||||||
max_amount_to_add_token_b: u128,
|
max_amount_to_add_token_b: u128,
|
||||||
|
a_mention: &CliAccountMention,
|
||||||
|
b_mention: &CliAccountMention,
|
||||||
|
lp_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let instruction = amm_core::Instruction::AddLiquidity {
|
let instruction = amm_core::Instruction::AddLiquidity {
|
||||||
min_amount_liquidity,
|
min_amount_liquidity,
|
||||||
@ -352,48 +266,19 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
.add_required(a_mention, user_holding_a, self.0)
|
||||||
|
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
||||||
|
.and_then(|()| groups.add_optional(lp_mention, user_holding_lp, self.0))
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let signing_key_a = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_a)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let signing_key_b = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_b)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program.id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
|
||||||
&message,
|
|
||||||
&[signing_key_a, signing_key_b],
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
|
||||||
pub async fn send_remove_liquidity(
|
pub async fn send_remove_liquidity(
|
||||||
&self,
|
&self,
|
||||||
user_holding_a: AccountId,
|
user_holding_a: AccountId,
|
||||||
@ -402,6 +287,7 @@ impl Amm<'_> {
|
|||||||
remove_liquidity_amount: u128,
|
remove_liquidity_amount: u128,
|
||||||
min_amount_to_remove_token_a: u128,
|
min_amount_to_remove_token_a: u128,
|
||||||
min_amount_to_remove_token_b: u128,
|
min_amount_to_remove_token_b: u128,
|
||||||
|
lp_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let instruction = amm_core::Instruction::RemoveLiquidity {
|
let instruction = amm_core::Instruction::RemoveLiquidity {
|
||||||
remove_liquidity_amount,
|
remove_liquidity_amount,
|
||||||
@ -445,36 +331,12 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![user_holding_lp])
|
.add_required(lp_mention, user_holding_lp, self.0)
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let signing_key_lp = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(user_holding_lp)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program.id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key_lp]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use ata_core::{compute_ata_seed, get_associated_token_account_id};
|
use ata_core::{compute_ata_seed, get_associated_token_account_id};
|
||||||
use common::{HashType, transaction::NSSATransaction};
|
use common::HashType;
|
||||||
use nssa::{
|
use nssa::{
|
||||||
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
||||||
};
|
};
|
||||||
use nssa_core::SharedSecretKey;
|
use nssa_core::SharedSecretKey;
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
|
||||||
|
|
||||||
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, WalletCore};
|
use crate::{
|
||||||
|
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||||
|
signing::SigningGroup,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Ata<'wallet>(pub &'wallet WalletCore);
|
pub struct Ata<'wallet>(pub &'wallet WalletCore);
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ impl Ata<'_> {
|
|||||||
&self,
|
&self,
|
||||||
owner_id: AccountId,
|
owner_id: AccountId,
|
||||||
definition_id: AccountId,
|
definition_id: AccountId,
|
||||||
|
owner_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let program = Program::ata();
|
let program = Program::ata();
|
||||||
let ata_program_id = program.id();
|
let ata_program_id = program.id();
|
||||||
@ -26,36 +29,15 @@ impl Ata<'_> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let account_ids = vec![owner_id, definition_id, ata_id];
|
let account_ids = vec![owner_id, definition_id, ata_id];
|
||||||
|
|
||||||
let nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![owner_id])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let Some(signing_key) = self.0.storage.key_chain().pub_account_signing_key(owner_id) else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
|
||||||
};
|
|
||||||
|
|
||||||
let instruction = ata_core::Instruction::Create { ata_program_id };
|
let instruction = ata_core::Instruction::Create { ata_program_id };
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let mut groups = SigningGroup::new();
|
||||||
program.id(),
|
groups
|
||||||
account_ids,
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
nonces,
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
instruction,
|
self.0
|
||||||
)?;
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
|
.await
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_transfer(
|
pub async fn send_transfer(
|
||||||
@ -64,6 +46,7 @@ impl Ata<'_> {
|
|||||||
definition_id: AccountId,
|
definition_id: AccountId,
|
||||||
recipient_id: AccountId,
|
recipient_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
owner_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let program = Program::ata();
|
let program = Program::ata();
|
||||||
let ata_program_id = program.id();
|
let ata_program_id = program.id();
|
||||||
@ -73,39 +56,18 @@ impl Ata<'_> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let account_ids = vec![owner_id, sender_ata_id, recipient_id];
|
let account_ids = vec![owner_id, sender_ata_id, recipient_id];
|
||||||
|
|
||||||
let nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![owner_id])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let Some(signing_key) = self.0.storage.key_chain().pub_account_signing_key(owner_id) else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
|
||||||
};
|
|
||||||
|
|
||||||
let instruction = ata_core::Instruction::Transfer {
|
let instruction = ata_core::Instruction::Transfer {
|
||||||
ata_program_id,
|
ata_program_id,
|
||||||
amount,
|
amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let mut groups = SigningGroup::new();
|
||||||
program.id(),
|
groups
|
||||||
account_ids,
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
nonces,
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
instruction,
|
self.0
|
||||||
)?;
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
|
.await
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_burn(
|
pub async fn send_burn(
|
||||||
@ -113,6 +75,7 @@ impl Ata<'_> {
|
|||||||
owner_id: AccountId,
|
owner_id: AccountId,
|
||||||
definition_id: AccountId,
|
definition_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
owner_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let program = Program::ata();
|
let program = Program::ata();
|
||||||
let ata_program_id = program.id();
|
let ata_program_id = program.id();
|
||||||
@ -122,39 +85,18 @@ impl Ata<'_> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let account_ids = vec![owner_id, holder_ata_id, definition_id];
|
let account_ids = vec![owner_id, holder_ata_id, definition_id];
|
||||||
|
|
||||||
let nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![owner_id])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let Some(signing_key) = self.0.storage.key_chain().pub_account_signing_key(owner_id) else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
|
||||||
};
|
|
||||||
|
|
||||||
let instruction = ata_core::Instruction::Burn {
|
let instruction = ata_core::Instruction::Burn {
|
||||||
ata_program_id,
|
ata_program_id,
|
||||||
amount,
|
amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let mut groups = SigningGroup::new();
|
||||||
program.id(),
|
groups
|
||||||
account_ids,
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
nonces,
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
instruction,
|
self.0
|
||||||
)?;
|
.send_public_tx(&program, account_ids, instruction, groups)
|
||||||
|
.await
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_create_private_owner(
|
pub async fn send_create_private_owner(
|
||||||
@ -181,7 +123,12 @@ impl Ata<'_> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency())
|
.send_privacy_preserving_tx(
|
||||||
|
accounts,
|
||||||
|
instruction_data,
|
||||||
|
&ata_with_token_dependency(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(hash, mut secrets)| {
|
.map(|(hash, mut secrets)| {
|
||||||
let secret = secrets.pop().expect("expected owner's secret");
|
let secret = secrets.pop().expect("expected owner's secret");
|
||||||
@ -218,7 +165,12 @@ impl Ata<'_> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency())
|
.send_privacy_preserving_tx(
|
||||||
|
accounts,
|
||||||
|
instruction_data,
|
||||||
|
&ata_with_token_dependency(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(hash, mut secrets)| {
|
.map(|(hash, mut secrets)| {
|
||||||
let secret = secrets.pop().expect("expected owner's secret");
|
let secret = secrets.pop().expect("expected owner's secret");
|
||||||
@ -254,7 +206,12 @@ impl Ata<'_> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency())
|
.send_privacy_preserving_tx(
|
||||||
|
accounts,
|
||||||
|
instruction_data,
|
||||||
|
&ata_with_token_dependency(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(hash, mut secrets)| {
|
.map(|(hash, mut secrets)| {
|
||||||
let secret = secrets.pop().expect("expected owner's secret");
|
let secret = secrets.pop().expect("expected owner's secret");
|
||||||
|
|||||||
@ -24,6 +24,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
vec![account],
|
vec![account],
|
||||||
Program::serialize_instruction(instruction).unwrap(),
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
&Program::authenticated_transfer_program().into(),
|
&Program::authenticated_transfer_program().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -58,6 +59,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -91,6 +93,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -1,17 +1,9 @@
|
|||||||
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
|
use authenticated_transfer_core::Instruction as AuthTransferInstruction;
|
||||||
use common::{HashType, transaction::NSSATransaction};
|
use common::HashType;
|
||||||
use nssa::{
|
use nssa::{AccountId, program::Program};
|
||||||
AccountId, PublicTransaction,
|
|
||||||
program::Program,
|
|
||||||
public_transaction::{Message, WitnessSet},
|
|
||||||
};
|
|
||||||
use pyo3::exceptions::PyRuntimeError;
|
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
|
||||||
|
|
||||||
use super::NativeTokenTransfer;
|
use super::NativeTokenTransfer;
|
||||||
use crate::{
|
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroup};
|
||||||
ExecutionFailureKind, cli::CliAccountMention, helperfunctions::read_pin, signing::SigningGroups,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl NativeTokenTransfer<'_> {
|
impl NativeTokenTransfer<'_> {
|
||||||
pub async fn send_public_transfer(
|
pub async fn send_public_transfer(
|
||||||
@ -22,56 +14,22 @@ impl NativeTokenTransfer<'_> {
|
|||||||
from_mention: &CliAccountMention,
|
from_mention: &CliAccountMention,
|
||||||
to_mention: &CliAccountMention,
|
to_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_sender(from_mention, from, self.0)
|
.add_required(from_mention, from, self.0)
|
||||||
.and_then(|()| groups.add_recipient(to_mention, to, self.0))
|
.and_then(|()| groups.add_optional(to_mention, to, self.0))
|
||||||
.map_err(|e| {
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
|
|
||||||
e.to_string(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let program_id = Program::authenticated_transfer_program().id();
|
self.0
|
||||||
let nonces = self
|
.send_public_tx(
|
||||||
.0
|
&Program::authenticated_transfer_program(),
|
||||||
.get_accounts_nonces(groups.signing_ids())
|
vec![from, to],
|
||||||
|
AuthTransferInstruction::Transfer {
|
||||||
|
amount: balance_to_move,
|
||||||
|
},
|
||||||
|
groups,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let message = Message::try_new(
|
|
||||||
program_id,
|
|
||||||
vec![from, to],
|
|
||||||
nonces,
|
|
||||||
AuthTransferInstruction::Transfer {
|
|
||||||
amount: balance_to_move,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(ExecutionFailureKind::TransactionBuildError)?;
|
|
||||||
|
|
||||||
let pin = if groups.needs_pin() {
|
|
||||||
read_pin()
|
|
||||||
.map_err(|e| {
|
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
|
|
||||||
e.to_string(),
|
|
||||||
))
|
|
||||||
})?
|
|
||||||
.as_str()
|
|
||||||
.to_owned()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
|
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_account(
|
pub async fn register_account(
|
||||||
@ -79,53 +37,18 @@ impl NativeTokenTransfer<'_> {
|
|||||||
from: AccountId,
|
from: AccountId,
|
||||||
account_mention: &CliAccountMention,
|
account_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![from])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let account_ids = vec![from];
|
|
||||||
let program_id = Program::authenticated_transfer_program().id();
|
|
||||||
let message = Message::try_new(
|
|
||||||
program_id,
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
AuthTransferInstruction::Initialize,
|
|
||||||
)
|
|
||||||
.map_err(ExecutionFailureKind::TransactionBuildError)?;
|
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
|
||||||
groups
|
groups
|
||||||
.add_sender(account_mention, from, self.0)
|
.add_required(account_mention, from, self.0)
|
||||||
.map_err(|e| {
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
|
|
||||||
e.to_string(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let pin = if groups.needs_pin() {
|
self.0
|
||||||
read_pin()
|
.send_public_tx(
|
||||||
.map_err(|e| {
|
&Program::authenticated_transfer_program(),
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
|
vec![from],
|
||||||
e.to_string(),
|
AuthTransferInstruction::Initialize,
|
||||||
))
|
groups,
|
||||||
})?
|
)
|
||||||
.as_str()
|
.await
|
||||||
.to_owned()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
|
|
||||||
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use nssa::AccountId;
|
|||||||
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||||
|
|
||||||
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
use super::{NativeTokenTransfer, auth_transfer_preparation};
|
||||||
use crate::{ExecutionFailureKind, PrivacyPreservingAccount};
|
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, cli::CliAccountMention};
|
||||||
|
|
||||||
impl NativeTokenTransfer<'_> {
|
impl NativeTokenTransfer<'_> {
|
||||||
pub async fn send_shielded_transfer(
|
pub async fn send_shielded_transfer(
|
||||||
@ -11,9 +11,9 @@ impl NativeTokenTransfer<'_> {
|
|||||||
from: AccountId,
|
from: AccountId,
|
||||||
to: AccountId,
|
to: AccountId,
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
|
from_mention: &CliAccountMention,
|
||||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx_with_pre_check(
|
.send_privacy_preserving_tx_with_pre_check(
|
||||||
vec![
|
vec![
|
||||||
@ -25,6 +25,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
|
Some(from_mention),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -43,9 +44,9 @@ impl NativeTokenTransfer<'_> {
|
|||||||
to_vpk: ViewingPublicKey,
|
to_vpk: ViewingPublicKey,
|
||||||
to_identifier: Identifier,
|
to_identifier: Identifier,
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
|
from_mention: &CliAccountMention,
|
||||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||||
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx_with_pre_check(
|
.send_privacy_preserving_tx_with_pre_check(
|
||||||
vec![
|
vec![
|
||||||
@ -59,6 +60,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
|
Some(from_mention),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -62,6 +62,7 @@ impl Pinata<'_> {
|
|||||||
],
|
],
|
||||||
nssa::program::Program::serialize_instruction(solution).unwrap(),
|
nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||||
&nssa::program::Program::pinata().into(),
|
&nssa::program::Program::pinata().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
use common::{HashType, transaction::NSSATransaction};
|
use common::HashType;
|
||||||
use nssa::{AccountId, program::Program};
|
use nssa::{AccountId, program::Program};
|
||||||
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
|
||||||
use token_core::Instruction;
|
use token_core::Instruction;
|
||||||
|
|
||||||
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, WalletCore};
|
use crate::{
|
||||||
|
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||||
|
signing::SigningGroup,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Token<'wallet>(pub &'wallet WalletCore);
|
pub struct Token<'wallet>(pub &'wallet WalletCore);
|
||||||
|
|
||||||
@ -15,48 +17,21 @@ impl Token<'_> {
|
|||||||
supply_account_id: AccountId,
|
supply_account_id: AccountId,
|
||||||
name: String,
|
name: String,
|
||||||
total_supply: u128,
|
total_supply: u128,
|
||||||
|
definition_mention: &CliAccountMention,
|
||||||
|
supply_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let account_ids = vec![definition_account_id, supply_account_id];
|
let account_ids = vec![definition_account_id, supply_account_id];
|
||||||
let program_id = nssa::program::Program::token().id();
|
|
||||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||||
let nonces = self
|
|
||||||
.0
|
let mut groups = SigningGroup::new();
|
||||||
.get_accounts_nonces(account_ids.clone())
|
groups
|
||||||
|
.add_required(definition_mention, definition_account_id, self.0)
|
||||||
|
.and_then(|()| groups.add_required(supply_mention, supply_account_id, self.0))
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&Program::token(), account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program_id,
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let def_private_key = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(definition_account_id)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
let supply_private_key = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(supply_account_id)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
|
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
|
||||||
&message,
|
|
||||||
&[def_private_key, supply_private_key],
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_new_definition_private_owned_supply(
|
pub async fn send_new_definition_private_owned_supply(
|
||||||
@ -80,6 +55,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -112,6 +88,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -146,6 +123,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -161,63 +139,23 @@ impl Token<'_> {
|
|||||||
sender_account_id: AccountId,
|
sender_account_id: AccountId,
|
||||||
recipient_account_id: AccountId,
|
recipient_account_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
sender_mention: &CliAccountMention,
|
||||||
|
recipient_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let account_ids = vec![sender_account_id, recipient_account_id];
|
let account_ids = vec![sender_account_id, recipient_account_id];
|
||||||
let program_id = nssa::program::Program::token().id();
|
|
||||||
let instruction = Instruction::Transfer {
|
let instruction = Instruction::Transfer {
|
||||||
amount_to_transfer: amount,
|
amount_to_transfer: amount,
|
||||||
};
|
};
|
||||||
let mut nonces = self
|
|
||||||
.0
|
let mut groups = SigningGroup::new();
|
||||||
.get_accounts_nonces(vec![sender_account_id])
|
groups
|
||||||
|
.add_required(sender_mention, sender_account_id, self.0)
|
||||||
|
.and_then(|()| groups.add_optional(recipient_mention, recipient_account_id, self.0))
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&Program::token(), account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let mut private_keys = Vec::new();
|
|
||||||
let sender_sk = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(sender_account_id)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
private_keys.push(sender_sk);
|
|
||||||
|
|
||||||
if let Some(recipient_sk) = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(recipient_account_id)
|
|
||||||
{
|
|
||||||
private_keys.push(recipient_sk);
|
|
||||||
let recipient_nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![recipient_account_id])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
nonces.extend(recipient_nonces);
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"Receiver's account ({recipient_account_id}) private key not found in wallet. Proceeding with only sender's key."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
program_id,
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_transfer_transaction_private_owned_account(
|
pub async fn send_transfer_transaction_private_owned_account(
|
||||||
@ -244,6 +182,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -282,6 +221,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -314,6 +254,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -330,13 +271,13 @@ impl Token<'_> {
|
|||||||
sender_account_id: AccountId,
|
sender_account_id: AccountId,
|
||||||
recipient_account_id: AccountId,
|
recipient_account_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
sender_mention: &CliAccountMention,
|
||||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||||
let instruction = Instruction::Transfer {
|
let instruction = Instruction::Transfer {
|
||||||
amount_to_transfer: amount,
|
amount_to_transfer: amount,
|
||||||
};
|
};
|
||||||
let instruction_data =
|
let instruction_data =
|
||||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx(
|
.send_privacy_preserving_tx(
|
||||||
vec![
|
vec![
|
||||||
@ -347,6 +288,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
Some(sender_mention),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -365,13 +307,13 @@ impl Token<'_> {
|
|||||||
recipient_vpk: ViewingPublicKey,
|
recipient_vpk: ViewingPublicKey,
|
||||||
recipient_identifier: Identifier,
|
recipient_identifier: Identifier,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
sender_mention: &CliAccountMention,
|
||||||
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
|
||||||
let instruction = Instruction::Transfer {
|
let instruction = Instruction::Transfer {
|
||||||
amount_to_transfer: amount,
|
amount_to_transfer: amount,
|
||||||
};
|
};
|
||||||
let instruction_data =
|
let instruction_data =
|
||||||
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
Program::serialize_instruction(instruction).expect("Instruction should serialize");
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.send_privacy_preserving_tx(
|
.send_privacy_preserving_tx(
|
||||||
vec![
|
vec![
|
||||||
@ -384,6 +326,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
Some(sender_mention),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -400,41 +343,21 @@ impl Token<'_> {
|
|||||||
definition_account_id: AccountId,
|
definition_account_id: AccountId,
|
||||||
holder_account_id: AccountId,
|
holder_account_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
holder_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let account_ids = vec![definition_account_id, holder_account_id];
|
let account_ids = vec![definition_account_id, holder_account_id];
|
||||||
let instruction = Instruction::Burn {
|
let instruction = Instruction::Burn {
|
||||||
amount_to_burn: amount,
|
amount_to_burn: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![holder_account_id])
|
.add_required(holder_mention, holder_account_id, self.0)
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&Program::token(), account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
Program::token().id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.expect("Instruction should serialize");
|
|
||||||
|
|
||||||
let signing_key = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(holder_account_id)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_burn_transaction_private_owned_account(
|
pub async fn send_burn_transaction_private_owned_account(
|
||||||
@ -461,6 +384,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -493,6 +417,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -526,6 +451,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -542,63 +468,23 @@ impl Token<'_> {
|
|||||||
definition_account_id: AccountId,
|
definition_account_id: AccountId,
|
||||||
holder_account_id: AccountId,
|
holder_account_id: AccountId,
|
||||||
amount: u128,
|
amount: u128,
|
||||||
|
definition_mention: &CliAccountMention,
|
||||||
|
holder_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let account_ids = vec![definition_account_id, holder_account_id];
|
let account_ids = vec![definition_account_id, holder_account_id];
|
||||||
let instruction = Instruction::Mint {
|
let instruction = Instruction::Mint {
|
||||||
amount_to_mint: amount,
|
amount_to_mint: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut nonces = self
|
let mut groups = SigningGroup::new();
|
||||||
.0
|
groups
|
||||||
.get_accounts_nonces(vec![definition_account_id])
|
.add_required(definition_mention, definition_account_id, self.0)
|
||||||
|
.and_then(|()| groups.add_optional(holder_mention, holder_account_id, self.0))
|
||||||
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send_public_tx(&Program::token(), account_ids, instruction, groups)
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
|
|
||||||
let mut private_keys = Vec::new();
|
|
||||||
let definition_sk = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(definition_account_id)
|
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
|
||||||
private_keys.push(definition_sk);
|
|
||||||
|
|
||||||
if let Some(holder_sk) = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.key_chain()
|
|
||||||
.pub_account_signing_key(holder_account_id)
|
|
||||||
{
|
|
||||||
private_keys.push(holder_sk);
|
|
||||||
let recipient_nonces = self
|
|
||||||
.0
|
|
||||||
.get_accounts_nonces(vec![holder_account_id])
|
|
||||||
.await
|
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
|
||||||
nonces.extend(recipient_nonces);
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"Holder's account ({holder_account_id}) private key not found in wallet. Proceeding with only definition's key."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
|
||||||
Program::token().id(),
|
|
||||||
account_ids,
|
|
||||||
nonces,
|
|
||||||
instruction,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let witness_set =
|
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.0
|
|
||||||
.sequencer_client
|
|
||||||
.send_transaction(NSSATransaction::Public(tx))
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_mint_transaction_private_owned_account(
|
pub async fn send_mint_transaction_private_owned_account(
|
||||||
@ -625,6 +511,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -663,6 +550,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -695,6 +583,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -728,6 +617,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -765,6 +655,7 @@ impl Token<'_> {
|
|||||||
],
|
],
|
||||||
instruction_data,
|
instruction_data,
|
||||||
&Program::token().into(),
|
&Program::token().into(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use keycard_wallet::{KeycardWallet, python_path};
|
use keycard_wallet::{KeycardWallet, python_path};
|
||||||
use nssa::{AccountId, PrivateKey, PublicKey, Signature};
|
use nssa::{AccountId, PrivateKey, PublicKey, Signature};
|
||||||
|
use pyo3::Python;
|
||||||
|
|
||||||
use crate::{WalletCore, cli::CliAccountMention};
|
use crate::{WalletCore, cli::CliAccountMention};
|
||||||
|
|
||||||
@ -9,12 +10,12 @@ use crate::{WalletCore, cli::CliAccountMention};
|
|||||||
/// Local signers are signed in pure Rust; all keycard signers share a single Python session
|
/// Local signers are signed in pure Rust; all keycard signers share a single Python session
|
||||||
/// with one `connect` / `close_session` pair.
|
/// with one `connect` / `close_session` pair.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SigningGroups {
|
pub struct SigningGroup {
|
||||||
local: Vec<(AccountId, PrivateKey)>,
|
local: Vec<(AccountId, PrivateKey)>,
|
||||||
keycard: Vec<(AccountId, String)>,
|
keycard: Vec<(AccountId, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SigningGroups {
|
impl SigningGroup {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
@ -22,7 +23,7 @@ impl SigningGroups {
|
|||||||
|
|
||||||
/// Add a sender. Keycard paths are queued for the hardware session; local accounts
|
/// Add a sender. Keycard paths are queued for the hardware session; local accounts
|
||||||
/// have their signing key resolved eagerly. Errors if no key is found.
|
/// have their signing key resolved eagerly. Errors if no key is found.
|
||||||
pub fn add_sender(
|
pub fn add_required(
|
||||||
&mut self,
|
&mut self,
|
||||||
mention: &CliAccountMention,
|
mention: &CliAccountMention,
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
@ -42,9 +43,9 @@ impl SigningGroups {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a recipient. Same as [`add_sender`] but silently skips accounts with no local
|
/// Add a recipient. Same as [`add_required`] but silently skips accounts with no local
|
||||||
/// key and no keycard path — they are foreign and require neither a signature nor a nonce.
|
/// key and no keycard path — they are foreign and require neither a signature nor a nonce.
|
||||||
pub fn add_recipient(
|
pub fn add_optional(
|
||||||
&mut self,
|
&mut self,
|
||||||
mention: &CliAccountMention,
|
mention: &CliAccountMention,
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
@ -112,3 +113,37 @@ impl SigningGroups {
|
|||||||
Ok(sigs)
|
Ok(sigs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lazily opens and reuses a single Keycard session for all keycard signers in one transaction.
|
||||||
|
pub struct KeycardSessionContext {
|
||||||
|
pin: String,
|
||||||
|
wallet: Option<KeycardWallet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeycardSessionContext {
|
||||||
|
pub fn new(pin: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
pin: pin.into(),
|
||||||
|
wallet: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_connect<'py>(
|
||||||
|
&'py mut self,
|
||||||
|
py: Python<'py>,
|
||||||
|
) -> pyo3::PyResult<&'py KeycardWallet> {
|
||||||
|
if self.wallet.is_none() {
|
||||||
|
python_path::add_python_path(py)?;
|
||||||
|
let wallet = KeycardWallet::new(py)?;
|
||||||
|
wallet.connect(py, &self.pin)?;
|
||||||
|
self.wallet = Some(wallet);
|
||||||
|
}
|
||||||
|
Ok(self.wallet.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(self, py: Python<'_>) {
|
||||||
|
if let Some(w) = self.wallet {
|
||||||
|
drop(w.close_session(py));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user