erpc-sdk 0.8.1 → 0.9.0
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/LICENSE +71 -0
- data/README.md +111 -14
- data/lib/erpc_sdk/bridge.rb +151 -47
- data/lib/erpc_sdk/bridge_local.rb +1836 -0
- data/lib/erpc_sdk/generated/bridge_capabilities.rb +3 -3
- data/lib/erpc_sdk/generated/token_catalog.rb +11 -4
- data/lib/erpc_sdk/token_catalog.rb +13 -3
- data/lib/erpc_sdk/version.rb +1 -1
- data/lib/erpc_sdk.rb +1 -0
- metadata +17 -2
data/lib/erpc_sdk/bridge.rb
CHANGED
|
@@ -30,6 +30,11 @@ module ERPC
|
|
|
30
30
|
STATUS_NOT_FOUND = "BRIDGE_STATUS_NOT_FOUND"
|
|
31
31
|
TIMEOUT = "BRIDGE_TIMEOUT"
|
|
32
32
|
ABORTED = "BRIDGE_ABORTED"
|
|
33
|
+
LOCAL_RPC_REQUIRED = "BRIDGE_LOCAL_RPC_REQUIRED"
|
|
34
|
+
SOURCE_RPC_TRANSPORT = "BRIDGE_SOURCE_RPC_TRANSPORT"
|
|
35
|
+
SOURCE_RPC_INVALID_RESPONSE = "BRIDGE_SOURCE_RPC_INVALID_RESPONSE"
|
|
36
|
+
LOCAL_PLAN_INVALID = "BRIDGE_LOCAL_PLAN_INVALID"
|
|
37
|
+
LOCAL_BUILD_INVALID = "BRIDGE_LOCAL_BUILD_INVALID"
|
|
33
38
|
|
|
34
39
|
BRIDGE_INVALID_ARGUMENT = INVALID_ARGUMENT
|
|
35
40
|
BRIDGE_UNSUPPORTED_ROUTE = UNSUPPORTED_ROUTE
|
|
@@ -44,6 +49,11 @@ module ERPC
|
|
|
44
49
|
BRIDGE_STATUS_NOT_FOUND = STATUS_NOT_FOUND
|
|
45
50
|
BRIDGE_TIMEOUT = TIMEOUT
|
|
46
51
|
BRIDGE_ABORTED = ABORTED
|
|
52
|
+
BRIDGE_LOCAL_RPC_REQUIRED = LOCAL_RPC_REQUIRED
|
|
53
|
+
BRIDGE_SOURCE_RPC_TRANSPORT = SOURCE_RPC_TRANSPORT
|
|
54
|
+
BRIDGE_SOURCE_RPC_INVALID_RESPONSE = SOURCE_RPC_INVALID_RESPONSE
|
|
55
|
+
BRIDGE_LOCAL_PLAN_INVALID = LOCAL_PLAN_INVALID
|
|
56
|
+
BRIDGE_LOCAL_BUILD_INVALID = LOCAL_BUILD_INVALID
|
|
47
57
|
|
|
48
58
|
ALL = [
|
|
49
59
|
INVALID_ARGUMENT,
|
|
@@ -58,7 +68,12 @@ module ERPC
|
|
|
58
68
|
BUILD_INVALID,
|
|
59
69
|
STATUS_NOT_FOUND,
|
|
60
70
|
TIMEOUT,
|
|
61
|
-
ABORTED
|
|
71
|
+
ABORTED,
|
|
72
|
+
LOCAL_RPC_REQUIRED,
|
|
73
|
+
SOURCE_RPC_TRANSPORT,
|
|
74
|
+
SOURCE_RPC_INVALID_RESPONSE,
|
|
75
|
+
LOCAL_PLAN_INVALID,
|
|
76
|
+
LOCAL_BUILD_INVALID
|
|
62
77
|
].freeze
|
|
63
78
|
end
|
|
64
79
|
|
|
@@ -75,7 +90,12 @@ module ERPC
|
|
|
75
90
|
BridgeErrorCode::BUILD_INVALID => "Bridge provider build is invalid",
|
|
76
91
|
BridgeErrorCode::STATUS_NOT_FOUND => "Bridge status was not found",
|
|
77
92
|
BridgeErrorCode::TIMEOUT => "Bridge provider request timed out",
|
|
78
|
-
BridgeErrorCode::ABORTED => "Bridge provider request was aborted"
|
|
93
|
+
BridgeErrorCode::ABORTED => "Bridge provider request was aborted",
|
|
94
|
+
BridgeErrorCode::LOCAL_RPC_REQUIRED => "Bridge local source RPC is required",
|
|
95
|
+
BridgeErrorCode::SOURCE_RPC_TRANSPORT => "Bridge source RPC transport failed",
|
|
96
|
+
BridgeErrorCode::SOURCE_RPC_INVALID_RESPONSE => "Bridge source RPC response is invalid",
|
|
97
|
+
BridgeErrorCode::LOCAL_PLAN_INVALID => "Bridge local source-swap plan is invalid",
|
|
98
|
+
BridgeErrorCode::LOCAL_BUILD_INVALID => "Bridge local unsigned build is invalid"
|
|
79
99
|
}.freeze
|
|
80
100
|
|
|
81
101
|
class BridgeError < Error
|
|
@@ -122,7 +142,7 @@ module ERPC
|
|
|
122
142
|
|
|
123
143
|
attr_reader :builder_endpoint, :explorer_endpoint, :builder_api_key,
|
|
124
144
|
:allow_unauthenticated_build, :minimum_quote_validity_seconds,
|
|
125
|
-
:timeout, :http_adapter
|
|
145
|
+
:timeout, :http_adapter, :local_build
|
|
126
146
|
|
|
127
147
|
def initialize(builder_endpoint: DEFAULT_BUILDER_ENDPOINT,
|
|
128
148
|
explorer_endpoint: DEFAULT_EXPLORER_ENDPOINT,
|
|
@@ -131,6 +151,7 @@ module ERPC
|
|
|
131
151
|
minimum_quote_validity_seconds: 60,
|
|
132
152
|
timeout: DEFAULT_TIMEOUT,
|
|
133
153
|
http_adapter: nil,
|
|
154
|
+
local_build: nil,
|
|
134
155
|
**aliases)
|
|
135
156
|
if aliases.key?(:builderEndpoint)
|
|
136
157
|
builder_endpoint = aliases.delete(:builderEndpoint)
|
|
@@ -162,6 +183,9 @@ module ERPC
|
|
|
162
183
|
if aliases.key?(:httpAdapter)
|
|
163
184
|
http_adapter = aliases.delete(:httpAdapter)
|
|
164
185
|
end
|
|
186
|
+
if aliases.key?(:localBuild)
|
|
187
|
+
local_build = aliases.delete(:localBuild)
|
|
188
|
+
end
|
|
165
189
|
fail_bridge(BridgeErrorCode::INVALID_ARGUMENT) unless aliases.empty?
|
|
166
190
|
|
|
167
191
|
@builder_endpoint = normalize_endpoint(builder_endpoint)
|
|
@@ -180,11 +204,31 @@ module ERPC
|
|
|
180
204
|
if !http_adapter.nil? && !http_adapter.respond_to?(:request)
|
|
181
205
|
fail_bridge(BridgeErrorCode::INVALID_ARGUMENT)
|
|
182
206
|
end
|
|
207
|
+
if !local_build.nil?
|
|
208
|
+
unless local_build.is_a?(Hash) && local_build.keys.all? { |key| key.is_a?(String) || key.is_a?(Symbol) }
|
|
209
|
+
fail_bridge(BridgeErrorCode::INVALID_ARGUMENT)
|
|
210
|
+
end
|
|
211
|
+
allowed_local = %w[sourceSwapEndpoint source_swap_endpoint ethereumRpc ethereum_rpc solanaRpc solana_rpc]
|
|
212
|
+
fail_bridge(BridgeErrorCode::INVALID_ARGUMENT) unless local_build.keys.all? { |key| allowed_local.include?(key.to_s) }
|
|
213
|
+
local_build = local_build.each_with_object({}) do |(key, value), copy|
|
|
214
|
+
normalized_key = {
|
|
215
|
+
"sourceSwapEndpoint" => :source_swap_endpoint,
|
|
216
|
+
"source_swap_endpoint" => :source_swap_endpoint,
|
|
217
|
+
"ethereumRpc" => :ethereum_rpc,
|
|
218
|
+
"ethereum_rpc" => :ethereum_rpc,
|
|
219
|
+
"solanaRpc" => :solana_rpc,
|
|
220
|
+
"solana_rpc" => :solana_rpc
|
|
221
|
+
}.fetch(key.to_s)
|
|
222
|
+
fail_bridge(BridgeErrorCode::INVALID_ARGUMENT) if copy.key?(normalized_key)
|
|
223
|
+
copy[normalized_key] = value
|
|
224
|
+
end
|
|
225
|
+
end
|
|
183
226
|
|
|
184
227
|
@allow_unauthenticated_build = allow_unauthenticated_build
|
|
185
228
|
@minimum_quote_validity_seconds = minimum_quote_validity_seconds
|
|
186
229
|
@timeout = timeout.to_f
|
|
187
230
|
@http_adapter = http_adapter
|
|
231
|
+
@local_build = local_build
|
|
188
232
|
freeze
|
|
189
233
|
end
|
|
190
234
|
|
|
@@ -196,7 +240,8 @@ module ERPC
|
|
|
196
240
|
"builder_api_key=#{key.inspect} " \
|
|
197
241
|
"allow_unauthenticated_build=#{allow_unauthenticated_build.inspect} " \
|
|
198
242
|
"minimum_quote_validity_seconds=#{minimum_quote_validity_seconds.inspect} " \
|
|
199
|
-
"timeout=#{timeout.inspect} http_adapter=#{adapter.inspect}
|
|
243
|
+
"timeout=#{timeout.inspect} http_adapter=#{adapter.inspect} " \
|
|
244
|
+
"local_build=#{local_build.nil? ? nil : '[configured]'}>"
|
|
200
245
|
end
|
|
201
246
|
|
|
202
247
|
alias to_s inspect
|
|
@@ -304,15 +349,17 @@ module ERPC
|
|
|
304
349
|
JsonNode = Struct.new(:value, :start, :end, :object_entries, :array_items, :raw_number,
|
|
305
350
|
keyword_init: true)
|
|
306
351
|
DirectionFacts = Struct.new(
|
|
307
|
-
:bridge_capability_id, :source_chain_id, :destination_chain_id,
|
|
352
|
+
:asset, :bridge_capability_id, :source_chain_id, :destination_chain_id,
|
|
308
353
|
:source_token_deployment_id, :destination_token_deployment_id,
|
|
309
354
|
:source_token_address, :destination_token_address,
|
|
310
355
|
:source_token_standard, :destination_token_standard,
|
|
311
356
|
:source_provider_chain_id, :destination_provider_chain_id,
|
|
312
357
|
:source_wormhole_chain_id, :destination_wormhole_chain_id,
|
|
313
|
-
:source_name, :destination_name, :
|
|
358
|
+
:source_name, :destination_name, :source_token_name, :destination_token_name,
|
|
359
|
+
:source_token_mint, :destination_token_mint,
|
|
314
360
|
:source_usdc_deployment_id, :source_usdc_address, :source_usdc_standard,
|
|
315
|
-
:swift_contract,
|
|
361
|
+
:swift_contract, :forwarder_function_selector, :jupiter_program_address,
|
|
362
|
+
:dependencies, :source_swap_required, keyword_init: true
|
|
316
363
|
)
|
|
317
364
|
NormalizedRoute = Struct.new(:request, :facts, :capability, keyword_init: true)
|
|
318
365
|
|
|
@@ -508,7 +555,10 @@ module ERPC
|
|
|
508
555
|
SOLANA_SWIFT_PROGRAM = "mayan34VedncxdK2XobtvWFDXQASUTBXhUVzt2kKgny"
|
|
509
556
|
ETHEREUM_FORWARDER = "0x337685fdab40d39bd02028545a4ffa7d287cc3e2"
|
|
510
557
|
SOLANA_JUPITER_V6 = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"
|
|
511
|
-
|
|
558
|
+
ETHEREUM_EURC_FORWARDER_SELECTOR = "0x30dedc57"
|
|
559
|
+
ETHEREUM_USDC_FORWARDER_SELECTOR = "0xe4269fc4"
|
|
560
|
+
ETHEREUM_FORWARDER_SELECTOR = ETHEREUM_EURC_FORWARDER_SELECTOR
|
|
561
|
+
MAYAN_USDC_MINT = "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM"
|
|
512
562
|
DEFAULT_BUILDER_ENDPOINT = MayanSwiftV2BridgeConfig::DEFAULT_BUILDER_ENDPOINT
|
|
513
563
|
DEFAULT_EXPLORER_ENDPOINT = MayanSwiftV2BridgeConfig::DEFAULT_EXPLORER_ENDPOINT
|
|
514
564
|
DEFAULT_TIMEOUT = MayanSwiftV2BridgeConfig::DEFAULT_TIMEOUT
|
|
@@ -528,6 +578,14 @@ module ERPC
|
|
|
528
578
|
"wormhole-guardian-messaging",
|
|
529
579
|
"mayan-explorer-indexer"
|
|
530
580
|
].freeze
|
|
581
|
+
DIRECT_USDC_DEPENDENCIES = [
|
|
582
|
+
"mayan-hosted-quote-api",
|
|
583
|
+
"mayan-hosted-transaction-builder",
|
|
584
|
+
"swift-auction-solvers",
|
|
585
|
+
"relayers",
|
|
586
|
+
"wormhole-guardian-messaging",
|
|
587
|
+
"mayan-explorer-indexer"
|
|
588
|
+
].freeze
|
|
531
589
|
|
|
532
590
|
EVM_ADDRESS = /\A0x[0-9a-fA-F]{40}\z/.freeze
|
|
533
591
|
EVM_HASH = /\A0x[0-9a-fA-F]{64}\z/.freeze
|
|
@@ -671,7 +729,8 @@ module ERPC
|
|
|
671
729
|
allow_unauthenticated_build: value.allow_unauthenticated_build,
|
|
672
730
|
minimum_quote_validity_seconds: value.minimum_quote_validity_seconds,
|
|
673
731
|
timeout: value.timeout,
|
|
674
|
-
http_adapter: injected_adapter
|
|
732
|
+
http_adapter: injected_adapter,
|
|
733
|
+
local_build: value.local_build
|
|
675
734
|
)
|
|
676
735
|
end
|
|
677
736
|
elsif value.is_a?(Hash)
|
|
@@ -683,7 +742,8 @@ module ERPC
|
|
|
683
742
|
"allowUnauthenticatedBuild" => :allow_unauthenticated_build,
|
|
684
743
|
"minimumQuoteValiditySeconds" => :minimum_quote_validity_seconds,
|
|
685
744
|
"timeoutMs" => :timeout_ms,
|
|
686
|
-
"httpAdapter" => :http_adapter
|
|
745
|
+
"httpAdapter" => :http_adapter,
|
|
746
|
+
"localBuild" => :local_build
|
|
687
747
|
}
|
|
688
748
|
normalized = {}
|
|
689
749
|
source.each do |key, item|
|
|
@@ -754,39 +814,63 @@ module ERPC
|
|
|
754
814
|
value
|
|
755
815
|
end
|
|
756
816
|
|
|
757
|
-
def direction_facts(source_chain_id, destination_chain_id)
|
|
817
|
+
def direction_facts(source_chain_id, destination_chain_id, source_token_id, destination_token_id)
|
|
758
818
|
if source_chain_id == ETHEREUM_CHAIN_ID && destination_chain_id == SOLANA_CHAIN_ID
|
|
819
|
+
is_eurc = source_token_id == ETHEREUM_EURC_DEPLOYMENT_ID && destination_token_id == SOLANA_EURC_DEPLOYMENT_ID
|
|
820
|
+
is_usdc = source_token_id == ETHEREUM_USDC_DEPLOYMENT_ID && destination_token_id == SOLANA_USDC_DEPLOYMENT_ID
|
|
821
|
+
fail_bridge(BridgeErrorCode::UNSUPPORTED_ROUTE) unless is_eurc || is_usdc
|
|
822
|
+
asset = is_eurc ? "eurc" : "usdc"
|
|
759
823
|
return DirectionFacts.new(
|
|
760
|
-
|
|
824
|
+
asset: asset,
|
|
825
|
+
bridge_capability_id: is_eurc ? "bridge-mayan-swift-v2-eurc-eth-sol" : "bridge-mayan-swift-v2-usdc-eth-sol",
|
|
761
826
|
source_chain_id: source_chain_id, destination_chain_id: destination_chain_id,
|
|
762
|
-
source_token_deployment_id:
|
|
763
|
-
|
|
764
|
-
|
|
827
|
+
source_token_deployment_id: source_token_id, destination_token_deployment_id: destination_token_id,
|
|
828
|
+
source_token_address: is_eurc ? ETHEREUM_EURC_ADDRESS : ETHEREUM_USDC_ADDRESS,
|
|
829
|
+
destination_token_address: is_eurc ? SOLANA_EURC_ADDRESS : SOLANA_USDC_ADDRESS,
|
|
765
830
|
source_token_standard: "erc20", destination_token_standard: "spl-token",
|
|
766
831
|
source_provider_chain_id: ETHEREUM_PROVIDER_CHAIN_ID, destination_provider_chain_id: SOLANA_PROVIDER_CHAIN_ID,
|
|
767
832
|
source_wormhole_chain_id: ETHEREUM_WORMHOLE_CHAIN_ID, destination_wormhole_chain_id: SOLANA_WORMHOLE_CHAIN_ID,
|
|
768
833
|
source_name: ETHEREUM_NAME, destination_name: SOLANA_NAME,
|
|
769
|
-
|
|
834
|
+
source_token_name: is_eurc ? "EuroC" : "USD Coin",
|
|
835
|
+
destination_token_name: is_eurc ? "EuroC" : "USD Coin",
|
|
836
|
+
source_token_mint: is_eurc ? "" : MAYAN_USDC_MINT,
|
|
837
|
+
destination_token_mint: is_eurc ? SOLANA_EURC_ADDRESS : SOLANA_USDC_ADDRESS,
|
|
770
838
|
source_usdc_deployment_id: ETHEREUM_USDC_DEPLOYMENT_ID,
|
|
771
839
|
source_usdc_address: ETHEREUM_USDC_ADDRESS, source_usdc_standard: "erc20",
|
|
772
|
-
swift_contract: ETHEREUM_SWIFT_CONTRACT
|
|
840
|
+
swift_contract: ETHEREUM_SWIFT_CONTRACT,
|
|
841
|
+
forwarder_function_selector: is_eurc ? ETHEREUM_EURC_FORWARDER_SELECTOR : ETHEREUM_USDC_FORWARDER_SELECTOR,
|
|
842
|
+
jupiter_program_address: nil,
|
|
843
|
+
dependencies: is_eurc ? BASE_DEPENDENCIES : DIRECT_USDC_DEPENDENCIES,
|
|
844
|
+
source_swap_required: is_eurc
|
|
773
845
|
)
|
|
774
846
|
end
|
|
775
847
|
if source_chain_id == SOLANA_CHAIN_ID && destination_chain_id == ETHEREUM_CHAIN_ID
|
|
848
|
+
is_eurc = source_token_id == SOLANA_EURC_DEPLOYMENT_ID && destination_token_id == ETHEREUM_EURC_DEPLOYMENT_ID
|
|
849
|
+
is_usdc = source_token_id == SOLANA_USDC_DEPLOYMENT_ID && destination_token_id == ETHEREUM_USDC_DEPLOYMENT_ID
|
|
850
|
+
fail_bridge(BridgeErrorCode::UNSUPPORTED_ROUTE) unless is_eurc || is_usdc
|
|
851
|
+
asset = is_eurc ? "eurc" : "usdc"
|
|
776
852
|
return DirectionFacts.new(
|
|
777
|
-
|
|
853
|
+
asset: asset,
|
|
854
|
+
bridge_capability_id: is_eurc ? "bridge-mayan-swift-v2-eurc-sol-eth" : "bridge-mayan-swift-v2-usdc-sol-eth",
|
|
778
855
|
source_chain_id: source_chain_id, destination_chain_id: destination_chain_id,
|
|
779
|
-
source_token_deployment_id:
|
|
780
|
-
|
|
781
|
-
|
|
856
|
+
source_token_deployment_id: source_token_id, destination_token_deployment_id: destination_token_id,
|
|
857
|
+
source_token_address: is_eurc ? SOLANA_EURC_ADDRESS : SOLANA_USDC_ADDRESS,
|
|
858
|
+
destination_token_address: is_eurc ? ETHEREUM_EURC_ADDRESS : ETHEREUM_USDC_ADDRESS,
|
|
782
859
|
source_token_standard: "spl-token", destination_token_standard: "erc20",
|
|
783
860
|
source_provider_chain_id: SOLANA_PROVIDER_CHAIN_ID, destination_provider_chain_id: ETHEREUM_PROVIDER_CHAIN_ID,
|
|
784
861
|
source_wormhole_chain_id: SOLANA_WORMHOLE_CHAIN_ID, destination_wormhole_chain_id: ETHEREUM_WORMHOLE_CHAIN_ID,
|
|
785
862
|
source_name: SOLANA_NAME, destination_name: ETHEREUM_NAME,
|
|
786
|
-
|
|
863
|
+
source_token_name: is_eurc ? "EuroC" : "USD Coin",
|
|
864
|
+
destination_token_name: is_eurc ? "EuroC" : "USD Coin",
|
|
865
|
+
source_token_mint: is_eurc ? SOLANA_EURC_ADDRESS : SOLANA_USDC_ADDRESS,
|
|
866
|
+
destination_token_mint: is_eurc ? "" : MAYAN_USDC_MINT,
|
|
787
867
|
source_usdc_deployment_id: SOLANA_USDC_DEPLOYMENT_ID,
|
|
788
868
|
source_usdc_address: SOLANA_USDC_ADDRESS, source_usdc_standard: "spl-token",
|
|
789
|
-
swift_contract: SOLANA_SWIFT_PROGRAM
|
|
869
|
+
swift_contract: SOLANA_SWIFT_PROGRAM,
|
|
870
|
+
forwarder_function_selector: nil,
|
|
871
|
+
jupiter_program_address: is_eurc ? SOLANA_JUPITER_V6 : nil,
|
|
872
|
+
dependencies: is_eurc ? BASE_DEPENDENCIES + ["jupiter-v6-source-swap"] : DIRECT_USDC_DEPENDENCIES,
|
|
873
|
+
source_swap_required: is_eurc
|
|
790
874
|
)
|
|
791
875
|
end
|
|
792
876
|
fail_bridge(BridgeErrorCode::UNSUPPORTED_ROUTE)
|
|
@@ -834,10 +918,10 @@ module ERPC
|
|
|
834
918
|
"sourceUsdcAddress" => facts.source_usdc_address, "sourceUsdcStandard" => facts.source_usdc_standard,
|
|
835
919
|
"sourceUsdcDecimals" => 6, "swiftContract" => facts.swift_contract,
|
|
836
920
|
"forwarderAddress" => facts.source_chain_id == ETHEREUM_CHAIN_ID ? ETHEREUM_FORWARDER : nil,
|
|
837
|
-
"forwarderFunctionSelector" => facts.
|
|
838
|
-
"jupiterProgramAddress" => facts.
|
|
921
|
+
"forwarderFunctionSelector" => facts.forwarder_function_selector,
|
|
922
|
+
"jupiterProgramAddress" => facts.jupiter_program_address,
|
|
839
923
|
"builderEndpoint" => DEFAULT_BUILDER_ENDPOINT, "explorerEndpoint" => DEFAULT_EXPLORER_ENDPOINT,
|
|
840
|
-
"dependencies" => facts.
|
|
924
|
+
"dependencies" => facts.dependencies,
|
|
841
925
|
"status" => "active"
|
|
842
926
|
}
|
|
843
927
|
fail_bridge(BridgeErrorCode::UNSUPPORTED_ROUTE) unless strict_equal?(capability, expected)
|
|
@@ -853,8 +937,7 @@ module ERPC
|
|
|
853
937
|
destination_token_id = require_string(request["destinationTokenDeploymentId"], BridgeErrorCode::INVALID_ARGUMENT)
|
|
854
938
|
amount = normalize_positive_uint64(request["amountIn"], BridgeErrorCode::INVALID_ARGUMENT).first
|
|
855
939
|
slippage = normalize_slippage(request["slippageBps"], BridgeErrorCode::INVALID_ARGUMENT)
|
|
856
|
-
facts = direction_facts(source_chain_id, destination_chain_id)
|
|
857
|
-
fail_bridge(BridgeErrorCode::UNSUPPORTED_ROUTE) unless source_token_id == facts.source_token_deployment_id && destination_token_id == facts.destination_token_deployment_id
|
|
940
|
+
facts = direction_facts(source_chain_id, destination_chain_id, source_token_id, destination_token_id)
|
|
858
941
|
unless catalog_token_matches?(facts.source_token_deployment_id, facts.source_chain_id, facts.source_token_address, facts.source_token_standard) &&
|
|
859
942
|
catalog_token_matches?(facts.destination_token_deployment_id, facts.destination_chain_id, facts.destination_token_address, facts.destination_token_standard) &&
|
|
860
943
|
catalog_token_matches?(facts.source_usdc_deployment_id, facts.source_chain_id, facts.source_usdc_address, facts.source_usdc_standard)
|
|
@@ -934,12 +1017,12 @@ module ERPC
|
|
|
934
1017
|
EVM_ADDRESS.match?(value) ? value.downcase : value
|
|
935
1018
|
end
|
|
936
1019
|
|
|
937
|
-
def provider_token(node, address:, standard:, chain_id:, wormhole_chain_id:, mint:)
|
|
1020
|
+
def provider_token(node, address:, standard:, chain_id:, wormhole_chain_id:, mint:, name:)
|
|
938
1021
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless node.object_entries && node.value.is_a?(Hash)
|
|
939
1022
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_address_equals?(provider_string(node, "contract"), address)
|
|
940
1023
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(node, "mint", allow_empty: true) == mint
|
|
941
1024
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_address_equals?(provider_string(node, "realOriginContractAddress"), address)
|
|
942
|
-
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(node, "name") ==
|
|
1025
|
+
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(node, "name") == name
|
|
943
1026
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(node, "standard") == standard
|
|
944
1027
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_integer(node, "chainId") == chain_id
|
|
945
1028
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_integer(node, "wChainId") == wormhole_chain_id
|
|
@@ -972,8 +1055,8 @@ module ERPC
|
|
|
972
1055
|
deadline = provider_uint64(node, "deadline64", positive: true)
|
|
973
1056
|
ensure_quote_deadline(deadline.last)
|
|
974
1057
|
|
|
975
|
-
provider_token(required_node(node, "fromToken"), address: facts.source_token_address, standard: facts.source_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl", chain_id: facts.source_provider_chain_id, wormhole_chain_id: facts.source_wormhole_chain_id, mint: facts.
|
|
976
|
-
provider_token(required_node(node, "toToken"), address: facts.destination_token_address, standard: facts.destination_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl", chain_id: facts.destination_provider_chain_id, wormhole_chain_id: facts.destination_wormhole_chain_id, mint: facts.
|
|
1058
|
+
provider_token(required_node(node, "fromToken"), address: facts.source_token_address, standard: facts.source_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl", chain_id: facts.source_provider_chain_id, wormhole_chain_id: facts.source_wormhole_chain_id, mint: facts.source_token_mint, name: facts.source_token_name)
|
|
1059
|
+
provider_token(required_node(node, "toToken"), address: facts.destination_token_address, standard: facts.destination_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl", chain_id: facts.destination_provider_chain_id, wormhole_chain_id: facts.destination_wormhole_chain_id, mint: facts.destination_token_mint, name: facts.destination_token_name)
|
|
977
1060
|
provider_standard = facts.source_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl"
|
|
978
1061
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_address(node, "swiftInputContract", facts.source_usdc_address) == facts.source_usdc_address
|
|
979
1062
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(node, "swiftInputContractStandard") == provider_standard
|
|
@@ -982,20 +1065,24 @@ module ERPC
|
|
|
982
1065
|
|
|
983
1066
|
middle = required_node(node, "minMiddleAmount")
|
|
984
1067
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless middle.raw_number && middle.value.is_a?(Numeric) && middle.value.finite? && middle.value.positive?
|
|
985
|
-
|
|
1068
|
+
evm_router = object_value(node, "evmSwapRouterAddress")
|
|
1069
|
+
if facts.source_swap_required && facts.source_chain_id == ETHEREUM_CHAIN_ID
|
|
986
1070
|
router_address = normalize_evm_address(provider_string(node, "evmSwapRouterAddress"), BridgeErrorCode::PROVIDER_INVALID_RESPONSE)
|
|
987
1071
|
router_kind = "provider-selected-evm"
|
|
988
|
-
|
|
1072
|
+
elsif facts.source_swap_required
|
|
989
1073
|
router_address = SOLANA_JUPITER_V6
|
|
990
1074
|
router_kind = "jupiter-v6"
|
|
991
|
-
evm_router = object_value(node, "evmSwapRouterAddress")
|
|
992
1075
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless evm_router == :__bridge_missing__ || evm_router.nil?
|
|
1076
|
+
else
|
|
1077
|
+
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless evm_router == :__bridge_missing__ || evm_router.nil?
|
|
1078
|
+
router_address = nil
|
|
1079
|
+
router_kind = nil
|
|
993
1080
|
end
|
|
994
1081
|
quote_id = provider_string(node, "quoteId")
|
|
995
1082
|
signature = provider_string(node, "signature")
|
|
996
1083
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless QUOTE_ID.match?(quote_id) && EVM_SIGNATURE.match?(signature)
|
|
997
1084
|
|
|
998
|
-
dependencies = facts.
|
|
1085
|
+
dependencies = facts.dependencies
|
|
999
1086
|
{
|
|
1000
1087
|
"quoteKind" => "mayan-swift-v2", "providerId" => "mayan-swift-v2",
|
|
1001
1088
|
"sourceChainId" => request.fetch("sourceChainId"), "destinationChainId" => request.fetch("destinationChainId"),
|
|
@@ -1006,7 +1093,8 @@ module ERPC
|
|
|
1006
1093
|
"deadline" => deadline.first, "slippageBps" => request.fetch("slippageBps"),
|
|
1007
1094
|
"quoteId" => quote_id.downcase, "providerSignature" => signature.downcase,
|
|
1008
1095
|
"sourceSwap" => {
|
|
1009
|
-
"required" =>
|
|
1096
|
+
"required" => facts.source_swap_required,
|
|
1097
|
+
"inputTokenDeploymentId" => facts.source_swap_required ? facts.source_token_deployment_id : facts.source_usdc_deployment_id,
|
|
1010
1098
|
"intermediateTokenDeploymentId" => facts.source_usdc_deployment_id,
|
|
1011
1099
|
"intermediateTokenAddress" => facts.source_usdc_address,
|
|
1012
1100
|
"intermediateTokenStandard" => provider_standard, "intermediateTokenDecimals" => 6,
|
|
@@ -1110,8 +1198,14 @@ module ERPC
|
|
|
1110
1198
|
fail_bridge(code) unless quote["quoteKind"] == "mayan-swift-v2" && quote["providerId"] == "mayan-swift-v2"
|
|
1111
1199
|
source_chain = require_string(quote["sourceChainId"], code)
|
|
1112
1200
|
destination_chain = require_string(quote["destinationChainId"], code)
|
|
1113
|
-
|
|
1114
|
-
|
|
1201
|
+
source_token_id = require_string(quote["sourceTokenDeploymentId"], code)
|
|
1202
|
+
destination_token_id = require_string(quote["destinationTokenDeploymentId"], code)
|
|
1203
|
+
facts = begin
|
|
1204
|
+
direction_facts(source_chain, destination_chain, source_token_id, destination_token_id)
|
|
1205
|
+
rescue BridgeError => error
|
|
1206
|
+
fail_bridge(code) if error.code == BridgeErrorCode::UNSUPPORTED_ROUTE
|
|
1207
|
+
ERPC.raise_safe_bridge_error(error)
|
|
1208
|
+
end
|
|
1115
1209
|
amount = normalize_positive_uint64(quote["amountIn"], code)
|
|
1116
1210
|
expected_amount = normalize_positive_uint64(quote["expectedAmountOut"], code)
|
|
1117
1211
|
minimum_amount = normalize_positive_uint64(quote["minimumAmountOut"], code)
|
|
@@ -1126,9 +1220,13 @@ module ERPC
|
|
|
1126
1220
|
source_swap = require_record(quote["sourceSwap"], code)
|
|
1127
1221
|
exact_keys(source_swap, SOURCE_SWAP_KEYS, code)
|
|
1128
1222
|
provider_standard = facts.source_chain_id == ETHEREUM_CHAIN_ID ? "erc20" : "spl"
|
|
1129
|
-
fail_bridge(code) unless source_swap["required"] ==
|
|
1223
|
+
fail_bridge(code) unless source_swap["required"] == facts.source_swap_required && source_swap["inputTokenDeploymentId"] == (facts.source_swap_required ? facts.source_token_deployment_id : facts.source_usdc_deployment_id) && source_swap["intermediateTokenDeploymentId"] == facts.source_usdc_deployment_id && source_swap["intermediateTokenAddress"] == facts.source_usdc_address && source_swap["intermediateTokenStandard"] == provider_standard && source_swap["intermediateTokenDecimals"] == 6
|
|
1130
1224
|
provider_minimum = require_string(source_swap["providerMinimumAmount"], code)
|
|
1131
|
-
if facts.
|
|
1225
|
+
if !facts.source_swap_required
|
|
1226
|
+
fail_bridge(code) unless source_swap["routerKind"].nil? && source_swap["routerAddress"].nil?
|
|
1227
|
+
normalized_router = nil
|
|
1228
|
+
router_kind = nil
|
|
1229
|
+
elsif facts.source_chain_id == ETHEREUM_CHAIN_ID
|
|
1132
1230
|
router = source_swap["routerAddress"]
|
|
1133
1231
|
fail_bridge(code) unless source_swap["routerKind"] == "provider-selected-evm" && router.is_a?(String) && EVM_ADDRESS.match?(router) && router.downcase != "0x#{'0' * 40}"
|
|
1134
1232
|
normalized_router = router.downcase
|
|
@@ -1138,7 +1236,7 @@ module ERPC
|
|
|
1138
1236
|
normalized_router = SOLANA_JUPITER_V6
|
|
1139
1237
|
router_kind = "jupiter-v6"
|
|
1140
1238
|
end
|
|
1141
|
-
dependencies = facts.
|
|
1239
|
+
dependencies = facts.dependencies
|
|
1142
1240
|
fail_bridge(code) unless source_swap.is_a?(Hash) && quote["dependencies"].is_a?(Array) && strict_equal?(quote["dependencies"], dependencies)
|
|
1143
1241
|
raw = require_string(quote["rawSignedQuoteJson"], code)
|
|
1144
1242
|
fail_bridge(code) if raw.bytesize > MAX_RAW_QUOTE_BYTES
|
|
@@ -1152,7 +1250,8 @@ module ERPC
|
|
|
1152
1250
|
"deadline" => deadline.first, "slippageBps" => slippage,
|
|
1153
1251
|
"quoteId" => quote_id.downcase, "providerSignature" => signature.downcase,
|
|
1154
1252
|
"sourceSwap" => {
|
|
1155
|
-
"required" =>
|
|
1253
|
+
"required" => facts.source_swap_required,
|
|
1254
|
+
"inputTokenDeploymentId" => facts.source_swap_required ? facts.source_token_deployment_id : facts.source_usdc_deployment_id,
|
|
1156
1255
|
"intermediateTokenDeploymentId" => facts.source_usdc_deployment_id,
|
|
1157
1256
|
"intermediateTokenAddress" => facts.source_usdc_address,
|
|
1158
1257
|
"intermediateTokenStandard" => provider_standard, "intermediateTokenDecimals" => 6,
|
|
@@ -1193,7 +1292,7 @@ module ERPC
|
|
|
1193
1292
|
value.is_a?(String) && (value == "0" || value.match?(/\A0x0+\z/i))
|
|
1194
1293
|
end
|
|
1195
1294
|
|
|
1196
|
-
def validate_evm_build_result(wrapper, swapper_address)
|
|
1295
|
+
def validate_evm_build_result(wrapper, swapper_address, facts)
|
|
1197
1296
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_string(wrapper, "chainCategory") == "evm" && provider_string(wrapper, "quoteType") == "SWIFT"
|
|
1198
1297
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) if provider_boolean(wrapper, "gasless")
|
|
1199
1298
|
transaction = required_node(wrapper, "transaction")
|
|
@@ -1203,7 +1302,12 @@ module ERPC
|
|
|
1203
1302
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless provider_integer(transaction, "chainId") == 1
|
|
1204
1303
|
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless numeric_zero?(object_value(transaction, "value"))
|
|
1205
1304
|
data = provider_string(transaction, "data").downcase
|
|
1206
|
-
|
|
1305
|
+
selector = facts.forwarder_function_selector
|
|
1306
|
+
# EURC's swap-and-forward call has the provider's fixed 13-word
|
|
1307
|
+
# envelope. Direct USDC forwarding has nine static words plus the
|
|
1308
|
+
# dynamic bytes length word.
|
|
1309
|
+
minimum_words = facts.asset == "usdc" ? 10 : 13
|
|
1310
|
+
fail_bridge(BridgeErrorCode::PROVIDER_INVALID_RESPONSE) unless selector.is_a?(String) && HEX_BYTES.match?(data) && data.length.even? && data.start_with?(selector) && data.length >= 2 + 8 + minimum_words * 64
|
|
1207
1311
|
{
|
|
1208
1312
|
"kind" => "evm-unsigned-transaction", "chainId" => ETHEREUM_CHAIN_ID,
|
|
1209
1313
|
"from" => swapper_address, "to" => ETHEREUM_FORWARDER, "data" => data, "value" => "0"
|
|
@@ -1343,7 +1447,7 @@ module ERPC
|
|
|
1343
1447
|
swap_message = object_value(wrapper, "swapMessageV0Params")
|
|
1344
1448
|
fail_bridge(BridgeErrorCode::BUILD_INVALID) unless swap_message == :__bridge_missing__ || swap_message.nil?
|
|
1345
1449
|
transaction = begin
|
|
1346
|
-
facts.source_chain_id == ETHEREUM_CHAIN_ID ? validate_evm_build_result(wrapper, swapper_address) : validate_solana_build_result(wrapper, swapper_address)
|
|
1450
|
+
facts.source_chain_id == ETHEREUM_CHAIN_ID ? validate_evm_build_result(wrapper, swapper_address, facts) : validate_solana_build_result(wrapper, swapper_address)
|
|
1347
1451
|
rescue BridgeError => error
|
|
1348
1452
|
ERPC.raise_safe_bridge_error(error) if error.code == BridgeErrorCode::BUILD_INVALID
|
|
1349
1453
|
fail_bridge(BridgeErrorCode::BUILD_INVALID)
|
|
@@ -1353,7 +1457,7 @@ module ERPC
|
|
|
1353
1457
|
"quote" => deep_dup(quote), "sourceChainId" => quote.fetch("sourceChainId"),
|
|
1354
1458
|
"destinationChainId" => quote.fetch("destinationChainId"), "transaction" => transaction,
|
|
1355
1459
|
"allowance" => facts.source_chain_id == ETHEREUM_CHAIN_ID ? {
|
|
1356
|
-
"tokenDeploymentId" =>
|
|
1460
|
+
"tokenDeploymentId" => facts.source_token_deployment_id, "tokenAddress" => facts.source_token_address,
|
|
1357
1461
|
"owner" => swapper_address, "spender" => ETHEREUM_FORWARDER, "requiredAmount" => quote.fetch("amountIn")
|
|
1358
1462
|
} : nil,
|
|
1359
1463
|
"validation" => {
|