Lewati ke konten utama

๐Ÿ”— Platform ร— PAY.ID Bridge

Goal: Make any platform's milestone escrow and PAY.ID's reputation system work as one unified experience.

Not replacing your platform with PAY.ID. Not replacing PAY.ID with your platform.

But: Blending both into a seamless composition.

This pattern works for Platform, Upwork, Fiverr, or any custom bounty/marketplace system.


The Philosophyโ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ USER EXPERIENCE โ”‚
โ”‚ โ”‚
โ”‚ Platform Bounty Card โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ ๐Ÿ’ฐ 5 ETH | 3 Milestones โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ Client: 0xabc... โญ 850 (Platformร—VRAN) โ”‚ โ”‚
โ”‚ โ”‚ Freelancer: 0xdef... โญ 720 โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ [Claim] [View Milestones] โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ–ผ โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Platform โ”‚ โ”‚ PAY.ID โ”‚ โ”‚ PAY.ID โ”‚
โ”‚ Escrow โ”‚ โ”‚ VRAN โ”‚ โ”‚ Rules โ”‚
โ”‚ (External) โ”‚ โ”‚ Reputation โ”‚ โ”‚ Engine โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Your Platform = Execution layer (escrow, milestones, disputes, payments) PAY.ID = Policy layer (reputation checks, rule evaluation, decision proofs)


What You Getโ€‹

FeaturePlatform AlonePAY.ID AlonePlatform ร— PAY.ID
Milestone escrowโœ…โœ… (simpler)โœ… Platform's rich features
Multi-arbiter disputeโœ…โŒ (single)โœ… Platform's system
Reputation scoringโœ… (5D)โœ… (VRAN)โœ… Blended 850
Anti-scam blacklistโŒโœ…โœ… Both checked
Policy-driven paymentsโŒโœ…โœ… Rules gate escrow
Spending limitsโŒโœ…โœ… Before bounty creation
Attestation gatingโŒโœ…โœ… KYC for high-value

Setup (One-Liner)โ€‹

import {
PayIDProvider,
DefaultReputationAdapter,
createCompositeIntegration,
} from 'payid-react';
import { createPlatformSdk } from 'your-platform-sdk'; // your platform's SDK

const platform = createPlatformSdk({ chainId: 31337 });

// Create PAY.ID VRAN adapter
const vranAdapter = new DefaultReputationAdapter(
publicClient,
walletClient,
contracts.vindexRegistry,
);

// Create composite: Platform (60%) + VRAN (40%)
const { reputationAdapter, escrowAdapter } = createCompositeIntegration({
platform,
vranAdapter,
platformWeight: 0.6,
vranWeight: 0.4,
});

function App() {
return (
<PayIDProvider
contracts={contracts}
reputationAdapter={reputationAdapter}
escrowAdapter={escrowAdapter}
>
<App />
</PayIDProvider>
);
}

How the Blend Worksโ€‹

Reputation Formulaโ€‹

Composite Score = PlatformScore ร— weight + VRANScore ร— (1 - weight)

Example:
Platform: 900 (great freelancer)
VRAN: 600 (new to PAY.ID network)
Weight: 0.6

Composite = 900 ร— 0.6 + 600 ร— 0.4 = 540 + 240 = 780

Fail-closed: If VRAN says isBlacklisted: true, composite score is irrelevant โ€” user is blocked.

What the Hook Returnsโ€‹

const { data } = useReputation({ target: freelancerAddress });
// data.score โ†’ 780 (composite)
// data.isTrusted โ†’ true (โ‰ฅ 700 threshold)
// data.isBlacklisted โ†’ false (both systems clear)

Escrow Flow with Policy Layerโ€‹

1. Client posts bounty
โ”œโ”€ Platform: createEscrow(freelancer, milestones, deadline)
โ””โ”€ PAY.ID: evaluate rule "can this client spend 5 ETH?"
โ””โ”€ Decision Proof generated, stored on bounty

2. Freelancer claims bounty
โ”œโ”€ Platform: claimBounty(bountyId)
โ””โ”€ PAY.ID: check freelancer reputation โ‰ฅ minReputation
โ””โ”€ If REJECT โ†’ claim blocked, reason shown

3. Freelancer submits milestone
โ”œโ”€ Platform: submitDeliverable(evidenceHash)
โ””โ”€ PAY.ID: attest deliverable hash on-chain (optional)

