Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions scripts/identity-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Identity Binding Utilities

Command-line scripts for managing didgit.dev identity bindings on Base Sepolia.

## Prerequisites

```bash
npm install viem
export WALLET_PRIVATE_KEY=0x...
```

## Scripts

### bind-identity.mjs

Binds an existing EAS attestation to the UsernameUniqueResolver contract.

**When to use:** After creating an identity attestation via the web app or API, run this to register it in the resolver. This enables contribution attestations and automatically sets the `*/*` repository pattern.

```bash
node bind-identity.mjs
```

**What it does:**
1. Calls `bindIdentity(attestationUid, wallet, domain, username)` on the resolver
2. Automatically registers the `*/*` pattern (all repos in all orgs)
3. Verifies the binding was successful

### verify-binding.mjs

Checks if an identity is bound in the resolver.

```bash
node verify-binding.mjs
```

**Output:**
- Identity owner (wallet address for username)
- Binding status

### check-my-patterns.mjs

Lists all repository patterns registered for your identity.

```bash
node check-my-patterns.mjs
```

**Output:**
- All registered patterns (e.g., `*/*`, `cyberstorm-dev/*`, etc.)
- Enabled/disabled status for each pattern

## Architecture Notes

### Why Two Steps?

1. **EAS Attestation** - Creates the cryptographic proof (signature + gist)
2. **Resolver Binding** - Registers the attestation on-chain for uniqueness enforcement

The resolver ensures:
- One username → one wallet (no squatting)
- One wallet → one username (no multi-claiming)
- Repository patterns are tracked per identity

### Automatic `*/*` Registration

When `bindIdentity()` is called, the contract automatically enables the `*/*` pattern:

```solidity
bytes32 wildcardPatternKey = _patternKey("*", "*");
repoPatterns[identityKey][wildcardPatternKey] = true;
```

This allows immediate contribution attestations to any repo without manual pattern setup.

## Contract Addresses

- **Resolver:** `0x20c1cb4313efc28d325d3a893a68ca8c82911b0c`
- **EAS:** `0x4200000000000000000000000000000000000021`
- **Network:** Base Sepolia

## Related Docs

- [Identity Schema](../../docs/schemas/IDENTITY.md)
- [Contribution Schema](../../docs/schemas/CONTRIBUTION.md)
- [Contract Reference](../../docs/reference/CONTRACTS.md)
133 changes: 133 additions & 0 deletions scripts/identity-utils/bind-identity.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env node
/**
* Bind Loki's identity in the resolver
* This enables contribution attestations and automatically sets wildcard pattern
*/

import { createWalletClient, http, createPublicClient } from 'viem';
import { baseSepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c';
const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
const RPC_URL = 'https://sepolia.base.org';

// My attestation UID from IDENTITY.md
const MY_ATTESTATION_UID = '0xd440aad8b6751a2e1e0d2045a0443e615fec882f92313b793b682f2b546cb109';
const MY_WALLET = '0x7a1de0Fa7242194bbA84E915f39bF7E621B50d2E';
const MY_USERNAME = 'loki-cyberstorm';

const RESOLVER_ABI = [
{
type: 'function',
name: 'bindIdentity',
inputs: [
{ name: 'attestationUid', type: 'bytes32' },
{ name: 'recipient', type: 'address' },
{ name: 'domain', type: 'string' },
{ name: 'username', type: 'string' }
],
outputs: [],
stateMutability: 'nonpayable'
},
{
type: 'function',
name: 'getIdentity',
inputs: [
{ name: 'domain', type: 'string' },
{ name: 'username', type: 'string' }
],
outputs: [{ type: 'address' }],
stateMutability: 'view'
}
];

async function main() {
if (!WALLET_PRIVATE_KEY) {
console.error('❌ WALLET_PRIVATE_KEY not set in environment');
process.exit(1);
}

const account = privateKeyToAccount(WALLET_PRIVATE_KEY);
console.log(`🎭 Binding identity for: ${account.address}\n`);

const walletClient = createWalletClient({
account,
chain: baseSepolia,
transport: http(RPC_URL)
});

const publicClient = createPublicClient({
chain: baseSepolia,
transport: http(RPC_URL)
});

console.log('📋 Identity Details:');
console.log(` Username: github.com:${MY_USERNAME}`);
console.log(` Wallet: ${MY_WALLET}`);
console.log(` Attestation UID: ${MY_ATTESTATION_UID}`);
console.log();

try {
console.log('📝 Binding identity in resolver...');

const hash = await walletClient.writeContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'bindIdentity',
args: [
MY_ATTESTATION_UID,
MY_WALLET,
'github.com',
MY_USERNAME
]
});

console.log(`📤 Transaction sent: ${hash}`);
console.log(`🔗 Explorer: https://sepolia.basescan.org/tx/${hash}`);

// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });

if (receipt.status === 'success') {
console.log('✅ Identity bound successfully!');
} else {
console.log('❌ Transaction failed');
process.exit(1);
}

// Verify binding
console.log('\n🔍 Verifying binding...');

const boundWallet = await publicClient.readContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'getIdentity',
args: ['github.com', MY_USERNAME]
});

