-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support invoking smart contracts from MPC Wallets (#176)
### What changed? Why? This adds support for invoking smart contracts from MPC wallets and from developer-managed wallets. ``` abi = abi = [ { inputs: [{internalType: 'address', name: 'recipient', type: 'address'}], name: 'mint', outputs: [{internalType: 'uint256', name: '', type: 'uint256'}], stateMutability: 'payable', type: 'function' } ] invocation = wallet.invoke_contract(contract_address: '0xa82aB8504fDeb2dADAa3B4F075E967BbE35065b9', abi: abi, method: 'mint', args: { recipient: '0x475d41de7A81298Ba263184996800CBcaAD73C0b' }) invocation.wait! ``` #### Qualified Impact <!-- Please evaluate what components could be affected and what the impact would be if there was an error. How would this error be resolved, e.g. rollback a deploy, push a new fix, disable a feature flag, etc... --> This adds a new feature and should not impact existing features
- Loading branch information
1 parent
bc1ca77
commit 33bdaf3
Showing
11 changed files
with
866 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
=begin | ||
#Coinbase Platform API | ||
#This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. | ||
The version of the OpenAPI document: 0.0.1-alpha | ||
Contact: [email protected] | ||
Generated by: https://openapi-generator.tech | ||
Generator version: 7.7.0 | ||
=end | ||
|
||
require 'date' | ||
require 'time' | ||
|
||
module Coinbase::Client | ||
class Feature | ||
TRANSFER = "transfer".freeze | ||
TRADE = "trade".freeze | ||
FAUCET = "faucet".freeze | ||
SERVER_SIGNER = "server_signer".freeze | ||
UNKNOWN_DEFAULT_OPEN_API = "unknown_default_open_api".freeze | ||
|
||
def self.all_vars | ||
@all_vars ||= [TRANSFER, TRADE, FAUCET, SERVER_SIGNER, UNKNOWN_DEFAULT_OPEN_API].freeze | ||
end | ||
|
||
# Builds the enum from string | ||
# @param [String] The enum value in the form of the string | ||
# @return [String] The enum value | ||
def self.build_from_hash(value) | ||
new.build_from_hash(value) | ||
end | ||
|
||
# Builds the enum from string | ||
# @param [String] The enum value in the form of the string | ||
# @return [String] The enum value | ||
def build_from_hash(value) | ||
return value if Feature.all_vars.include?(value) | ||
raise "Invalid ENUM value #{value} for class #Feature" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
# frozen_string_literal: true | ||
|
||
module Coinbase | ||
# A representation of a Contract Invocation. | ||
class ContractInvocation | ||
class << self | ||
# Creates a new ContractInvocation object. | ||
# @param address_id [String] The Address ID of the signing Address | ||
# @param wallet_id [String] The Wallet ID associated with the signing Address | ||
# @param contract_address [String] The contract address | ||
# @param abi [Array<Hash>] The contract ABI | ||
# @param method [String] The contract method | ||
# @param args [Hash] The arguments to pass to the contract method. | ||
# The keys should be the argument names, and the values should be the argument values. | ||
# @return [ContractInvocation] The new Contract Invocation object | ||
# @raise [Coinbase::ApiError] If the request to create the Contract Invocation fails | ||
def create( | ||
address_id:, | ||
wallet_id:, | ||
contract_address:, | ||
abi:, | ||
method:, | ||
args: {} | ||
) | ||
model = Coinbase.call_api do | ||
contract_invocation_api.create_contract_invocation( | ||
wallet_id, | ||
address_id, | ||
contract_address: contract_address, | ||
abi: abi.to_json, | ||
method: method, | ||
args: args.to_json | ||
) | ||
end | ||
|
||
new(model) | ||
end | ||
|
||
# Enumerates the payload signatures for a given address belonging to a wallet. | ||
# The result is an enumerator that lazily fetches from the server, and can be iterated over, | ||
# converted an array, etc... | ||
# @return [Enumerable<Coinbase::ContractInvocation>] Enumerator that returns payload signatures | ||
def list(wallet_id:, address_id:) | ||
Coinbase::Pagination.enumerate( | ||
->(page) { fetch_page(wallet_id, address_id, page) } | ||
) do |contract_invocation| | ||
new(contract_invocation) | ||
end | ||
end | ||
|
||
private | ||
|
||
def contract_invocation_api | ||
Coinbase::Client::ContractInvocationsApi.new(Coinbase.configuration.api_client) | ||
end | ||
|
||
def fetch_page(wallet_id, address_id, page) | ||
contract_invocation_api.list_contract_invocations( | ||
wallet_id, | ||
address_id, | ||
limit: DEFAULT_PAGE_LIMIT, | ||
page: page | ||
) | ||
end | ||
end | ||
|
||
# Returns a new ContractInvocation object. Do not use this method directly. | ||
# Instead use Coinbase::ContractInvocation.create. | ||
# @param model [Coinbase::Client::ContractInvocation] The underlying Contract Invocation obejct | ||
def initialize(model) | ||
raise unless model.is_a?(Coinbase::Client::ContractInvocation) | ||
|
||
@model = model | ||
end | ||
|
||
# Returns the Contract Invocation ID. | ||
# @return [String] The Contract Invocation ID | ||
def id | ||
@model.contract_invocation_id | ||
end | ||
|
||
# Returns the Wallet ID of the Contract Invocation. | ||
# @return [String] The Wallet ID | ||
def wallet_id | ||
@model.wallet_id | ||
end | ||
|
||
# Returns the Address ID of the Contract Invocation. | ||
# @return [String] The Address ID | ||
def address_id | ||
@model.address_id | ||
end | ||
|
||
# Returns the Network of the Contract Invocation. | ||
# @return [Coinbase::Network] The Network | ||
def network | ||
@network ||= Coinbase::Network.from_id(@model.network_id) | ||
end | ||
|
||
# Returns the Contract Address of the Contract Invocation. | ||
# @return [String] The Contract Address | ||
def contract_address | ||
@model.contract_address | ||
end | ||
|
||
# Returns the ABI of the Contract Invocation. | ||
# @return [Array<Hash>] The ABI | ||
def abi | ||
JSON.parse(@model.abi) | ||
end | ||
|
||
# Returns the method of the Contract Invocation. | ||
# @return [String] The method | ||
def method | ||
@model.method | ||
end | ||
|
||
# Returns the arguments of the Contract Invocation. | ||
# @return [Hash] The arguments | ||
def args | ||
JSON.parse(@model.args).transform_keys(&:to_sym) | ||
end | ||
|
||
# Returns the transaction. | ||
# @return [Coinbase::Transaction] The Transfer transaction | ||
def transaction | ||
@transaction ||= Coinbase::Transaction.new(@model.transaction) | ||
end | ||
|
||
# Returns the status of the Contract Invocation. | ||
# @return [String] The status | ||
def status | ||
transaction.status | ||
end | ||
|
||
# Signs the Contract Invocation transaction with the given key. | ||
# This is required before broadcasting the Contract Invocation when not using | ||
# a Server-Signer. | ||
# @param key [Eth::Key] The key to sign the ContractInvocation with | ||
# @raise [RuntimeError] If the key is not an Eth::Key | ||
# @return [ContractInvocation] The ContractInvocation object | ||
def sign(key) | ||
raise unless key.is_a?(Eth::Key) | ||
|
||
transaction.sign(key) | ||
|
||
self | ||
end | ||
|
||
# Broadcasts the ContractInvocation to the Network. | ||
# @raise [RuntimeError] If the ContractInvocation is not signed | ||
# @return [ContractInvocation] The ContractInvocation object | ||
def broadcast! | ||
raise TransactionNotSignedError unless transaction.signed? | ||
|
||
@model = Coinbase.call_api do | ||
contract_invocation_api.broadcast_contract_invocation( | ||
wallet_id, | ||
address_id, | ||
id, | ||
{ signed_payload: transaction.signature } | ||
) | ||
end | ||
|
||
@transaction = Coinbase::Transaction.new(@model.transaction) | ||
|
||
self | ||
end | ||
|
||
# # Reload reloads the Contract Invocation model with the latest version from the server side. | ||
# @return [ContractInvocation] The most recent version of Contract Invocation from the server | ||
def reload | ||
@model = Coinbase.call_api do | ||
contract_invocation_api.get_contract_invocation(wallet_id, address_id, id) | ||
end | ||
|
||
@transaction = Coinbase::Transaction.new(@model.transaction) | ||
|
||
self | ||
end | ||
|
||
# Waits until the Contract Invocation is signed or failed by polling the server at the given interval. Raises a | ||
# Timeout::Error if the Contract Invocation takes longer than the given timeout. | ||
# @param interval_seconds [Integer] The interval at which to poll the server, in seconds | ||
# @param timeout_seconds [Integer] The maximum amount of time to wait for the Contract Invocation to be signed, | ||
# in seconds. | ||
# @return [ContractInvocation] The completed Contract Invocation object | ||
def wait!(interval_seconds = 0.2, timeout_seconds = 20) | ||
start_time = Time.now | ||
|
||
loop do | ||
reload | ||
|
||
return self if transaction.terminal_state? | ||
|
||
raise Timeout::Error, 'Contract Invocation timed out' if Time.now - start_time > timeout_seconds | ||
|
||
self.sleep interval_seconds | ||
end | ||
|
||
self | ||
end | ||
|
||
# Returns a String representation of the Contract Invocation. | ||
# @return [String] a String representation of the Contract Invocation | ||
def to_s | ||
Coinbase.pretty_print_object( | ||
self.class, | ||
id: id, | ||
wallet_id: wallet_id, | ||
address_id: address_id, | ||
network_id: network.id, | ||
status: status, | ||
abi: abi.to_json, | ||
method: method, | ||
args: args.to_json, | ||
transaction_hash: transaction.transaction_hash, | ||
transaction_link: transaction.transaction_link | ||
) | ||
end | ||
|
||
# Same as to_s. | ||
# @return [String] a String representation of the ContractInvocation | ||
def inspect | ||
to_s | ||
end | ||
|
||
private | ||
|
||
def contract_invocation_api | ||
@contract_invocation_api ||= Coinbase::Client::ContractInvocationsApi.new(Coinbase.configuration.api_client) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
module Coinbase | ||
VERSION = '0.2.0' | ||
VERSION = '0.3.0' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.