Docs menu

02 · 7 min read

Create and claim

What happens between a sender clicking Create gift link and a recipient holding the tokens: the link itself, the two transactions that create it, the three ways to receive, and how an unclaimed link comes home.

01Anatomy of a link#

URL
https://yield-links-hedera.vercel.app/claim#k=0x<64 hex characters>&c=296
PartMeaning
kThe private half of a throwaway key made in the sender's browser. Its address is the link's linkKey on chain.
cThe chain id: 296 is Hedera testnet, 295 is mainnet.
#Everything after it is the fragment. Browsers never send the fragment to a server, so the key never reaches the app, the relayer or a log.

The key is single use, forever. Links are never deleted, only moved to Claimed or Refunded, so an old signature can never be replayed against a new link that reuses a key.

A link is bearer value. Whoever has it can claim it to any address. Share it like cash, and keep expiries short.

02Creating a link#

The sender signs two transactions:

  1. Approve the source, not YieldLinks, for amount + ROUNDING_DUST. The source pulls the tokens, because it is the only contract that ever holds them.
  2. createLink(linkKey, token, amount, expiry, policy) with 1.5 HBAR attached as the prepaid network fee.
TypeScript
import { newKeypair, buildClaimUrl } from "~~/utils/yieldlinks";

const { privateKey, address: linkKey } = newKeypair();
// approve(source, amount + 10n), then:
await writeLinks({
  functionName: "createLink",
  args: [linkKey, SAUCE, amount, expiry, policy],
  value: parseEther("1.5"),
});
const url = buildClaimUrl(window.location.origin, privateKey, chainId);

Inside the call the source stakes the SAUCE in SaucerSwap's Infinity Pool (enter, SAUCE to xSAUCE) and YieldLinks mints the link its shares of the pool. Expiry must be in the future and at most 365 days away; the UI offers 1, 7 or 30 days.

03Who keeps the yield#

Chosen per link, and fixed once it is created.

PolicyRecipient getsYield goes to
RecipientPrincipal plus yieldThe recipient. The gift grows for them.
SenderThe principalThe sender, paid out in the same claim
CharityThe principalThe CHARITY address set at deploy (CHARITY_ADDRESS, defaults to the deployer)

04Three ways to receive#

OptionForWhat happens
New walletSomeone with nothingThe browser generates an ED25519 key. The relayer creates the account for it (0.1 HBAR welcome balance, unlimited token auto-association), then the claim pays it.
My walletAnyone with a wallet, including MetaMaskConnect, and the claim pays the connected address.
AddressPaying someone elsePaste any EVM address. It need not exist on Hedera yet: the HIP-904 airdrop creates it.

A generated wallet is shown once, in two forms: the 96-character DER key (302e…) that HashPack imports, and the 64-character raw key. It works in HashPack, Blade and Hedera tools, but not MetaMask, which only takes ECDSA keys. Anyone who wants MetaMask should choose My wallet.

05The relayer submits it#

The claim page never sends the private key anywhere. It signs a claim and posts the signature:

TypeScript
// EIP-712, domain { name: "YieldLinks", version: "1", chainId, verifyingContract }
// type Claim(address linkKey, address recipient)
const signature = await signClaim(privateKey, contract, chainId, recipient);
await fetch("/api/claim", {
  method: "POST",
  body: JSON.stringify({ chainId, linkKey, recipient, signature }),
});

/api/claim checks the link prepaid at least 1.35 HBAR, simulates the call, and only then sends claim. A bad claim costs the relayer nothing. On chain the contract recovers the signer, which must equal linkKey, so a watcher who swaps in their own recipient breaks the signature.

The claim prices the link's shares, unstakes from SaucerSwap, and pays through a HIP-904 airdrop, which creates and associates the recipient's account if needed. The prepaid fee goes to whoever submitted it.

06Cancel and refund#

WhenWho may call refundWhere it goes
Before expiryOnly the senderPrincipal, yield and the prepaid fee, back to the sender
After expiryAnyoneStill only the sender

Nothing needs a keeper or a schedule: an expired link just waits until someone calls refund. The sender cancels from My links.

07Life of a link#

State
None ──createLink──▶ Open ──claim (valid signature, before expiry)──▶ Claimed
                       │
                       └──refund (sender any time, anyone after expiry)──▶ Refunded

Claimed and Refunded are final. The link's shares are burned and its fee is zeroed in the same step, before any tokens move.

Next · 8 minContract reference