aimo-cli 0.1.7

AiMo Network client CLI
use std::path::PathBuf;

use aimo_core::keys::{MetadataV1, Scope, SecretKeyV1, Wallet};
use chrono::{Duration, Utc};
use solana_sdk::{signature::Keypair, signer::Signer};

use aimo_core::utils::id::create_keypair_from_file;

/// Generate a secret key with given metadata and keypair.
///
/// NOTE: `valid_for` is how many **DAYS** this key should be valid for.
pub fn generate_secret_key(
    tag: &str,
    valid_for: u32,
    scopes: Vec<Scope>,
    usage_limit: u64,
    id: Option<PathBuf>,
) -> anyhow::Result<String> {
    let keypair = create_keypair_from_file(id)?;
    let valid_for = Duration::days(valid_for.into()).num_milliseconds();
    let created_at = Utc::now().timestamp_millis();

    let metadata = MetadataV1 {
        created_at,
        usage_limit,
        valid_for,
        scopes,
    };

    generate_key_with(&keypair, tag, metadata)
}

fn generate_key_with(keypair: &Keypair, tag: &str, metadata: MetadataV1) -> anyhow::Result<String> {
    // Create canonical JSON string of the metadata
    let canonical_metadata = metadata.to_canonical_json()?;
    let signature = keypair
        .sign_message(canonical_metadata.as_bytes())
        .to_string();
    let signer = keypair.pubkey().to_string();

    let payload = SecretKeyV1 {
        version: 1,
        wallet: Wallet::Solana,
        signer,
        signature,
        metadata,
    };

    Ok(payload.into_string(tag)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn generated_valid_key() {
        let keypair = Keypair::new();
        let valid_for = Duration::days(30).num_milliseconds();
        let created_at = Utc::now().timestamp_millis();
        let scopes = vec![Scope::CompletionModel];

        let metadata = MetadataV1 {
            created_at,
            usage_limit: 1000,
            valid_for,
            scopes,
        };

        let sk = generate_key_with(&keypair, "test", metadata.clone()).unwrap();
        let (scope, key) = SecretKeyV1::decode(&sk).unwrap();
        assert_eq!(scope, "test");
        assert!(key.verify_signature().is_ok());
        assert_eq!(key.signer, keypair.pubkey().to_string());
        assert_eq!(key.metadata, metadata);
    }
}