4. Client releases milestone
โ”œโ”€ Platform: approveMilestone(index)
โ””โ”€ PAY.ID: verify Decision Proof before signing release tx
โ””โ”€ Rule: "Only release if freelancer rep โ‰ฅ 700 AND milestone confirmed"

5. Dispute raised
โ”œโ”€ Platform: multi-arbiter voting (3 arbiters)
โ””โ”€ PAY.ID: arbiters' reputation checked before vote counted

UI: Unified Bounty Cardโ€‹

import { useReputation } from 'payid-react';
import { useBounty } from 'platform-hooks';

function BountyCard({ bountyId }: { bountyId: bigint }) {
const { bounty } = useBounty(bountyId);
const { data: posterRep } = useReputation({ target: bounty.poster });

// Composite score shows both sources
const scoreLabel = posterRep
? `${posterRep.score} ยท ${posterRep.isTrusted ? 'Trusted' : 'Neutral'}`
: 'Loading...';

return (
<Card>
<Header>
<Amount>{formatEther(bounty.amount)} ETH</Amount>
<StatusBadge status={bounty.status} />
</Header>

<Row>
<Avatar address={bounty.poster} />
<span>{shortAddr(bounty.poster)}</span>
<ReputationBadge score={posterRep?.score} source="Platformร—VRAN" />
</Row>

<Milestones milestones={bounty.milestones} />

<Actions>
{posterRep?.isTrusted && (
<Button onClick={claim}>Claim Bounty</Button>
)}
{!posterRep?.isTrusted && (
<Tooltip>Client reputation too low to claim</Tooltip>
)}
</Actions>
</Card>
);
}

Custom Weight Configurationโ€‹

Different use cases need different weights:

Use CasePlatform WeightVRAN WeightWhy
Freelancer marketplace0.60.4Platform's completion rate matters most
High-value enterprise0.30.7Anti-scam is critical
Community DAO0.50.5Balanced trust
Anonymous gigs0.01.0Only VRAN (no Platform history)
const { reputationAdapter } = createCompositeIntegration({
platform,
vranAdapter,
platformWeight: 0.3, // enterprise: prioritize anti-scam
vranWeight: 0.7,
});

Decision Matrixโ€‹

Your SituationWhat to Do
Only Platform, no PAY.IDescrowAdapter={PlatformEscrowAdapter}, reputationAdapter={NoopReputationAdapter}
Only PAY.ID, no PlatformDon't use this bridge โ€” use default adapters
Both, but trust Platform moreplatformWeight: 0.7, vranWeight: 0.3
Both, but PAY.ID anti-scam criticalplatformWeight: 0.3, vranWeight: 0.7
Want to hide PAY.ID featuresreputationAdapter={NoopReputationAdapter}

Under the Hoodโ€‹

// PlatformCompositeReputationAdapter.getReputation()
async function getReputation(target) {
const [platformScore, vranResult] = await Promise.all([
platform.reputation.getScore(target), // 0โ€“1000
vranAdapter.getReputation(target), // { score, isBlacklisted }
]);

// Blend
const composite = Math.min(1000,
platformScore * platformWeight + vranResult.score * vranWeight
);

// Fail-closed: either blacklisted = both blacklisted
return {
score: composite,
isBlacklisted: vranResult.isBlacklisted,
isTrusted: composite >= threshold && !vranResult.isBlacklisted,
};
}

Read Hooks: React Query Integrationโ€‹

All injected adapters use @tanstack/react-query under the hood:

// useReputation({ target }) internally:
const { data, isLoading, error } = useQuery({
queryKey: ['payid', 'reputation', 'injected', adapter.name, account],
queryFn: () => adapter.getReputation(account),
enabled: !!account,
staleTime: 30_000, // 30s cache
});

Benefits you get for free:

  • Caching โ€” Re-mount component? Data served from cache, no re-fetch
  • Deduping โ€” 2 components with same queryKey = 1 network call
  • Background refetch โ€” Window focus triggers silent refresh
  • Error retry โ€” Automatic retry with exponential backoff

Write Hooks: Adapter-Routedโ€‹

useSubmitReport and useConfirmReport now route through the adapter system:

Source PathBehavior
Injected adapterCalls adapter.submitReport() via useMutation
Contract pathUses wagmi useWriteContract as before
function ScamReportButton({ target }: { target: Address }) {
const { submitReport, isPending, isSuccess } = useSubmitReport({});

return (
<Button
onClick={() => submitReport(target, evidenceHash, parseEther('0.01'))}
loading={isPending}
>
{isSuccess ? 'Reported!' : 'Submit Report'}
</Button>
);
}

