Class: Mpp::Methods::Tempo::TempoMethod

Inherits:
Object
  • Object
show all
Defined in:
lib/mpp/methods/tempo/client_method.rb

Overview

Tempo payment method implementation. Handles client-side credential creation for Tempo payments.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(account: nil, fee_payer: nil, root_account: nil, rpc_url: Defaults::RPC_URL, chain_id: nil, currency: nil, recipient: nil, decimals: 6, client_id: nil, expected_recipients: nil, fee_payer_allowed_fee_tokens: nil) ⇒ TempoMethod

Returns a new instance of TempoMethod.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mpp/methods/tempo/client_method.rb', line 25

def initialize(account: nil, fee_payer: nil, root_account: nil,
  rpc_url: Defaults::RPC_URL, chain_id: nil, currency: nil,
  recipient: nil, decimals: 6, client_id: nil,
  expected_recipients: nil, fee_payer_allowed_fee_tokens: nil)
  @name = "tempo"
  @account = 
  @fee_payer = fee_payer
  @fee_payer_allowed_fee_tokens =
    fee_payer_allowed_fee_tokens&.map { |token| token.to_s.downcase }
  @root_account = 
  @rpc_url = rpc_url
  @chain_id = chain_id
  @currency = currency
  @recipient = recipient
  @decimals = decimals
  @client_id = client_id
  @expected_recipients = expected_recipients&.map(&:downcase)&.to_set
  @intents = {}
end

Instance Attribute Details

#accountObject (readonly)

Returns the value of attribute account.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def 
  @account
end

#chain_idObject (readonly)

Returns the value of attribute chain_id.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def chain_id
  @chain_id
end

#client_idObject (readonly)

Returns the value of attribute client_id.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def client_id
  @client_id
end

#currencyObject (readonly)

Returns the value of attribute currency.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def currency
  @currency
end

#decimalsObject (readonly)

Returns the value of attribute decimals.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def decimals
  @decimals
end

#expected_recipientsObject (readonly)

Returns the value of attribute expected_recipients.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def expected_recipients
  @expected_recipients
end

#fee_payerObject (readonly)

Returns the value of attribute fee_payer.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def fee_payer
  @fee_payer
end

#fee_payer_allowed_fee_tokensObject (readonly)

Returns the value of attribute fee_payer_allowed_fee_tokens.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def fee_payer_allowed_fee_tokens
  @fee_payer_allowed_fee_tokens
end

#intentsObject

Returns the value of attribute intents.



23
24
25
# File 'lib/mpp/methods/tempo/client_method.rb', line 23

def intents
  @intents
end

#nameObject (readonly)

Returns the value of attribute name.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def name
  @name
end

#recipientObject (readonly)

Returns the value of attribute recipient.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def recipient
  @recipient
end

#root_accountObject (readonly)

Returns the value of attribute root_account.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def 
  @root_account
end

#rpc_urlObject (readonly)

Returns the value of attribute rpc_url.



20
21
22
# File 'lib/mpp/methods/tempo/client_method.rb', line 20

def rpc_url
  @rpc_url
end

Instance Method Details

#create_credential(challenge, mode: nil) ⇒ Object

Create a credential to satisfy the given challenge.

mode: :pull (default) — return signed transaction for server to broadcast

:push — broadcast on-chain, return transaction hash
:proof — zero-amount transaction proving account ownership

Raises:

  • (ArgumentError)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/mpp/methods/tempo/client_method.rb', line 50

def create_credential(challenge, mode: nil)
  raise ArgumentError, "No account configured for signing" unless @account
  raise ArgumentError, "Unsupported intent: #{challenge.intent}" unless challenge.intent == "charge"

  mode ||= :pull
  request = challenge.request
  method_details = request["methodDetails"]
  method_details = {} unless method_details.is_a?(Hash)

  validate_recipients(request, method_details) if @expected_recipients

  use_fee_payer = method_details.fetch("feePayer", false)

  nonce_key = request.fetch("nonce_key", 0)
  if nonce_key.is_a?(String)
    nonce_key = nonce_key.start_with?("0x") ? nonce_key.to_i(16) : nonce_key.to_i
  end

  memo = method_details["memo"]
  memo ||= Attribution.encode(server_id: challenge.realm, client_id: @client_id, challenge_id: challenge.id)

  # Resolve RPC URL from challenge's chainId. Normalize the configured pin
  # once (it may be a String from ENV/config) so it compares equal to
  # integer chain ids everywhere, including the downstream RPC check.
  resolved_rpc_url = @rpc_url
  expected_chain_id = nil
  configured_chain_id = @chain_id.nil? ? nil : Integer(@chain_id)
  challenge_chain_id = method_details["chainId"]
  if challenge_chain_id
    begin
      parsed_chain_id = Integer(challenge_chain_id)
      # Chain pinning: reject a challenge whose chainId conflicts with the
      # configured chain, before any RPC call or signing.
      if configured_chain_id && parsed_chain_id != configured_chain_id
        raise TransactionError, "Chain ID mismatch: expected #{configured_chain_id}, got #{parsed_chain_id}"
      end
      resolved = Defaults::CHAIN_RPC_URLS[parsed_chain_id]
      if resolved
        resolved_rpc_url = resolved
        expected_chain_id = parsed_chain_id
      end
    rescue ArgumentError, TypeError
      # ignore
    end
  end

  expected_chain_id ||= configured_chain_id

  # Proof mode: sign EIP-712 typed data (no transaction needed)
  if mode == :proof
    chain_id = expected_chain_id || @chain_id
    raise ArgumentError, "chain_id required for proof mode" unless chain_id

    signature = Proof.sign(
      account: @account,
      chain_id: chain_id,
      challenge_id: challenge.id,
      realm: challenge.realm
    )

    return Mpp::Credential.new(
      challenge: challenge.to_echo,
      payload: {"type" => "proof", "signature" => signature},
      source: Proof.source(address: @account.address, chain_id: chain_id)
    )
  end

  raw_tx, chain_id = build_tempo_transfer(
    amount: request["amount"],
    currency: request["currency"],
    recipient: request["recipient"],
    nonce_key: nonce_key,
    memo: memo,
    rpc_url: resolved_rpc_url,
    expected_chain_id: expected_chain_id,
    awaiting_fee_payer: use_fee_payer
  )

  payload = if mode == :push
    tx_hash = Rpc.call(resolved_rpc_url, "eth_sendRawTransaction", [raw_tx])
    raise TransactionError, "No transaction hash returned" unless tx_hash
    {"type" => "hash", "hash" => tx_hash}
  else
    {"type" => "transaction", "signature" => raw_tx}
  end

  Mpp::Credential.new(
    challenge: challenge.to_echo,
    payload: payload,
    source: "did:pkh:eip155:#{chain_id}:#{@account.address}"
  )
end

#transform_request(request, _credential) ⇒ Object

Transform request - adds default methodDetails if needed.



144
145
146
# File 'lib/mpp/methods/tempo/client_method.rb', line 144

def transform_request(request, _credential)
  request
end