Skip to content

Commit f737729

Browse files
committed
Merge remote-tracking branch 'rad/patches/b0f3ac7e932935056cd6fc89f09aab1bfb386e12'
2 parents 453f673 + 678127d commit f737729

15 files changed

Lines changed: 382 additions & 30 deletions

.github/workflows/dapp-ipfs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
PUBLIC_DELEGATION_API_URL: ${{ vars.PUBLIC_DELEGATION_API_URL }}
5050
PUBLIC_SOROBAN_NETWORK_PASSPHRASE: ${{ vars.PUBLIC_SOROBAN_NETWORK_PASSPHRASE }}
5151
PUBLIC_SOROBAN_RPC_URL: ${{ vars.PUBLIC_SOROBAN_RPC_URL }}
52+
PUBLIC_STELLAR_REGISTRY_CONTRACT_ID: ${{ vars.PUBLIC_STELLAR_REGISTRY_CONTRACT_ID }}
5253
PUBLIC_HORIZON_URL: ${{ vars.PUBLIC_HORIZON_URL }}
5354
PUBLIC_TANSU_CONTRACT_ID: ${{ vars.PUBLIC_TANSU_CONTRACT_ID }}
5455
PUBLIC_TANSU_OWNER_ID: ${{ vars.PUBLIC_TANSU_OWNER_ID }}

.github/workflows/e2e.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
PUBLIC_DELEGATION_API_URL: "https://ipfs-testnet.tansu.dev"
2626
PUBLIC_SOROBAN_RPC_URL: "https://soroban-testnet.stellar.org:443"
2727
PUBLIC_SOROBAN_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
28+
PUBLIC_STELLAR_REGISTRY_CONTRACT_ID: "CBFFTTX7QKA76FS4LHHQG54BC7JF5RMEX4RTNNJ5KEL76LYHVO3E3OEE"
2829
PUBLIC_HORIZON_URL: "https://horizon-testnet.stellar.org"
2930
PUBLIC_TANSU_CONTRACT_ID: "CTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"
3031
PUBLIC_TANSU_OWNER_ID: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
PUBLIC_DELEGATION_API_URL: "https://ipfs-testnet.tansu.dev"
4747
PUBLIC_SOROBAN_RPC_URL: "https://soroban-testnet.stellar.org:443"
4848
PUBLIC_SOROBAN_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
49+
PUBLIC_STELLAR_REGISTRY_CONTRACT_ID: "CBFFTTX7QKA76FS4LHHQG54BC7JF5RMEX4RTNNJ5KEL76LYHVO3E3OEE"
4950
PUBLIC_HORIZON_URL: "https://horizon-testnet.stellar.org"
5051
PUBLIC_TANSU_CONTRACT_ID: "CTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"
5152
PUBLIC_TANSU_OWNER_ID: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"

dapp/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
PUBLIC_SOROBAN_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
44
PUBLIC_SOROBAN_RPC_URL="https://soroban-testnet.stellar.org:443"
5+
# Stellar Registry root contract on the configured network (testnet).
6+
PUBLIC_STELLAR_REGISTRY_CONTRACT_ID="CBFFTTX7QKA76FS4LHHQG54BC7JF5RMEX4RTNNJ5KEL76LYHVO3E3OEE"
57
PUBLIC_HORIZON_URL=https://horizon-testnet.stellar.org
68

