remitmd 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -14
- data/lib/remitmd/a2a.rb +188 -2
- data/lib/remitmd/errors.rb +52 -17
- data/lib/remitmd/http.rb +16 -8
- data/lib/remitmd/keccak.rb +1 -1
- data/lib/remitmd/mock.rb +8 -7
- data/lib/remitmd/models.rb +88 -58
- data/lib/remitmd/wallet.rb +92 -85
- data/lib/remitmd/x402_client.rb +201 -0
- data/lib/remitmd/x402_paywall.rb +170 -0
- data/lib/remitmd.rb +4 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b29ac02ca95fe1053bed848cca89111698f1744bbeb145681a76a3f9ed2d8e66
|
|
4
|
+
data.tar.gz: d55c4f33eaaa686fa0036e3a0566ee6382a0a544c01093a3d9887e745b2eaa01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c00d48fc571e6975d9dfd32c679d5b9cf46205742a8a725022907eb997d42de7a61ae040c22dfafb7d5a994277d25f5dafd2e589611e849f94a70c692d71102
|
|
7
|
+
data.tar.gz: dff3c27cf317ce994999efd38d57f1173f0a1136da0bf4dbcdbf1ff5879a9f737bef96875802108ca130b4e46b3b94eec1cc0f52cace3152c89357018d9f9621
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# remit.md Ruby SDK
|
|
2
2
|
|
|
3
|
+
> [Skill MD](https://remit.md) · [Docs](https://remit.md/docs) · [Agent Spec](https://remit.md/agent.md)
|
|
4
|
+
|
|
3
5
|
Universal payment protocol for AI agents — Ruby client library.
|
|
4
6
|
|
|
5
7
|
[](https://github.com/remit-md/sdk/actions/workflows/ci.yml)
|
|
@@ -38,7 +40,7 @@ Or from environment variables:
|
|
|
38
40
|
```ruby
|
|
39
41
|
wallet = Remitmd::RemitWallet.from_env
|
|
40
42
|
# Requires: REMITMD_PRIVATE_KEY
|
|
41
|
-
# Optional: REMITMD_CHAIN (default: "base"), REMITMD_API_URL
|
|
43
|
+
# Optional: REMITMD_CHAIN (default: "base"), REMITMD_API_URL
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
Permits are auto-signed. Every payment method fetches the on-chain USDC nonce, signs an EIP-2612 permit, and includes it automatically.
|
|
@@ -267,19 +269,6 @@ permit = wallet.sign_usdc_permit(
|
|
|
267
269
|
tx = wallet.pay("0xRecipient...", 5.00, permit: permit)
|
|
268
270
|
```
|
|
269
271
|
|
|
270
|
-
### Custom RPC URL
|
|
271
|
-
|
|
272
|
-
Override the JSON-RPC endpoint used for nonce fetching:
|
|
273
|
-
|
|
274
|
-
```ruby
|
|
275
|
-
wallet = Remitmd::RemitWallet.new(
|
|
276
|
-
private_key: key,
|
|
277
|
-
chain: "base_sepolia",
|
|
278
|
-
rpc_url: "https://your-rpc-provider.com/v1/base-sepolia"
|
|
279
|
-
)
|
|
280
|
-
# Or via environment: REMITMD_RPC_URL
|
|
281
|
-
```
|
|
282
|
-
|
|
283
272
|
## License
|
|
284
273
|
|
|
285
274
|
MIT — see [LICENSE](LICENSE)
|
data/lib/remitmd/a2a.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
|
-
module
|
|
7
|
+
module Remitmd
|
|
8
8
|
# A2A capability extension declared in an agent card.
|
|
9
9
|
AgentExtension = Data.define(:uri, :description, :required)
|
|
10
10
|
|
|
@@ -37,7 +37,7 @@ module RemitMd
|
|
|
37
37
|
|
|
38
38
|
# Fetch and parse the A2A agent card from +base_url/.well-known/agent-card.json+.
|
|
39
39
|
#
|
|
40
|
-
# card =
|
|
40
|
+
# card = Remitmd::AgentCard.discover("https://remit.md")
|
|
41
41
|
# puts card.name # => "remit.md"
|
|
42
42
|
# puts card.url # => "https://remit.md/a2a"
|
|
43
43
|
#
|
|
@@ -55,4 +55,190 @@ module RemitMd
|
|
|
55
55
|
new(JSON.parse(response.body))
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# ─── A2A task types ──────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
# Status of an A2A task.
|
|
62
|
+
class A2ATaskStatus
|
|
63
|
+
attr_reader :state, :message
|
|
64
|
+
|
|
65
|
+
def initialize(data)
|
|
66
|
+
@state = data["state"].to_s
|
|
67
|
+
@message = data["message"]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# An artifact part within an A2A artifact.
|
|
72
|
+
A2AArtifactPart = Data.define(:kind, :data)
|
|
73
|
+
|
|
74
|
+
# An artifact returned by an A2A task.
|
|
75
|
+
class A2AArtifact
|
|
76
|
+
attr_reader :name, :parts
|
|
77
|
+
|
|
78
|
+
def initialize(data)
|
|
79
|
+
@name = data["name"]
|
|
80
|
+
@parts = (data["parts"] || []).map do |p|
|
|
81
|
+
A2AArtifactPart.new(kind: p["kind"].to_s, data: p["data"] || {})
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# An A2A task returned by message/send, tasks/get, or tasks/cancel.
|
|
87
|
+
class A2ATask
|
|
88
|
+
attr_reader :id, :status, :artifacts
|
|
89
|
+
|
|
90
|
+
def initialize(data)
|
|
91
|
+
@id = data["id"].to_s
|
|
92
|
+
@status = A2ATaskStatus.new(data["status"] || {})
|
|
93
|
+
@artifacts = (data["artifacts"] || []).map { |a| A2AArtifact.new(a) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Extract txHash from task artifacts, if present.
|
|
97
|
+
# @return [String, nil]
|
|
98
|
+
def tx_hash
|
|
99
|
+
artifacts.each do |artifact|
|
|
100
|
+
artifact.parts.each do |part|
|
|
101
|
+
tx = part.data["txHash"] if part.data.is_a?(Hash)
|
|
102
|
+
return tx if tx.is_a?(String)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ─── IntentMandate ──────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
# A mandate authorizing a payment intent.
|
|
112
|
+
class IntentMandate
|
|
113
|
+
attr_reader :mandate_id, :expires_at, :issuer, :max_amount, :currency
|
|
114
|
+
|
|
115
|
+
def initialize(mandate_id:, expires_at:, issuer:, max_amount:, currency: "USDC")
|
|
116
|
+
@mandate_id = mandate_id
|
|
117
|
+
@expires_at = expires_at
|
|
118
|
+
@issuer = issuer
|
|
119
|
+
@max_amount = max_amount
|
|
120
|
+
@currency = currency
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def to_h
|
|
124
|
+
{
|
|
125
|
+
mandateId: @mandate_id,
|
|
126
|
+
expiresAt: @expires_at,
|
|
127
|
+
issuer: @issuer,
|
|
128
|
+
allowance: {
|
|
129
|
+
maxAmount: @max_amount,
|
|
130
|
+
currency: @currency,
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# ─── A2A JSON-RPC client ────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
# A2A JSON-RPC client — send payments and manage tasks via the A2A protocol.
|
|
139
|
+
#
|
|
140
|
+
# @example
|
|
141
|
+
# card = Remitmd::AgentCard.discover("https://remit.md")
|
|
142
|
+
# signer = Remitmd::PrivateKeySigner.new(ENV["REMITMD_KEY"])
|
|
143
|
+
# client = Remitmd::A2AClient.from_card(card, signer)
|
|
144
|
+
# task = client.send(to: "0xRecipient...", amount: 10)
|
|
145
|
+
# puts task.status.state
|
|
146
|
+
#
|
|
147
|
+
class A2AClient
|
|
148
|
+
CHAIN_IDS = {
|
|
149
|
+
"base" => 8453,
|
|
150
|
+
"base-sepolia" => 84_532,
|
|
151
|
+
}.freeze
|
|
152
|
+
|
|
153
|
+
# @param endpoint [String] full A2A endpoint URL from the agent card
|
|
154
|
+
# @param signer [#sign, #address] a signer for EIP-712 authentication
|
|
155
|
+
# @param chain_id [Integer] chain ID
|
|
156
|
+
# @param verifying_contract [String] verifying contract address
|
|
157
|
+
def initialize(endpoint:, signer:, chain_id:, verifying_contract: "")
|
|
158
|
+
parsed = URI(endpoint)
|
|
159
|
+
@base_url = "#{parsed.scheme}://#{parsed.host}#{parsed.port == parsed.default_port ? "" : ":#{parsed.port}"}"
|
|
160
|
+
@path = parsed.path.empty? ? "/a2a" : parsed.path
|
|
161
|
+
@transport = HttpTransport.new(
|
|
162
|
+
base_url: @base_url,
|
|
163
|
+
signer: signer,
|
|
164
|
+
chain_id: chain_id,
|
|
165
|
+
router_address: verifying_contract
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Convenience constructor from an AgentCard and a signer.
|
|
170
|
+
# @param card [AgentCard]
|
|
171
|
+
# @param signer [#sign, #address]
|
|
172
|
+
# @param chain [String] chain name (default: "base")
|
|
173
|
+
# @param verifying_contract [String]
|
|
174
|
+
# @return [A2AClient]
|
|
175
|
+
def self.from_card(card, signer, chain: "base", verifying_contract: "")
|
|
176
|
+
chain_id = CHAIN_IDS[chain] || CHAIN_IDS["base"]
|
|
177
|
+
new(
|
|
178
|
+
endpoint: card.url,
|
|
179
|
+
signer: signer,
|
|
180
|
+
chain_id: chain_id,
|
|
181
|
+
verifying_contract: verifying_contract
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Send a direct USDC payment via message/send.
|
|
186
|
+
# @param to [String] recipient 0x address
|
|
187
|
+
# @param amount [Numeric] amount in USDC
|
|
188
|
+
# @param memo [String] optional memo
|
|
189
|
+
# @param mandate [IntentMandate, nil] optional intent mandate
|
|
190
|
+
# @return [A2ATask]
|
|
191
|
+
def send(to:, amount:, memo: "", mandate: nil)
|
|
192
|
+
nonce = SecureRandom.hex(16)
|
|
193
|
+
message_id = SecureRandom.hex(16)
|
|
194
|
+
|
|
195
|
+
message = {
|
|
196
|
+
messageId: message_id,
|
|
197
|
+
role: "user",
|
|
198
|
+
parts: [
|
|
199
|
+
{
|
|
200
|
+
kind: "data",
|
|
201
|
+
data: {
|
|
202
|
+
model: "direct",
|
|
203
|
+
to: to,
|
|
204
|
+
amount: format("%.2f", amount),
|
|
205
|
+
memo: memo,
|
|
206
|
+
nonce: nonce,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
message[:metadata] = { mandate: mandate.to_h } if mandate
|
|
213
|
+
|
|
214
|
+
rpc("message/send", { message: message }, message_id)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Fetch the current state of an A2A task by ID.
|
|
218
|
+
# @param task_id [String]
|
|
219
|
+
# @return [A2ATask]
|
|
220
|
+
def get_task(task_id)
|
|
221
|
+
rpc("tasks/get", { id: task_id }, task_id[0, 16])
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Cancel an in-progress A2A task.
|
|
225
|
+
# @param task_id [String]
|
|
226
|
+
# @return [A2ATask]
|
|
227
|
+
def cancel_task(task_id)
|
|
228
|
+
rpc("tasks/cancel", { id: task_id }, task_id[0, 16])
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
def rpc(method, params, call_id)
|
|
234
|
+
body = { jsonrpc: "2.0", id: call_id, method: method, params: params }
|
|
235
|
+
data = @transport.post(@path, body)
|
|
236
|
+
if data.is_a?(Hash) && data["error"]
|
|
237
|
+
err_msg = data["error"]["message"] || JSON.generate(data["error"])
|
|
238
|
+
raise RemitError.new("SERVER_ERROR", "A2A error: #{err_msg}")
|
|
239
|
+
end
|
|
240
|
+
result = data.is_a?(Hash) ? (data["result"] || data) : data
|
|
241
|
+
A2ATask.new(result)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
58
244
|
end
|
data/lib/remitmd/errors.rb
CHANGED
|
@@ -15,29 +15,64 @@ module Remitmd
|
|
|
15
15
|
# puts e.doc_url # => "https://remit.md/docs/api-reference/error-codes#invalid_address"
|
|
16
16
|
# end
|
|
17
17
|
class RemitError < StandardError
|
|
18
|
-
# Error code constants
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# Error code constants — matches TS SDK (28 codes)
|
|
19
|
+
# Auth errors
|
|
20
|
+
INVALID_SIGNATURE = "INVALID_SIGNATURE"
|
|
21
|
+
NONCE_REUSED = "NONCE_REUSED"
|
|
22
|
+
TIMESTAMP_EXPIRED = "TIMESTAMP_EXPIRED"
|
|
23
|
+
UNAUTHORIZED = "UNAUTHORIZED"
|
|
24
|
+
|
|
25
|
+
# Balance / funds
|
|
26
|
+
INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE"
|
|
27
|
+
BELOW_MINIMUM = "BELOW_MINIMUM"
|
|
28
|
+
|
|
29
|
+
# Escrow errors
|
|
22
30
|
ESCROW_NOT_FOUND = "ESCROW_NOT_FOUND"
|
|
31
|
+
ESCROW_ALREADY_FUNDED = "ESCROW_ALREADY_FUNDED"
|
|
32
|
+
ESCROW_EXPIRED = "ESCROW_EXPIRED"
|
|
33
|
+
|
|
34
|
+
# Invoice errors
|
|
35
|
+
INVALID_INVOICE = "INVALID_INVOICE"
|
|
36
|
+
DUPLICATE_INVOICE = "DUPLICATE_INVOICE"
|
|
37
|
+
SELF_PAYMENT = "SELF_PAYMENT"
|
|
38
|
+
INVALID_PAYMENT_TYPE = "INVALID_PAYMENT_TYPE"
|
|
39
|
+
|
|
40
|
+
# Tab errors
|
|
41
|
+
TAB_DEPLETED = "TAB_DEPLETED"
|
|
42
|
+
TAB_EXPIRED = "TAB_EXPIRED"
|
|
23
43
|
TAB_NOT_FOUND = "TAB_NOT_FOUND"
|
|
44
|
+
|
|
45
|
+
# Stream errors
|
|
24
46
|
STREAM_NOT_FOUND = "STREAM_NOT_FOUND"
|
|
47
|
+
RATE_EXCEEDS_CAP = "RATE_EXCEEDS_CAP"
|
|
48
|
+
|
|
49
|
+
# Bounty errors
|
|
50
|
+
BOUNTY_EXPIRED = "BOUNTY_EXPIRED"
|
|
51
|
+
BOUNTY_CLAIMED = "BOUNTY_CLAIMED"
|
|
52
|
+
BOUNTY_MAX_ATTEMPTS = "BOUNTY_MAX_ATTEMPTS"
|
|
25
53
|
BOUNTY_NOT_FOUND = "BOUNTY_NOT_FOUND"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
|
|
55
|
+
# Chain errors
|
|
56
|
+
CHAIN_MISMATCH = "CHAIN_MISMATCH"
|
|
57
|
+
CHAIN_UNSUPPORTED = "CHAIN_UNSUPPORTED"
|
|
58
|
+
|
|
59
|
+
# Rate limiting
|
|
60
|
+
RATE_LIMITED = "RATE_LIMITED"
|
|
61
|
+
|
|
62
|
+
# Cancellation errors
|
|
63
|
+
CANCEL_BLOCKED_CLAIM_START = "CANCEL_BLOCKED_CLAIM_START"
|
|
64
|
+
CANCEL_BLOCKED_EVIDENCE = "CANCEL_BLOCKED_EVIDENCE"
|
|
65
|
+
|
|
66
|
+
# Protocol errors
|
|
67
|
+
VERSION_MISMATCH = "VERSION_MISMATCH"
|
|
29
68
|
NETWORK_ERROR = "NETWORK_ERROR"
|
|
69
|
+
|
|
70
|
+
# Legacy aliases (kept for backward compat within the SDK)
|
|
71
|
+
INVALID_ADDRESS = "INVALID_ADDRESS"
|
|
72
|
+
INVALID_AMOUNT = "INVALID_AMOUNT"
|
|
73
|
+
INSUFFICIENT_FUNDS = INSUFFICIENT_BALANCE
|
|
30
74
|
SERVER_ERROR = "SERVER_ERROR"
|
|
31
|
-
|
|
32
|
-
SIGNATURE_INVALID = "SIGNATURE_INVALID"
|
|
33
|
-
ESCROW_ALREADY_RELEASED = "ESCROW_ALREADY_RELEASED"
|
|
34
|
-
ESCROW_EXPIRED = "ESCROW_EXPIRED"
|
|
35
|
-
TAB_LIMIT_EXCEEDED = "TAB_LIMIT_EXCEEDED"
|
|
36
|
-
BOUNTY_ALREADY_AWARDED = "BOUNTY_ALREADY_AWARDED"
|
|
37
|
-
STREAM_NOT_ACTIVE = "STREAM_NOT_ACTIVE"
|
|
38
|
-
DEPOSIT_ALREADY_SETTLED = "DEPOSIT_ALREADY_SETTLED"
|
|
39
|
-
USDC_TRANSFER_FAILED = "USDC_TRANSFER_FAILED"
|
|
40
|
-
CHAIN_UNAVAILABLE = "CHAIN_UNAVAILABLE"
|
|
75
|
+
DEPOSIT_NOT_FOUND = "DEPOSIT_NOT_FOUND"
|
|
41
76
|
|
|
42
77
|
attr_reader :code, :doc_url, :context
|
|
43
78
|
|
data/lib/remitmd/http.rb
CHANGED
|
@@ -8,9 +8,11 @@ require "openssl"
|
|
|
8
8
|
|
|
9
9
|
module Remitmd
|
|
10
10
|
# Chain configuration: maps chain names to (api_url, chain_id) pairs.
|
|
11
|
+
# Canonical keys use hyphens; underscored variants are accepted as aliases.
|
|
11
12
|
CHAIN_CONFIG = {
|
|
12
|
-
"base" => { url: "https://remit.md/api/v1",
|
|
13
|
-
"
|
|
13
|
+
"base" => { url: "https://remit.md/api/v1", chain_id: 8453 },
|
|
14
|
+
"base-sepolia" => { url: "https://testnet.remit.md/api/v1", chain_id: 84532 },
|
|
15
|
+
"base_sepolia" => { url: "https://testnet.remit.md/api/v1", chain_id: 84532 },
|
|
14
16
|
}.freeze
|
|
15
17
|
|
|
16
18
|
# HTTP transport layer. Signs each request with EIP-712 auth headers and
|
|
@@ -40,9 +42,11 @@ module Remitmd
|
|
|
40
42
|
|
|
41
43
|
def request(method, path, body)
|
|
42
44
|
attempt = 0
|
|
45
|
+
# Generate idempotency key once per request (stable across retries).
|
|
46
|
+
idempotency_key = %i[post put patch].include?(method) ? SecureRandom.uuid : nil
|
|
43
47
|
begin
|
|
44
48
|
attempt += 1
|
|
45
|
-
req = build_request(method, path, body)
|
|
49
|
+
req = build_request(method, path, body, idempotency_key)
|
|
46
50
|
resp = @http.request(req)
|
|
47
51
|
handle_response(resp, path)
|
|
48
52
|
rescue RemitError => e
|
|
@@ -58,7 +62,7 @@ module Remitmd
|
|
|
58
62
|
end
|
|
59
63
|
end
|
|
60
64
|
|
|
61
|
-
def build_request(method, path, body)
|
|
65
|
+
def build_request(method, path, body, idempotency_key = nil)
|
|
62
66
|
full_path = "#{@uri.path}#{path}"
|
|
63
67
|
req = case method
|
|
64
68
|
when :get then Net::HTTP::Get.new(full_path)
|
|
@@ -70,9 +74,10 @@ module Remitmd
|
|
|
70
74
|
nonce_hex = "0x#{nonce_bytes.unpack1("H*")}"
|
|
71
75
|
timestamp = Time.now.to_i
|
|
72
76
|
|
|
73
|
-
#
|
|
77
|
+
# Strip query string before signing — only the path is included in EIP-712.
|
|
78
|
+
sign_path = full_path.split("?").first
|
|
74
79
|
http_method = method.to_s.upcase
|
|
75
|
-
digest = eip712_hash(http_method,
|
|
80
|
+
digest = eip712_hash(http_method, sign_path, timestamp, nonce_bytes)
|
|
76
81
|
signature = @signer.sign(digest)
|
|
77
82
|
|
|
78
83
|
req["Content-Type"] = "application/json"
|
|
@@ -81,6 +86,7 @@ module Remitmd
|
|
|
81
86
|
req["X-Remit-Nonce"] = nonce_hex
|
|
82
87
|
req["X-Remit-Timestamp"] = timestamp.to_s
|
|
83
88
|
req["X-Remit-Signature"] = signature
|
|
89
|
+
req["X-Idempotency-Key"] = idempotency_key if idempotency_key
|
|
84
90
|
|
|
85
91
|
if body
|
|
86
92
|
req.body = body.to_json
|
|
@@ -150,8 +156,10 @@ module Remitmd
|
|
|
150
156
|
when 200..299
|
|
151
157
|
parsed
|
|
152
158
|
when 400
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
# Support nested error format: { "error": { "code": "...", "message": "..." } }
|
|
160
|
+
err = parsed.is_a?(Hash) && parsed["error"].is_a?(Hash) ? parsed["error"] : parsed
|
|
161
|
+
code = err["code"] || RemitError::SERVER_ERROR
|
|
162
|
+
raise RemitError.new(code, err["message"] || "Bad request", context: parsed)
|
|
155
163
|
when 401
|
|
156
164
|
raise RemitError.new(RemitError::UNAUTHORIZED,
|
|
157
165
|
"Authentication failed — check your private key and chain ID")
|
data/lib/remitmd/keccak.rb
CHANGED
|
@@ -77,7 +77,7 @@ module Remitmd
|
|
|
77
77
|
RC.each { |rc| keccak_round!(state, rc) }
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def keccak_round!(state, rc)
|
|
80
|
+
def keccak_round!(state, rc)
|
|
81
81
|
# Theta
|
|
82
82
|
c = Array.new(5) do |x|
|
|
83
83
|
state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]
|
data/lib/remitmd/mock.rb
CHANGED
|
@@ -148,7 +148,6 @@ module Remitmd
|
|
|
148
148
|
"deposit" => "0xMockDeposit00000000000000000000000001",
|
|
149
149
|
"fee_calculator" => "0xMockFeeCalc0000000000000000000000001",
|
|
150
150
|
"key_registry" => "0xMockKeyReg000000000000000000000000001",
|
|
151
|
-
"arbitration" => "0xMockArbitr000000000000000000000000001",
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
# Balance
|
|
@@ -181,6 +180,7 @@ module Remitmd
|
|
|
181
180
|
invoice_id = fetch!(b, :invoice_id)
|
|
182
181
|
inv = @state[:pending_invoices].delete(invoice_id)
|
|
183
182
|
raise not_found(RemitError::ESCROW_NOT_FOUND, invoice_id) unless inv
|
|
183
|
+
|
|
184
184
|
payee = (inv[:to_agent] || inv["to_agent"]).to_s
|
|
185
185
|
amount = decimal!(inv, :amount)
|
|
186
186
|
memo = (inv[:task] || inv["task"]).to_s
|
|
@@ -200,7 +200,7 @@ module Remitmd
|
|
|
200
200
|
in ["POST", path] if path.end_with?("/release") && path.include?("/escrows/")
|
|
201
201
|
id = extract_id(path, "/escrows/", "/release")
|
|
202
202
|
esc = @state[:escrows].fetch(id) { raise not_found(RemitError::ESCROW_NOT_FOUND, id) }
|
|
203
|
-
new_esc = update_escrow(esc, status: EscrowStatus::
|
|
203
|
+
new_esc = update_escrow(esc, status: EscrowStatus::COMPLETED)
|
|
204
204
|
@state[:escrows][id] = new_esc
|
|
205
205
|
tx = make_tx(from: esc.payer, to: esc.payee, amount: esc.amount)
|
|
206
206
|
@state[:transactions] << tx
|
|
@@ -269,7 +269,7 @@ module Remitmd
|
|
|
269
269
|
in ["POST", path] if path.end_with?("/close") && path.include?("/tabs/")
|
|
270
270
|
id = extract_id(path, "/tabs/", "/close")
|
|
271
271
|
tab = @state[:tabs].fetch(id) { raise not_found(RemitError::TAB_NOT_FOUND, id) }
|
|
272
|
-
new_tab = update_tab(tab, status: TabStatus::
|
|
272
|
+
new_tab = update_tab(tab, status: TabStatus::CLOSED)
|
|
273
273
|
@state[:tabs][id] = new_tab
|
|
274
274
|
tab_hash(new_tab)
|
|
275
275
|
|
|
@@ -311,7 +311,7 @@ module Remitmd
|
|
|
311
311
|
# Bounty submit
|
|
312
312
|
in ["POST", path] if path.end_with?("/submit") && path.include?("/bounties/")
|
|
313
313
|
id = extract_id(path, "/bounties/", "/submit")
|
|
314
|
-
|
|
314
|
+
@state[:bounties].fetch(id) { raise not_found(RemitError::BOUNTY_NOT_FOUND, id) }
|
|
315
315
|
{
|
|
316
316
|
"id" => 1,
|
|
317
317
|
"bounty_id" => id,
|
|
@@ -323,9 +323,9 @@ module Remitmd
|
|
|
323
323
|
|
|
324
324
|
# Bounty award
|
|
325
325
|
in ["POST", path] if path.end_with?("/award") && path.include?("/bounties/")
|
|
326
|
-
id
|
|
327
|
-
|
|
328
|
-
bnt
|
|
326
|
+
id = extract_id(path, "/bounties/", "/award")
|
|
327
|
+
_submission_id = (b[:submission_id] || b["submission_id"]).to_i
|
|
328
|
+
bnt = @state[:bounties].fetch(id) { raise not_found(RemitError::BOUNTY_NOT_FOUND, id) }
|
|
329
329
|
new_bnt = update_bounty(bnt, status: BountyStatus::AWARDED)
|
|
330
330
|
@state[:bounties][id] = new_bnt
|
|
331
331
|
bounty_hash(new_bnt)
|
|
@@ -621,6 +621,7 @@ module Remitmd
|
|
|
621
621
|
def check_balance!(amount)
|
|
622
622
|
bal = @state[:balance]
|
|
623
623
|
return if bal >= amount
|
|
624
|
+
|
|
624
625
|
raise RemitError.new(
|
|
625
626
|
RemitError::INSUFFICIENT_FUNDS,
|
|
626
627
|
"Insufficient balance: have #{bal.to_s("F")} USDC, need #{amount.to_s("F")} USDC",
|