Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] CLI #13

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@
{
"package": "SolanaSwift",
"repositoryURL": "https://github.com/p2p-org/solana-swift.git",
"state": {
"branch": "feature/universal-add-signature",
"revision": "9ffd12fcadba74cfd795f10a6f47a4df74864367",
"version": null
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "596dab76f1e82f8e6c5d6b64534d989b2ba47f69",
"version": "2.5.2"
"revision": "9f39744e025c7d377987f30b03770805dcb0bcd1",
"version": "1.1.4"
}
},
{
Expand Down
19 changes: 16 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@ let package = Package(
products: [
.library(
name: "FeeRelayerSwift",
targets: ["FeeRelayerSwift"]),
targets: ["FeeRelayerSwift"]
),
.executable(
name: "FeeRelayerCLI",
targets: ["FeeRelayerCLI"]
),
],
dependencies: [
.package(url: "https://github.com/p2p-org/solana-swift.git", from: "2.5.2"),
.package(url: "https://github.com/p2p-org/OrcaSwapSwift.git", from: "2.1.1")
.package(url: "https://github.com/p2p-org/solana-swift.git", branch: "feature/universal-add-signature"),
.package(url: "https://github.com/p2p-org/OrcaSwapSwift.git", from: "2.1.1"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.4"),
],
targets: [
.executableTarget(
name: "FeeRelayerCLI",
dependencies: [
"FeeRelayerSwift",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "FeeRelayerSwift",
dependencies: [
Expand Down
209 changes: 209 additions & 0 deletions Sources/FeeRelayerCLI/cli.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2022 P2P Validator Authors. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.

import ArgumentParser
import FeeRelayerSwift
import Foundation
import OrcaSwapSwift
import SolanaSwift

@main
enum App {
static func main() async {
await FeeRelayerCommand.main()
}
}

struct FeeRelayerCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A Swift command-line tool for API Gateway",
subcommands: [CreateAccount.self, RelaySend.self]
)
}

struct CreateAccount: AsyncParsableCommand {
@Option(help: "Wallet")
var wallet: String = "5bYReP8iw5UuLVS5wmnXfEfrYCKdiQ1FFAZQao8JqY7V"

@Option(help: "To address")
var mint: String = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"

@Option(help: "Transfer amount")
var amount: UInt64 = 5000

@Option(help: "Bloch hash")
var blochHash: String?

@Option(help: "Fee payer")
var feePayer: String = "FG4Y3yX4AAchp1HvNZ7LfzFTewF2f6nDoMDCohTFrdpT"

@Option(help: "Lamport per signature")
var lamportPerSignature: UInt64 = 5000

@Option(help: "Lamport per signature")
var rentExemption: UInt64 = 0

@Option(help: "Fee relayer endpoint")
var feeRelayerEndpoint: String = "https://fee-relayer.key.app"

@Option(help: "Fee relayer endpoint")
var solanaEndpoint: String = "https://api.mainnet-beta.solana.com"

@Flag(help: "cURL")
var curl: Bool = false

func run() async throws {
let solana = JSONRPCAPIClient(endpoint: .init(address: solanaEndpoint, network: .mainnetBeta))
var recentBlochHash = blochHash
if recentBlochHash == nil {
recentBlochHash = try await solana.getRecentBlockhash()
}

let from = try PublicKey(string: wallet)
let mint = try PublicKey(string: mint)
let to = try PublicKey.associatedTokenAddress(walletAddress: from, tokenMintAddress: mint)

let transaction = Transaction(
instructions: [
SystemProgram.createAccountInstruction(
from: try PublicKey(string: feePayer),
toNewPubkey: to,
lamports: amount,
space: 165,
programId: TokenProgram.id
),
SystemProgram.transferInstruction(
from: try PublicKey(string: wallet),
to: try PublicKey(string: feePayer),
lamports: 5000
),
],
recentBlockhash: recentBlochHash,
feePayer: try PublicKey(string: feePayer)
)

let preparedTransaction = PreparedTransaction(
transaction: transaction,
signers: [],
expectedFee: .init(transaction: lamportPerSignature, accountBalances: rentExemption)
)

var httpClient: HTTPClient = FeeRelayerHTTPClient()
if curl {
httpClient = CURLHTTPClient()
}

let apiClient = FeeRelayerSwift.APIClient(httpClient: httpClient, baseUrlString: feeRelayerEndpoint, version: 1)
do {
let result = try await apiClient.sendTransaction(.signRelayTransaction(.init(preparedTransaction: preparedTransaction)))
print(result)
} catch is CURLHTTPClient.Error {
return
} catch {
print(error)
}
}
}

struct RelaySend: AsyncParsableCommand {
@Option(help: "Source address")
var from: String = "5bYReP8iw5UuLVS5wmnXfEfrYCKdiQ1FFAZQao8JqY7V"

@Option(help: "Source address")
var seedPhrase: String?

@Option(help: "Destination address")
var to: String = "9zRnk58ydEKxQ4BKyETG8uQQecppcxMvQaJWLkjocvPm"

@Option(help: "Transfer amount")
var amount: UInt64 = 5000

@Option(help: "Bloch hash")
var blochHash: String?

@Option(help: "Fee payer")
var feePayer: String = "FG4Y3yX4AAchp1HvNZ7LfzFTewF2f6nDoMDCohTFrdpT"

@Option(help: "Lamport per signature")
var lamportPerSignature: UInt64 = 10000

@Option(help: "Lamport per signature")
var rentExemption: UInt64 = 0

@Option(help: "Fee relayer endpoint")
var feeRelayerEndpoint: String = "https://fee-relayer.key.app"

@Option(help: "Fee relayer endpoint")
var solanaEndpoint: String = "https://api.mainnet-beta.solana.com"

@Flag(help: "cURL")
var curl: Bool = false

func run() async throws {
let solana = JSONRPCAPIClient(endpoint: .init(address: solanaEndpoint, network: .mainnetBeta))
var recentBlochHash = blochHash
if recentBlochHash == nil {
recentBlochHash = try await solana.getRecentBlockhash()
}

var transaction = Transaction(
instructions: [
SystemProgram.transferInstruction(
from: try PublicKey(string: from),
to: try PublicKey(string: to),
lamports: amount
),
],
recentBlockhash: recentBlochHash,
feePayer: try PublicKey(string: feePayer)
)



var signers: [Account] = []
if let seedPhrase = seedPhrase {
let account = try await Account(phrase: seedPhrase.components(separatedBy: " "), network: .mainnetBeta, derivablePath: .default)
try transaction.sign(signers: [account])
signers.append(account)
}

let preparedTransaction = PreparedTransaction(
transaction: transaction,
signers: signers,
expectedFee: .init(transaction: lamportPerSignature, accountBalances: rentExemption)
)

var httpClient: HTTPClient = FeeRelayerHTTPClient()
if curl {
httpClient = CURLHTTPClient()
}

let apiClient = FeeRelayerSwift.APIClient(httpClient: httpClient, baseUrlString: feeRelayerEndpoint, version: 1)
do {
let result = try await apiClient.sendTransaction(.signRelayTransaction(.init(preparedTransaction: preparedTransaction)))
try transaction.addSignature(.init(signature: Data(Base58.decode(result)), publicKey: try .init(string: feePayer)))

// print(transaction.jsonString)
// print(Base58.encode(try transaction.serialize(requiredAllSignatures: true, verifySignatures: true)))
print(try transaction.serialize().base64EncodedString())
} catch is CURLHTTPClient.Error {
return
} catch {
print(error)
}
}
}

class CURLHTTPClient: HTTPClient {
enum Error: Swift.Error {
case stop
}

var networkManager: FeeRelayerSwift.NetworkManager = URLSession.shared

func sendRequest<T>(request: URLRequest, decoder _: JSONDecoder) async throws -> T where T: Decodable {
print(request.cURL())
throw Error.stop
}
}