Skip to content

Commit a4f9ff4

Browse files
authored
[BA-3103] Utility for creating a link (deeplink, universal link, standard link) with a prolink (#175)
* add utility to convert prolink to baseapp universal link * add universal link to playground * address feedback * fix:lints * fix var name, return type, update tests * update playground * update playground * lint * update docstring
1 parent e47134a commit a4f9ff4

File tree

5 files changed

+487
-8
lines changed

5 files changed

+487
-8
lines changed

examples/testapp/src/pages/prolink-playground/index.page.tsx

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { decodeProlink, encodeProlink } from '@base-org/account';
1+
import { createProlinkUrl, decodeProlink, encodeProlink } from '@base-org/account';
22
import {
33
Accordion,
44
AccordionButton,
@@ -107,13 +107,17 @@ export default function ProlinkPlayground() {
107107
const [encodedPayload, setEncodedPayload] = useState('');
108108
const [error, setError] = useState<string | null>(null);
109109
const [decodedResult, setDecodedResult] = useState<unknown>(null);
110+
const [urlWithProlink, setUrlWithProlink] = useState('');
110111

111112
// Decode section
112113
const [decodeInput, setDecodeInput] = useState('');
113114
const [decodeLoading, setDecodeLoading] = useState(false);
114115
const [decodeError, setDecodeError] = useState<string | null>(null);
115116
const [decodeResult, setDecodeResult] = useState<unknown>(null);
116117

118+
// Link with Prolink section
119+
const [urlForLinkWithProlink, setUrlForLinkWithProlink] = useState('https://base.app/base-pay');
120+
117121
const generateProlink = async () => {
118122
setLoading(true);
119123
setError(null);
@@ -213,6 +217,10 @@ export default function ProlinkPlayground() {
213217
const payload = await encodeProlink(request);
214218
setEncodedPayload(payload);
215219

220+
// Generate link with prolink
221+
const urlWithProlink = createProlinkUrl(payload, urlForLinkWithProlink);
222+
setUrlWithProlink(urlWithProlink);
223+
216224
// Decode to verify
217225
const decoded = await decodeProlink(payload);
218226
setDecodedResult(decoded);
@@ -237,6 +245,28 @@ export default function ProlinkPlayground() {
237245
}
238246
};
239247

248+
const generateLinkWithProlink = () => {
249+
setLoading(true);
250+
setError(null);
251+
setUrlWithProlink('');
252+
253+
try {
254+
const urlWithProlink = createProlinkUrl(encodedPayload, urlForLinkWithProlink);
255+
setUrlWithProlink(urlWithProlink);
256+
} catch (err) {
257+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
258+
setError(errorMessage);
259+
toast({
260+
title: 'Error generating link with prolink',
261+
description: errorMessage,
262+
status: 'error',
263+
duration: 5000,
264+
});
265+
} finally {
266+
setLoading(false);
267+
}
268+
};
269+
240270
const copyToClipboard = () => {
241271
navigator.clipboard.writeText(encodedPayload);
242272
toast({
@@ -247,6 +277,16 @@ export default function ProlinkPlayground() {
247277
});
248278
};
249279

280+
const copyLinkWithProlinkToClipboard = () => {
281+
navigator.clipboard.writeText(urlWithProlink);
282+
toast({
283+
title: 'Copied!',
284+
description: 'Link copied to clipboard',
285+
status: 'success',
286+
duration: 2000,
287+
});
288+
};
289+
250290
const decodePayload = async () => {
251291
setDecodeLoading(true);
252292
setDecodeError(null);
@@ -299,6 +339,7 @@ export default function ProlinkPlayground() {
299339
<TabList>
300340
<Tab>Encode</Tab>
301341
<Tab>Decode</Tab>
342+
<Tab>Link with Prolink</Tab>
302343
</TabList>
303344

304345
<TabPanels>
@@ -616,6 +657,7 @@ export default function ProlinkPlayground() {
616657
<TabList>
617658
<Tab>Encoded Payload</Tab>
618659
<Tab>Decoded Result</Tab>
660+
<Tab>Link with Prolink</Tab>
619661
</TabList>
620662

621663
<TabPanels>
@@ -645,6 +687,27 @@ export default function ProlinkPlayground() {
645687
</Code>
646688
</Box>
647689
</TabPanel>
690+
691+
{/* Link with Prolink Tab */}
692+
<TabPanel>
693+
<VStack spacing={4} align="stretch">
694+
<HStack>
695+
<Box p={4} bg={codeBgColor} borderRadius="md" overflowX="auto">
696+
<Code display="block" whiteSpace="pre-wrap" wordBreak="break-all">
697+
{urlWithProlink}
698+
</Code>
699+
</Box>
700+
<Button size="sm" onClick={copyLinkWithProlinkToClipboard}>
701+
Copy Link
702+
</Button>
703+
</HStack>
704+
<Text fontSize="sm" color="gray.600">
705+
This link with prolink can be opened in the Base App to execute the
706+
transaction. See the "Link with Prolink" tab for customization
707+
options.
708+
</Text>
709+
</VStack>
710+
</TabPanel>
648711
</TabPanels>
649712
</Tabs>
650713
)}
@@ -720,7 +783,15 @@ export default function ProlinkPlayground() {
720783
<>
721784
<HStack>
722785
<Text fontWeight="bold">Method:</Text>
723-
<Text>{(decodeResult as { method: string }).method}</Text>
786+
<Text>
787+
{
788+
(
789+
decodeResult as {
790+
method: string;
791+
}
792+
).method
793+
}
794+
</Text>
724795
<Button size="sm" onClick={copyDecodedToClipboard}>
725796
Copy JSON
726797
</Button>
@@ -736,6 +807,95 @@ export default function ProlinkPlayground() {
736807
</Box>
737808
)}
738809
</TabPanel>
810+
811+
{/* Link with Prolink Tab */}
812+
<TabPanel px={0}>
813+
<Box borderWidth="1px" borderRadius="lg" p={6} bg={bgColor} borderColor={borderColor}>
814+
<VStack spacing={6} align="stretch">
815+
<FormControl>
816+
<FormLabel>Prolink Payload</FormLabel>
817+
<Input
818+
type="text"
819+
value={encodedPayload}
820+
onChange={(e) => setEncodedPayload(e.target.value)}
821+
placeholder="Paste encoded prolink payload here..."
822+
/>
823+
<Text fontSize="sm" color="gray.600">
824+
See the "Encode" tab to generate a prolink payload.
825+
</Text>
826+
</FormControl>
827+
828+
{/* URL for the link with the prolink */}
829+
<FormControl>
830+
<FormLabel>URL</FormLabel>
831+
<Input
832+
type="url"
833+
value={urlForLinkWithProlink}
834+
onChange={(e) => setUrlForLinkWithProlink(e.target.value)}
835+
placeholder="https://base.app/base-pay"
836+
/>
837+
</FormControl>
838+
839+
<Divider />
840+
841+
<Button
842+
colorScheme="blue"
843+
size="lg"
844+
onClick={generateLinkWithProlink}
845+
isLoading={loading}
846+
loadingText="Generating..."
847+
>
848+
Generate Link
849+
</Button>
850+
</VStack>
851+
</Box>
852+
853+
{/* Results */}
854+
{(urlWithProlink || error) && (
855+
<Box
856+
borderWidth="1px"
857+
borderRadius="lg"
858+
p={6}
859+
mt={6}
860+
bg={bgColor}
861+
borderColor={borderColor}
862+
>
863+
<VStack spacing={6} align="stretch">
864+
<Heading size="md">Results</Heading>
865+
866+
{error && (
867+
<Box
868+
p={4}
869+
bg="red.50"
870+
borderRadius="md"
871+
borderColor="red.200"
872+
borderWidth="1px"
873+
>
874+
<Text color="red.700" fontWeight="bold">
875+
Error:
876+
</Text>
877+
<Text color="red.600" mt={2}>
878+
{error}
879+
</Text>
880+
</Box>
881+
)}
882+
883+
{urlWithProlink && (
884+
<HStack>
885+
<Box p={4} bg={codeBgColor} borderRadius="md" overflowX="auto">
886+
<Code display="block" whiteSpace="pre-wrap" wordBreak="break-all">
887+
{urlWithProlink}
888+
</Code>
889+
</Box>
890+
<Button size="sm" onClick={copyLinkWithProlinkToClipboard}>
891+
Copy Link
892+
</Button>
893+
</HStack>
894+
)}
895+
</VStack>
896+
</Box>
897+
)}
898+
</TabPanel>
739899
</TabPanels>
740900
</Tabs>
741901
</VStack>

packages/account-sdk/src/index.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
// Copyright (c) 2018-2025 Coinbase, Inc. <https://www.coinbase.com/>
2-
export type { AppMetadata, Preference, ProviderInterface } from ':core/provider/interface.js';
2+
export type {
3+
AppMetadata,
4+
Preference,
5+
ProviderInterface,
6+
} from ':core/provider/interface.js';
37

48
export { createBaseAccountSDK } from './interface/builder/core/createBaseAccountSDK.js';
59

6-
export { getCryptoKeyAccount, removeCryptoKey } from './kms/crypto-key/index.js';
10+
export {
11+
getCryptoKeyAccount,
12+
removeCryptoKey,
13+
} from './kms/crypto-key/index.js';
714

815
export { PACKAGE_VERSION as VERSION } from './core/constants.js';
916

1017
export {
11-
CHAIN_IDS,
12-
TOKENS,
1318
base,
19+
CHAIN_IDS,
1420
getPaymentStatus,
1521
getSubscriptionStatus,
1622
pay,
1723
prepareCharge,
1824
subscribe,
25+
TOKENS,
1926
} from './interface/payment/index.js';
2027
export type {
2128
ChargeOptions,
@@ -40,5 +47,12 @@ export type {
4047
SubscriptionStatusOptions,
4148
} from './interface/payment/index.js';
4249

43-
export { decodeProlink, encodeProlink } from './interface/public-utilities/prolink/index.js';
44-
export type { ProlinkDecoded, ProlinkRequest } from './interface/public-utilities/prolink/index.js';
50+
export {
51+
createProlinkUrl,
52+
decodeProlink,
53+
encodeProlink,
54+
} from './interface/public-utilities/prolink/index.js';
55+
export type {
56+
ProlinkDecoded,
57+
ProlinkRequest,
58+
} from './interface/public-utilities/prolink/index.js';

0 commit comments

Comments
 (0)