79
PUBLIC_TANSU_CONTRACT_ID="CBXKUSLQPVF35FYURR5C42BPYA5UOVDXX2ELKIM2CAJMCI6HXG2BHGZA"
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { useCallback, useRef, useState } from "react";
2+
import Input from "components/utils/Input";
3+
import {
4+
getContractByName,
5+
STELLAR_REGISTRY_URL,
6+
type RegistryContract,
7+
} from "@service/StellarRegistryService";
8+
9+
interface ContractNameSearchProps {
10+
/** Called with the resolved contract when the user picks a result. */
11+
onSelect: (contract: RegistryContract) => void;
12+
/** Placeholder for the search input. */
13+
placeholder?: string;
14+
disabled?: boolean;
15+
}
16+
17+
type Status = "idle" | "loading" | "found" | "not-registered" | "error";
18+
19+
function shortAddress(address: string): string {
20+
return address.length > 12
21+
? `${address.slice(0, 6)}${address.slice(-4)}`
22+
: address;
23+
}
24+
25+
/**
26+
* Exact-match contract name resolution backed by the Stellar Registry
27+
* smart contract (on-chain, no backend involved).
28+
*
29+
* The on-chain registry only supports exact name lookups, so there is no
30+
* suggestion list: the name is resolved on Enter or when leaving the field.
31+
* Unregistered names get an explicit "not on the registry" mark with a link
32+
* to the registry website so the author can double-check the name there.
33+
*
34+
* Deliberately decoupled from any feature: it only emits resolved contracts
35+
* through onSelect, so it can be embedded anywhere a contract address is
36+
* needed (e.g. tooling that must pick a deployed contract by name).
37+
*/
38+
export default function ContractNameSearch({
39+
onSelect,
40+
placeholder = "Exact contract name (Stellar Registry)",
41+
disabled = false,
42+
}: ContractNameSearchProps) {
43+
const [query, setQuery] = useState("");
44+
const [resolved, setResolved] = useState<RegistryContract | null>(null);
45+
const [status, setStatus] = useState<Status>("idle");
46+
const resolveSeq = useRef(0);
47+
48+
const resolve = useCallback(async (raw: string) => {
49+
const trimmed = raw.trim();
50+
const seq = ++resolveSeq.current;
51+
52+
if (!trimmed) {
53+
setResolved(null);
54+
setStatus("idle");
55+
return;
56+
}
57+
58+
setStatus("loading");
59+
try {
60+
const contract = await getContractByName(trimmed);
61+
if (resolveSeq.current !== seq) return; // a newer lookup superseded it
62+
if (contract) {
63+
setResolved(contract);
64+
setStatus("found");
65+
} else {
66+
setResolved(null);
67+
setStatus("not-registered");
68+
}
69+
} catch {
70+
if (resolveSeq.current !== seq) return;
71+
setResolved(null);
72+
setStatus("error");
73+
}
74+
}, []);
75+
76+
// Enter resolves immediately; leaving the field resolves what is typed.
77+
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
78+
if (event.key === "Enter") {
79+
event.preventDefault();
80+
void resolve(query);
81+
}
82+
};
83+
84+
const handleBlur = () => {
85+
void resolve(query);
86+
};
87+
88+
const handleUse = () => {
89+
if (resolved) {
90+
onSelect(resolved);
91+
setQuery("");
92+
setResolved(null);
93+
setStatus("idle");
94+
}
95+
};
96+
97+
const showNotRegistered = status === "not-registered";
98+
const showFound = status === "found" && resolved !== null;
99+
100+
return (
101+
<div className="relative w-full">
102+
<Input
103+
placeholder={placeholder}
104+
value={query}
105+
disabled={disabled}
106+
onChange={(e) => setQuery(e.target.value)}
107+
onKeyDown={handleKeyDown}
108+
onBlur={handleBlur}
109+
/>
110+
111+
{status === "loading" && (
112+
<p className="mt-1 text-xs text-secondary">
113+
Resolving on the Stellar Registry…
114+
</p>
115+
)}
116+
117+
{showNotRegistered && (
118+
<p className="mt-1 text-xs text-red-500">
119+
{query.trim()}” is not on the Stellar Registry.{" "}
120+
<a
121+
href={STELLAR_REGISTRY_URL}
122+
target="_blank"
123+
rel="noopener noreferrer"
124+
className="underline"
125+
>
126+
Check the registry ↗
127+
</a>
128+
</p>
129+
)}
130+
131+
{status === "error" && (
132+
<p className="mt-1 text-xs text-red-500">
133+
Could not reach the Stellar Registry. Press Enter to retry, or paste
134+
the address manually.
135+
</p>
136+
)}
137+
138+
{showFound && (
139+
<div className="mt-2 flex flex-wrap justify-between items-center gap-2 p-2 border border-gray-300 rounded-md bg-white">
140+
<div>
141+
<span className="font-medium text-primary">
142+
{resolved!.contractName}
143+
</span>
144+
<span className="ml-2 text-xs text-secondary font-mono">
145+
{shortAddress(resolved!.contractId)}
146+
</span>
147+
</div>
148+
<button
149+
type="button"
150+
onClick={handleUse}
151+
className="px-3 py-1 text-sm rounded-md bg-primary text-white cursor-pointer"
152+
>
153+
Use this address
154+
</button>
155+
</div>
156+
)}
157+
</div>
158+
);
159+
}

dapp/src/components/EnhancedContractFunctionSelector.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99

1010
interface EnhancedContractFunctionSelectorProps {
1111
contractAddress: string;
12-
network: string;
1312
onFunctionSelect: (functionName: string, args: any[]) => void;
1413
selectedFunction?: string;
1514
initialArgs?: any[];
@@ -19,7 +18,6 @@ export const EnhancedContractFunctionSelector: React.FC<
1918
EnhancedContractFunctionSelectorProps
2019
> = ({
2120
contractAddress,
22-
network,
2321
onFunctionSelect,
2422
selectedFunction: initialSelectedFunction,
2523
initialArgs = [],
@@ -42,7 +40,7 @@ export const EnhancedContractFunctionSelector: React.FC<
4240
setSelectedFunction("");
4341
setArgs([]);
4442
}
45-
}, [contractAddress, network]);
43+
}, [contractAddress]);
4644