Result: Works identically whether you're using PAY.ID VRAN, a custom platform adapter, or a composite blend.


Read Hooks: useReport Routed Through Adapterโ€‹

useReport({ reportId }) now checks the adapter first:

Source PathBehavior
Injected adapter (has getReport)adapter.getReport(reportId) via useQuery
Contract pathwagmi useReadContract โ†’ reports(reportId)
function ReportLookup() {
const { report, isLoading } = useReport({ reportId: 42n });

return isLoading
? <Spinner />
: report
? <ReportCard report={report} />
: <p>Report not found</p>;
}

Custom platforms that store reports off-chain or in a different format can implement getReport on their adapter โ€” useReport will automatically use it.


Read Hooks: useSuccessfulReports Routed Through Adapterโ€‹

useSuccessfulReports({ target? }) now routes through the adapter system:

Source PathBehavior
Injected adapter (has getSuccessfulReports)adapter.getSuccessfulReports(address) via useQuery
Contract pathwagmi useReadContract โ†’ successfulReports(address)
function ReporterStats({ address }: { address: Address }) {
const { count, isLoading } = useSuccessfulReports({ target: address });

return isLoading
? <Spinner />
: <Badge>{count} successful reports</Badge>;
}

Custom platforms that track successful reports off-chain or in a different schema can implement getSuccessfulReports on their adapter.


Escrow Hooks: Full Source-Based Routingโ€‹

PAY.ID now provides escrow hooks with the same 3-tier resolution as reputation hooks:

import {
useUserEscrows,
useCreateEscrow,
useSubmitMilestone,
useReleaseMilestone,
} from 'payid-react';

function EscrowDashboard() {
const { escrows, isLoading } = useUserEscrows();
const { createEscrow, isPending } = useCreateEscrow();
const { releaseMilestone } = useReleaseMilestone();

return (
<div>
{isLoading ? 'Loading...' : escrows.map(e => (
<EscrowCard
key={String(e.id)}
escrow={e}
onRelease={(idx) => releaseMilestone(e.id, idx)}
/>
))}
<button
onClick={() => createEscrow(freelancer, asset, milestones, deadline)}
disabled={isPending}
>
Create Escrow
</button>
</div>
);
}
HookDescription
useUserEscrows({ user? })Read escrows for a user (as client or freelancer)
useCreateEscrow()Create escrow with milestones
useSubmitMilestone()Submit deliverable evidence (freelancer)
useReleaseMilestone()Release payment for a milestone (client/arbiter)
useDisputeEscrow()Raise a dispute
useResolveRefund()Resolve with full refund
useAutoRefund()Trigger auto-refund after deadline

Advanced: Fallback Chainโ€‹

Not all users have platform reputation yet. Use FallbackReputationAdapter to try platform first, then fall back to VRAN:

import {
createCompositeIntegration,
createFallbackReputation,
DefaultReputationAdapter,
} from 'payid-react';

const vranAdapter = new DefaultReputationAdapter(publicClient, walletClient, vindexAddr);

const { reputationAdapter: platformAdapter } = createCompositeIntegration({
platform,
vranAdapter,
});

// Progressive adoption: platform first, VRAN fallback
const fallbackAdapter = createFallbackReputation(platformAdapter, vranAdapter);

<PayIDProvider reputationAdapter={fallbackAdapter}>
<App />
</PayIDProvider>

Result: Users with platform history get blended scores. New users without platform history seamlessly fall back to pure VRAN.


Advanced: Middleware (Logging, Retry, Timeout)โ€‹

Debug production issues by wrapping adapters with middleware:

import { withMiddlewareReputation, withMiddlewareEscrow } from 'payid-react';

const reputationAdapter = withMiddlewareReputation(fallbackAdapter, {
log: true, // console.log every call with timing
retry: 3, // retry up to 3 times on failure
timeout: 5000, // abort after 5 seconds
});

const escrowAdapter = withMiddlewareEscrow(platformEscrowAdapter, {
log: process.env.NODE_ENV === 'development',
retry: 2,
});

Example log output:

[middleware] getReputation(0xabcโ€ฆ) โ†’ platform-composite
[middleware] getReputation(0xabcโ€ฆ) โ† platform-composite (124ms)
[middleware] getReputation(0xdefโ€ฆ) โ†’ platform-composite
[middleware] getReputation(0xdefโ€ฆ) failed (attempt 1/4), retrying in 1000msโ€ฆ

Next Stepsโ€‹