console.log(` github.com:${MY_USERNAME} -> ${boundWallet}`);
console.log(` Match: ${boundWallet.toLowerCase() === MY_WALLET.toLowerCase() ? '✅' : '❌'}`);

console.log('\n🎉 Done! The */* repository pattern was automatically enabled.');
console.log('🚀 Loki can now attest contributions to any GitHub repo in any org.');

} catch (error) {
console.error('❌ Error:', error.message);
if (error.message.includes('IDENTITY_TAKEN')) {
console.log('\n💡 Identity is already bound. Checking current binding...');
try {
const boundWallet = await publicClient.readContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'getIdentity',
args: ['github.com', MY_USERNAME]
});
console.log(` Current binding: github.com:${MY_USERNAME} -> ${boundWallet}`);
} catch {}
}
process.exit(1);
}
}

main();
73 changes: 73 additions & 0 deletions scripts/identity-utils/check-my-patterns.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node
import { createWalletClient, http, createPublicClient } from 'viem';
import { baseSepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c';
const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
const RPC_URL = 'https://sepolia.base.org';

const RESOLVER_ABI = [
{
type: 'function',
name: 'getRepositoryPatterns',
inputs: [
{ name: 'domain', type: 'string' },
{ name: 'identifier', type: 'string' }
],
outputs: [{
name: 'patterns',
type: 'tuple[]',
components: [
{ name: 'namespace', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'enabled', type: 'bool' }
]
}],
stateMutability: 'view'
}
];

async function main() {
const account = privateKeyToAccount(WALLET_PRIVATE_KEY);

const client = createPublicClient({
account,
chain: baseSepolia,
transport: http(RPC_URL)
});

console.log('🔍 Checking repository patterns for loki-cyberstorm...\n');

try {
const patterns = await client.readContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'getRepositoryPatterns',
args: ['github.com', 'loki-cyberstorm'],
account
});

if (patterns.length === 0) {
console.log('❌ No patterns registered');
} else {
console.log(`✅ Registered patterns (${patterns.length}):`);
patterns.forEach(p => {
const status = p.enabled ? '✅' : '❌';
const pattern = `${p.namespace}/${p.name}`;
console.log(` ${status} ${pattern}`);

if (p.namespace === '*' && p.name === '*') {
console.log(' ↳ This means ALL REPOS in ALL ORGS! 🚀');
}
});
}

console.log('\n🎉 Loki can attest contributions to any GitHub repo!');
} catch (error) {
console.log(`❌ Error: ${error.message}`);
process.exit(1);
}
}

main();
87 changes: 87 additions & 0 deletions scripts/identity-utils/verify-binding.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env node
import { createPublicClient, http } from 'viem';
import { baseSepolia } from 'viem/chains';

const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c';
const RPC_URL = 'https://sepolia.base.org';
const MY_WALLET = '0x7a1de0Fa7242194bbA84E915f39bF7E621B50d2E';

const RESOLVER_ABI = [
{
type: 'function',
name: 'getIdentityOwner',
inputs: [
{ name: 'domain', type: 'string' },
{ name: 'identifier', type: 'string' }
],
outputs: [{ name: 'owner', type: 'address' }],
stateMutability: 'view'
},
{
type: 'function',
name: 'getRepositoryPatterns',
inputs: [
{ name: 'domain', type: 'string' },
{ name: 'identifier', type: 'string' }
],
outputs: [{
name: 'patterns',
type: 'tuple[]',
components: [
{ name: 'namespace', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'enabled', type: 'bool' }
]
}],
stateMutability: 'view'
}
];

async function main() {
const client = createPublicClient({
chain: baseSepolia,
transport: http(RPC_URL)
});

console.log('🔍 Verifying identity binding...\n');

try {
const owner = await client.readContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'getIdentityOwner',
args: ['github.com', 'loki-cyberstorm']
});

console.log(`✅ Identity bound:`);
console.log(` github.com:loki-cyberstorm -> ${owner}`);
console.log(` Expected: ${MY_WALLET}`);
console.log(` Match: ${owner.toLowerCase() === MY_WALLET.toLowerCase() ? '✅' : '❌'}`);
} catch (error) {
console.log(`❌ getIdentityOwner failed: ${error.message}`);
process.exit(1);
}

console.log();

try {
const patterns = await client.readContract({
address: RESOLVER_ADDRESS,
abi: RESOLVER_ABI,
functionName: 'getRepositoryPatterns',
args: ['github.com', 'loki-cyberstorm']
});

console.log(`✅ Repository patterns:`);
patterns.forEach(p => {
const status = p.enabled ? '✅' : '❌';
console.log(` ${status} ${p.namespace}/${p.name}`);
});

console.log('\n🎉 Success! Loki can now attest contributions to registered repos.');
} catch (error) {
console.log(`❌ getRepositoryPatterns failed: ${error.message}`);
}
}

main();