4745
// Update args when selected function changes
4846
useEffect(() => {
@@ -76,10 +74,7 @@ export const EnhancedContractFunctionSelector: React.FC<
7674
setError(null);
7775

7876
try {
79-
const contractFuncs = await getContractFunctions(
80-
contractAddress,
81-
network as "testnet" | "mainnet",
82-
);
77+
const contractFuncs = await getContractFunctions(contractAddress);
8378
setFunctions(contractFuncs);
8479
} catch (err: any) {
8580
console.warn("Contract introspection failed:", err.message);

dapp/src/components/page/proposal/CreateProposalModal.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,6 @@ const CreateProposalModal = () => {
767767
onXdrChange={() => setApproveXdrError(null)}
768768
onModeChange={() => setApproveContractError(null)}
769769
onRemove={() => setShowApproveOutcome(false)}
770-
network="testnet"
771770
/>
772771
) : (
773772
<div className="flex justify-center">
@@ -793,7 +792,6 @@ const CreateProposalModal = () => {
793792
mode={rejectMode}
794793
setMode={setRejectMode}
795794
onRemove={() => setShowRejectOutcome(false)}
796-
network="testnet"
797795
/>
798796
) : (
799797
<div className="flex justify-center">
@@ -819,7 +817,6 @@ const CreateProposalModal = () => {
819817
mode={cancelledMode}
820818
setMode={setCancelledMode}
821819
onRemove={() => setShowCancelledOutcome(false)}
822-
network="testnet"
823820
/>
824821
) : (
825822
<div className="flex justify-center">

dapp/src/components/page/proposal/OutcomeInput.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Input from "components/utils/Input";
44
import OutcomeModeSelector from "./OutcomeModeSelector";
55
import OutcomeTemplateSelector from "./OutcomeTemplateSelector";
66
import EnhancedContractFunctionSelector from "components/EnhancedContractFunctionSelector";
7+
import ContractNameSearch from "components/ContractNameSearch";
78
import { capitalizeFirstLetter } from "utils/utils";
89
import type { OutcomeContract } from "types/proposal";
910
import type { OutcomeType } from "constants/outcomeTemplates";
@@ -35,9 +36,6 @@ interface OutcomeInputProps {
3536
onXdrChange?: (value: string) => void;
3637
onModeChange?: (mode: "xdr" | "contract" | "none") => void;
3738
onRemove?: () => void;
38-
39-
// Network for contract explorer
40-
network?: string;
4139
}
4240

4341
const OutcomeInput = ({
@@ -57,7 +55,6 @@ const OutcomeInput = ({
5755
onXdrChange,
5856
onModeChange,
5957
onRemove,
60-
network = "testnet", // Default to testnet
6158
}: OutcomeInputProps) => {
6259
const handleModeChange = (newMode: "xdr" | "contract" | "none") => {
6360
setMode(newMode);
@@ -236,6 +233,20 @@ const OutcomeInput = ({
236233
Contract Function
237234
</p>
238235

236+
{/* Resolve a contract by its registered name via the Stellar Registry
237+
smart contract (exact match, on-chain). Fills the address
238+
below on selection. */}
239+
<div className="w-full flex flex-col gap-2">
240+
<label className="text-sm font-medium text-primary">
241+
Contract Name (Stellar Registry)
242+
</label>
243+
<ContractNameSearch
244+
onSelect={(contract) =>
245+
handleContractAddressChange(contract.contractId)
246+
}
247+
/>
248+
</div>
249+
239250
<div className="flex flex-col gap-2">
240251
<label className="text-sm font-medium text-primary">
241252
Contract Address
@@ -254,7 +265,6 @@ const OutcomeInput = ({
254265
{contractOutcome?.address && (
255266
<EnhancedContractFunctionSelector
256267
contractAddress={contractOutcome.address}
257-
network={network}
258268
onFunctionSelect={handleContractFunctionSelect}
259269
selectedFunction={contractOutcome?.execute_fn || ""}
260270
initialArgs={contractOutcome?.args || []}

dapp/src/components/page/proposal/ProposalDetail.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,7 @@ export const OutcomeDetail: React.FC<{
232232
}
233233

234234
try {
235-
const functions = await getContractFunctions(
236-
detail.contract.address,
237-
"testnet",
238-
);
235+
const functions = await getContractFunctions(detail.contract.address);
239236
const match = functions.find(
240237
(func) => func.name === detail.contract?.execute_fn,
241238
);

dapp/src/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
interface ImportMetaEnv {
55
readonly PUBLIC_SOROBAN_NETWORK_PASSPHRASE: string;
66
readonly PUBLIC_SOROBAN_RPC_URL: string;
7+
readonly PUBLIC_STELLAR_REGISTRY_CONTRACT_ID: string;
78
readonly PUBLIC_HORIZON_URL: string;
89
readonly PUBLIC_TANSU_CONTRACT_ID: string;
910
readonly PUBLIC_TANSU_OWNER_ID: string;

0 commit comments

Comments
 (0)