-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1182645
Showing
53 changed files
with
6,085 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ignores: ['esbuild', 'esbuild-runner', '@metaplex-foundation/amman', 'supports-color'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('../../prettierrc-base.js') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Mudlands | ||
|
||
Mu _ltiple_ _I_ dl and S _olana tooling_. | ||
|
||
## Example | ||
|
||
To try this example have a look at [`./examples/cndy-idls.ts`](./examples/cndy-idls.ts). | ||
|
||
For another example have a look at [`./examples/usd-idls.ts`](./examples/usd-idls.ts) (this one | ||
only uploaded one IDL so it finishes quickly). | ||
|
||
Run with `yarn ex:usd`, but replace `SOLANA_MAINNET` with an RPC node like helius first. | ||
|
||
```ts | ||
import { findIdls } from '@ironforge/mudlands' | ||
|
||
// Helper to log IDLs | ||
function parseWrites(writes: { idl: Buffer }[]) { | ||
return writes.map((w) => JSON.parse(w.idl.toString())) | ||
} | ||
|
||
// Find all IDLs created for CandyMachine | ||
const CANDY_PROGRAM_ID = 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ' | ||
|
||
// May use different RPC provider to avoid getting rate limited | ||
const SOLANA_MAINNET = 'https://api.mainnet-beta.solana.com' | ||
|
||
const idlWrites = await findIdls(CANDY_PROGRAM_ID, SOLANA_MAINNET) | ||
console.log(JSON.stringify(parseWrites(idlWrites), null, 2)) | ||
``` | ||
|
||
## LICENSE | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { findIdls } from '../src/mudlands' | ||
|
||
// Helper to log IDLs | ||
function parseWrites(writes: { idl: Buffer }[]) { | ||
return writes.map((w) => JSON.parse(w.idl.toString())) | ||
} | ||
|
||
const PROGRAM_ID = process.argv[2] | ||
if (PROGRAM_ID == null) { | ||
console.error('Usage: esr check-idl.ts <programId>') | ||
process.exit(1) | ||
} | ||
|
||
// May use different RPC provider to avoid getting rate limited | ||
const SOLANA_MAINNET = 'https://api.mainnet-beta.solana.com' | ||
const RPC = process.env.RPC ?? SOLANA_MAINNET | ||
|
||
async function main() { | ||
console.error('Checking IDLs on RPC %s', RPC) | ||
const idlWrites = await findIdls(PROGRAM_ID, RPC) | ||
console.log(JSON.stringify(parseWrites(idlWrites), null, 2)) | ||
console.log('Total of %d idls', idlWrites.length) | ||
} | ||
|
||
main() | ||
.then(() => process.exit(0)) | ||
.catch((err: any) => { | ||
console.error(err) | ||
process.exit(1) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { findIdls } from '../src/mudlands' | ||
|
||
// Helper to log IDLs | ||
function parseWrites(writes: { idl: Buffer }[]) { | ||
return writes.map((w) => JSON.parse(w.idl.toString())) | ||
} | ||
|
||
// Find all IDLs created for CandyMachine | ||
const CANDY_PROGRAM_ID = 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ' | ||
|
||
// May use different RPC provider to avoid getting rate limited | ||
const SOLANA_MAINNET = 'https://api.mainnet-beta.solana.com' | ||
const RPC = process.env.RPC ?? SOLANA_MAINNET | ||
|
||
async function main() { | ||
const idlWrites = await findIdls(CANDY_PROGRAM_ID, RPC) | ||
console.log(JSON.stringify(parseWrites(idlWrites), null, 2)) | ||
console.log('Total of %d idls', idlWrites.length) | ||
} | ||
|
||
main() | ||
.then(() => process.exit(0)) | ||
.catch((err: any) => { | ||
console.error(err) | ||
process.exit(1) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { findIdls } from '../src/mudlands' | ||
|
||
// Helper to log IDLs | ||
function parseWrites(writes: { idl: Buffer }[]) { | ||
return writes.map((w) => JSON.parse(w.idl.toString())) | ||
} | ||
|
||
// Find all IDLs created for CandyMachine | ||
const USD_PROGRAM_ID = '1USDCmv8QmvZ9JaL7bmevGsNHn7ez8TNahJzCN551sb' | ||
|
||
// May use different RPC provider to avoid getting rate limited | ||
const SOLANA_MAINNET = 'https://api.mainnet-beta.solana.com' | ||
|
||
async function main() { | ||
const idlWrites = await findIdls(USD_PROGRAM_ID, SOLANA_MAINNET) | ||
console.log(JSON.stringify(parseWrites(idlWrites), null, 2)) | ||
} | ||
|
||
main() | ||
.then(() => process.exit(0)) | ||
.catch((err: any) => { | ||
console.error(err) | ||
process.exit(1) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "@ironforge/mudlands", | ||
"version": "0.0.0", | ||
"description": "Discovers all IDLs of a program.", | ||
"main": "src/mudlands.ts", | ||
"types": "src/mudlands.ts", | ||
"private": true, | ||
"scripts": { | ||
"build": "tsc", | ||
"lint": "prettier --check .", | ||
"lint:fix": "prettier --write .", | ||
"depcheck": "depcheck", | ||
"depcheck:fix": "for m in `depcheck --json | jq '.missing | keys[]' --raw-output`; do yarn add $m; done", | ||
"test": "node --test --test-reporter=spec -r esbuild-runner/register ./test/*.ts", | ||
"test:ix": "yarn test:ix:anchor && yarn test:ix:misc", | ||
"test:ix:anchor": "./test/anchor/run-ix.sh", | ||
"test:ix:misc": "node --test --test-reporter=spec -r esbuild-runner/register ./test/ix/*.ts", | ||
"ex:cndy": "node -r esbuild-runner/register ./examples/cndy-idls.ts", | ||
"ex:usd": "node -r esbuild-runner/register ./examples/usd-idls.ts", | ||
"check:idl": "node -r esbuild-runner/register ./examples/check-idl.ts" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/ironforge-cloud/mudlands.git" | ||
}, | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/ironforge-cloud/mudlands/issues" | ||
}, | ||
"homepage": "https://github.com/ironforge-cloud/mudlands#readme", | ||
"dependencies": { | ||
"@noble/ed25519": "^1.7.3", | ||
"@noble/hashes": "^1.3.2", | ||
"@solana/web3.js": "^1.78.5", | ||
"bn.js": "^5.2.1", | ||
"bs58": "^5.0.0" | ||
}, | ||
"devDependencies": { | ||
"@metaplex-foundation/amman": "^0.12.1", | ||
"@metaplex-foundation/amman-client": "^0.2.4", | ||
"@metaplex-foundation/rustbin": "^0.3.5", | ||
"@types/bn.js": "^5.1.2", | ||
"@types/debug": "^4.1.8", | ||
"@types/node": "^20.6.2", | ||
"debug": "^4.3.4", | ||
"depcheck": "^1.4.6", | ||
"esbuild": "^0.19.3", | ||
"esbuild-runner": "^2.2.2", | ||
"execa": "^7.2.0", | ||
"prettier": "^3.0.3", | ||
"spok": "^1.5.5", | ||
"supports-color": "^9.4.0", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Instruction } from '../types' | ||
|
||
export function matchingAccounts( | ||
ix: Instruction, | ||
accountKeys: string[], | ||
totalAccounts: number, | ||
accountsByIndex: Map<number, string>, | ||
dump = false | ||
) { | ||
if (accountKeys.length < totalAccounts) return null | ||
if (ix.accounts.length !== totalAccounts) return null | ||
|
||
const resolvedAccounts = new Array(ix.accounts.length) | ||
for (let i = 0; i < ix.accounts.length; i++) { | ||
const idx = ix.accounts[i] | ||
resolvedAccounts[i] = accountKeys[idx] | ||
} | ||
|
||
// If the programID was already mentioned in the ix.accounts | ||
// we don't need to add it again | ||
const programIdIndexInAccounts = ix.accounts.indexOf(ix.programIdIndex) | ||
if (programIdIndexInAccounts === -1) { | ||
resolvedAccounts.push(accountKeys[ix.programIdIndex]) | ||
} | ||
if (dump) { | ||
console.log({ accountKeys, programIdIndexInAccounts }) | ||
console.log({ ixAccount: ix.accounts }) | ||
console.log({ resolvedAccounts }) | ||
} | ||
|
||
for (const [idx, addr] of accountsByIndex.entries()) { | ||
if (idx >= totalAccounts) continue | ||
if (resolvedAccounts[idx] !== addr) return null | ||
} | ||
return resolvedAccounts | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Meta, Transaction } from '../types' | ||
import { ixDataMatchesDiscriminator } from '../utils' | ||
|
||
const DISC = Buffer.from('40f4bc78a7e9690a', 'hex') | ||
|
||
export type ExtractCreateAccountResult = { | ||
idl: string | ||
program: string | ||
slot: number | ||
} | ||
|
||
export async function extractCreateAccount( | ||
tx: Transaction, | ||
meta: Meta | undefined, | ||
programId: string, | ||
idlId: string, | ||
slot: number | ||
): Promise<ExtractCreateAccountResult | null> { | ||
if (meta?.err != null) { | ||
throw new Error('Cannot handle transaction with error') | ||
} | ||
|
||
// 1. Headers | ||
const header = tx.message.header | ||
const headerMatches = | ||
header.numReadonlySignedAccounts === 0 && | ||
header.numReadonlyUnsignedAccounts === 4 && | ||
header.numRequiredSignatures === 1 | ||
|
||
if (!headerMatches) return null | ||
|
||
// 2. Main Instruction | ||
const instructions = tx.message.instructions | ||
if (instructions.length !== 1) return null | ||
|
||
const ix = instructions[0] | ||
if (!ixDataMatchesDiscriminator(ix.data, DISC)) return null | ||
|
||
return { idl: idlId, program: programId, slot } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { Transaction } from '../types' | ||
import { unzip } from '../unzip' | ||
import { logError, logTrace } from '../utils' | ||
import { decodeIxData, bufMatchesIxDiscriminator } from '../utils/ix-data' | ||
import { matchingAccounts } from './accounts-match' | ||
|
||
const DISC = Buffer.from('40f4bc78a7e9690a', 'hex') | ||
const ZIP_MAGIC = Buffer.from('789c', 'hex') | ||
|
||
/** | ||
* Extracts IdlWrite transaction data if it is adn IdlWrite Transaction. | ||
* | ||
* @param {Transaction} tx | ||
* @param {string} programId for which the IDL is written | ||
* @param {string} tgtAddr the account that the IDL is written to | ||
* - for `init` this is the IDL address | ||
* - for `upgrade` this is another address that will then be used in | ||
* `SetBuffer` to update the IDL address itself | ||
* @returns {Buffer | null} the data of the IdlWrite instruction without the | ||
* instruction prefix | ||
*/ | ||
export async function extractIdlWriteTxData( | ||
tx: Transaction, | ||
programId: string, | ||
tgtAddr: string | ||
): Promise<Buffer | null> { | ||
// 1. Headers | ||
const header = tx.message.header | ||
const headerMatches = | ||
header.numReadonlySignedAccounts === 0 && | ||
header.numReadonlyUnsignedAccounts === 1 && | ||
header.numRequiredSignatures === 1 | ||
|
||
if (!headerMatches) return null | ||
|
||
// 2. Main Instructions and Accounts | ||
const instructions = tx.message.instructions | ||
if (instructions.length !== 1) return null | ||
|
||
/* | ||
* pub struct IdlAccounts<'info> { | ||
* pub idl: Account<'info, IdlAccount>, | ||
* pub authority: Signer<'info>, | ||
* } | ||
*/ | ||
const ix = instructions[0] | ||
const accountsByIdx = new Map([ | ||
[0, tgtAddr], | ||
[2, programId], | ||
]) | ||
const accs = matchingAccounts(ix, tx.message.accountKeys, 2, accountsByIdx) | ||
if (accs == null) return null | ||
|
||
const buf = decodeIxData(ix.data) | ||
|
||
const discMatches = bufMatchesIxDiscriminator(buf, DISC) | ||
if (!discMatches) return null | ||
|
||
// Cut off instruction prefix | ||
return buf.subarray(13) | ||
} | ||
|
||
export function deserializeWriteTxData(data: Buffer[]) { | ||
const buf = Buffer.concat(data) | ||
if (logTrace.enabled) { | ||
logTrace( | ||
'Deserializing IDL data from %d buffers, total bytes: %d', | ||
data.length, | ||
buf.byteLength | ||
) | ||
} | ||
const hasZipMagicHeader = buf.subarray(0, 2).equals(ZIP_MAGIC) | ||
if (!hasZipMagicHeader) { | ||
logError('Failed to find magic ZIP header') | ||
return null | ||
} | ||
|
||
return unzip(buf) | ||
} |
Oops, something went wrong.