Integrating Wallet Linking
Time to complete: 25-35 minutes
In this guide, we will showcase how to sign into an Embedded Wallet using traditional authentication like email or social login. Then, in order to link an external wallet address using Sequence Kit to your Embedded Wallet, we will sign a message which creates a queryable set of addresses for a single user profile stored off-chain privately. Finally, other features of the API will be delved into, like listing total wallets linked and removal of the state of linked wallets, both using signatures for privacy preserving means.
Demo
Quickstart
Try a quickstart application with a CLI command in your terminal to run locally:
Clone the repo
git clone https://github.com/0xsequence-demos/demo-embedded-wallet-linking.git && cd ./demo-embedded-wallet-linking
Copy environment variables
cp .example.env .env
Run locally
pnpm install && pnpm dev
Once you have tested out the example, simply update the .env
file with your Embedded Wallet configuration after creating a project with Builder:
Waas Config Key
used for the Embedded Wallet.Project Access Key
used for the Embedded Wallet.- How to obtain a
Google Client ID
for social authentication with Embedded Wallets.
Using the Wallet Linking API
1. Integrating the API Interface File
In order to begin using the API, an interface file is integrated into the cloned application so the Sequence API can be called upon conveniently.
The source code for the interface can be found here which then is instantiated with the existing Sequence server like such:
import {
API, // API interface
LinkedWallet // typed class
} from "./api.gen";
const api = new API("https://dev-api.sequence.app", fetch);
Simple examples using the API functions integrated in the repository using 0xsequence/waas
and wagmi
packages:
2. Link an external wallet to an embedded wallet
In order to link an external wallet (e.g. EOA, Sequence Universal Wallet, etc.) to an Embedded Wallet, we first need to authenticate the user with an embedded wallet which is categorized as the parent
wallet. Once authenticated, we would then authenticate them again using Sequence Kit where the user signs a message after authentication to validate ownership of that wallet. That message will be passed to the linkWallet
API endpoint, with the following typed arguments and return types (negating the optional values):
interface LinkWalletArgs {
parentWalletAddress: string // Address of the Embedded Wallet
parentWalletMessage: string // Message from the Embedded Wallet
parentWalletSignature: string // Signature from Embedded Wallet
linkedWalletAddress: string // External wallet address you want to link
linkedWalletMessage: string // Message from the external wallet
linkedWalletSignature: string // Signature from the external wallet
signatureChainId: string // ChainId which you want to verify, using 137 in the example.
linkedWalletType?: string // Optional value to identify what type of wallet is being connected, i.e., 'sequence'
}
interface LinkWalletReturn {
status: boolean
}
linkWallet(args: LinkWalletArgs, headers?: object, signal?: AbortSignal): Promise<LinkWalletReturn>
Sign Message to Attest to Owning an Address
There are two signatures required: one for the Embedded Wallet signing the Sequence Kit wallet address (linked wallet), and the linked wallet (from Sequence Kit) signing the parent wallet address (Embedded Wallet). You can sign any message presented to the user, as long as the message and signature are consistent with what is passed to the API later. For example, using the following code in React with Wagmi to obtain this information using Sequence:
import {
...
useSignMessage,
useAccount,
...
} from "wagmi";
function App() {
const { signMessageAsync } = useSignMessage();
const { address: kitWalletAddress } = useAccount();
const getSignaturesForLinking = async () => {
const parentWalletMessage = "linked wallet with address "
const linkedWalletMessage = "parent wallet with address "
const parentMessage = parentWalletMessage + kitWalletAddress
const parentWalletAddress = await sequenceWaas.getAddress()
const linkingMessage = "Link to " + linkedWalletMessage + parentWalletAddress
const response: any = await signMessageAsync({
message: linkingMessage,
})
const parentSignatureRes = await sequenceWaas.signMessage({
message: parentMessage
})
const parentSignature = parentSignatureRes.data.signature
const linkedSignature = response
const linkedWalletAddress = kitWalletAddress
return { parentWalletAddress, linkedWalletAddress, parentMessage, linkingMessage, parentSignature, linkedSignature }
}
}
Use API to Add Signature Payload & Parent Wallet Address
Call the previous getSignaturesForLinking
function and unpack the response to pass the responses into the api.linkWallet
function:
function App(){
const connections = useConnections();
...
const linkWallet = async () => {
const signaturesResult = await getSignaturesForLinking()
const { parentWalletAddress, linkedWalletAddress, parentMessage, linkingMessage, parentSignature, linkedSignature } = signaturesResult
const connectorName = connections[0]?.connector.name;
const response = await api.linkWallet({
signatureChainId: "137", // network chosen for both embedded wallet and sequence kit
linkedWalletType: connectorName,
parentWalletAddress,
parentWalletMessage: parentMessage,
parentWalletSignature: parentSignature,
linkedWalletAddress: linkedWalletAddress!,
linkedWalletMessage: linkingMessage,
linkedWalletSignature: linkedSignature,
})
console.log(`status: ${response.status}`)
}
}
3. Retrieving Linked Wallets
To protect the privacy of users, you can pass in a signature generated from the parent Embedded Wallet in order to retrieve the list of wallet addresses that is linked to a parent wallet address.
The API will return the linked wallet objects in the following format:
interface LinkedWallet {
id: number
walletType?: string
walletAddress: string
linkedWalletAddress: string
createdAt?: string
}
interface GetLinkedWalletsReturn {
linkedWallets: Array<LinkedWallet>
}
And this can be accomplished in the following way, by passing in a message, getting a signature from the parent Embedded Wallet, checking to see the signature is readable, then passing the variables: parentWalletAddress
,parentWalletMessage
,parentWalletSignature
and signatureChainId
to the API:
const getLinkedWallets = async () => {
const parentWalletAddress = await sequenceWaas.getAddress()
const message = "parent wallet with address " + parentWalletAddress
const signature = await sequenceWaas.signMessage({
message: message,
})
if (!signature.data.signature) {
console.error("Could not get signature from wallet to be linked")
throw new Error("Could not get signature from wallet to be linked")
}
const response = await api.getLinkedWallets({
parentWalletAddress: parentWalletAddress as `0x${string}`,
parentWalletMessage: message,
parentWalletSignature: signature.data.signature,
signatureChainId: "137",
})
response.linkedWallets.map((linkedWallet: LinkedWallet) => console.log(linkedWallet))
return response.linkedWallets
}
4. Removing Linked Wallets
Finally, if you no longer want to keep a wallet linked off-chain to a parent wallet, you can perform a signature by both wallet instances (Embedded Wallet & Sequence Kit linked wallet), and pass in the linked wallet address, like in step 6 in order to remove the linked state:
Sign Message to Attest to Removing a Linked Address
import {
...
useAccount,
useSignMessage
...
} from "wagmi";
function App() {
const { address: kitWalletAddress } = useAccount();
const { signMessageAsync } = useSignMessage();
const getSignaturesForUnlinking = async () => {
const parentWalletMessage = "linked wallet with address "
const linkedWalletMessage = "parent wallet with address "
const parentWalletAddress = await sequenceWaas.getAddress()
const linkingMessage = "Unlink from " + linkedWalletMessage + parentWalletAddress
const response: any = await signMessageAsync({
message: linkingMessage,
})
const parentSignatureRes = await sequenceWaas.signMessage({
message: parentMessage
})
const parentSignature = parentSignatureRes.data.signature
const linkedSignature = response
const linkedWalletAddress = kitWalletAddress
return { parentWalletAddress, linkedWalletAddress, parentMessage, linkingMessage, parentSignature, linkedSignature }
}
}
Use API to Add Signature Payload for Parent Address
function App(){
...
const unLinkWallet = async () => {
const signaturesResult = await getSignaturesForUnlinking()
const { parentWalletAddress, linkedWalletAddress, parentMessage, linkingMessage, parentSignature, linkedSignature } = signaturesResult;
const response = await api.removeLinkedWallet({
signatureChainId: "137", // network chosen for both Embedded Wallet and Sequence Kit
parentWalletAddress,
parentWalletMessage: parentMessage,
parentWalletSignature: parentSignature,
linkedWalletAddress: linkedWalletAddress!,
linkedWalletMessage: linkingMessage,
linkedWalletSignature: linkedSignature,
});
console.log(`status: ${response.status}`)
}
}
Conclusion
We've provided a sample application that allows you to authenticate a user with your embedded wallet configuration, validate a message from an external wallet, and then link these together using the Sequence Linking API. Furthermore, we went over how to use these API functions using simple examples from React & Wagmi. You can either use the standalone application we've provided or leverage the same flow in your own application from here.