ts: Reorganize program namespaces into well typed elements (#322)
This commit is contained in:
parent
e1229362bc
commit
2f780e0d27
|
@ -19,9 +19,10 @@ incremented for features.
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
|
* ts: Retrieving deserialized accounts from the `<program>.account.<my-account>` and `<program>.state` namespaces now require explicitly invoking the `fetch` API. For example, `program.account.myAccount(<adddress>)` and `program.state()` is now `program.account.myAccount.fetch(<address>)` and `program.state.fetch()` ([#322](https://github.com/project-serum/anchor/pull/322)).
|
||||||
* lang: `#[account(associated)]` now requires `init` to be provided to create an associated account. If not provided, then the address will be assumed to exist, and a constraint will be added to ensure its correctness ([#318](https://github.com/project-serum/anchor/pull/318)).
|
* lang: `#[account(associated)]` now requires `init` to be provided to create an associated account. If not provided, then the address will be assumed to exist, and a constraint will be added to ensure its correctness ([#318](https://github.com/project-serum/anchor/pull/318)).
|
||||||
* lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:". This change should only be noticed by library maintainers ([#320](https://github.com/project-serum/anchor/pull/320)).
|
* lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:" ([#320](https://github.com/project-serum/anchor/pull/320)).
|
||||||
* lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts. This change should only be noticed by library maintainers.
|
* lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts ([#321](https://github.com/project-serum/anchor/pull/321)).
|
||||||
|
|
||||||
## [0.6.0] - 2021-05-23
|
## [0.6.0] - 2021-05-23
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ describe("cashiers-check", () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkAccount = await program.account.check(check.publicKey);
|
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||||
assert.ok(checkAccount.from.equals(god));
|
assert.ok(checkAccount.from.equals(god));
|
||||||
assert.ok(checkAccount.to.equals(receiver));
|
assert.ok(checkAccount.to.equals(receiver));
|
||||||
assert.ok(checkAccount.amount.eq(new anchor.BN(100)));
|
assert.ok(checkAccount.amount.eq(new anchor.BN(100)));
|
||||||
|
@ -91,7 +91,7 @@ describe("cashiers-check", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkAccount = await program.account.check(check.publicKey);
|
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||||
assert.ok(checkAccount.burned === true);
|
assert.ok(checkAccount.burned === true);
|
||||||
|
|
||||||
let vaultAccount = await serumCmn.getTokenAccount(
|
let vaultAccount = await serumCmn.getTokenAccount(
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe("chat", () => {
|
||||||
signers: [chatRoom],
|
signers: [chatRoom],
|
||||||
});
|
});
|
||||||
|
|
||||||
const chat = await program.account.chatRoom(chatRoom.publicKey);
|
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||||
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||||
assert.ok(chat.messages.length === 33607);
|
assert.ok(chat.messages.length === 33607);
|
||||||
|
@ -76,7 +76,7 @@ describe("chat", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the chat room state is as expected.
|
// Check the chat room state is as expected.
|
||||||
const chat = await program.account.chatRoom(chatRoom.publicKey);
|
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||||
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||||
assert.ok(chat.messages.length === 33607);
|
assert.ok(chat.messages.length === 33607);
|
||||||
|
|
|
@ -41,8 +41,8 @@ describe("composite", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const dummyAAccount = await program.account.dummyA(dummyA.publicKey);
|
const dummyAAccount = await program.account.dummyA.fetch(dummyA.publicKey);
|
||||||
const dummyBAccount = await program.account.dummyB(dummyB.publicKey);
|
const dummyBAccount = await program.account.dummyB.fetch(dummyB.publicKey);
|
||||||
|
|
||||||
assert.ok(dummyAAccount.data.eq(new anchor.BN(1234)));
|
assert.ok(dummyAAccount.data.eq(new anchor.BN(1234)));
|
||||||
assert.ok(dummyBAccount.data.eq(new anchor.BN(4321)));
|
assert.ok(dummyBAccount.data.eq(new anchor.BN(4321)));
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe("interface", () => {
|
||||||
it("Is initialized!", async () => {
|
it("Is initialized!", async () => {
|
||||||
await counter.state.rpc.new(counterAuth.programId);
|
await counter.state.rpc.new(counterAuth.programId);
|
||||||
|
|
||||||
const stateAccount = await counter.state();
|
const stateAccount = await counter.state.fetch();
|
||||||
assert.ok(stateAccount.count.eq(new anchor.BN(0)));
|
assert.ok(stateAccount.count.eq(new anchor.BN(0)));
|
||||||
assert.ok(stateAccount.authProgram.equals(counterAuth.programId));
|
assert.ok(stateAccount.authProgram.equals(counterAuth.programId));
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,7 @@ describe("interface", () => {
|
||||||
authProgram: counterAuth.programId,
|
authProgram: counterAuth.programId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const stateAccount = await counter.state();
|
const stateAccount = await counter.state.fetch();
|
||||||
assert.ok(stateAccount.count.eq(new anchor.BN(3)));
|
assert.ok(stateAccount.count.eq(new anchor.BN(3)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ describe("Lockup and Registry", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
lockupAddress = await lockup.state.address();
|
lockupAddress = await lockup.state.address();
|
||||||
const lockupAccount = await lockup.state();
|
const lockupAccount = await lockup.state.fetch();
|
||||||
|
|
||||||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||||
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
|
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
|
||||||
|
@ -63,7 +63,7 @@ describe("Lockup and Registry", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let lockupAccount = await lockup.state();
|
let lockupAccount = await lockup.state.fetch();
|
||||||
assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));
|
assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));
|
||||||
|
|
||||||
await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
|
await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
|
||||||
|
@ -73,7 +73,7 @@ describe("Lockup and Registry", () => {
|
||||||
signers: [newAuthority],
|
signers: [newAuthority],
|
||||||
});
|
});
|
||||||
|
|
||||||
lockupAccount = await lockup.state();
|
lockupAccount = await lockup.state.fetch();
|
||||||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ describe("Lockup and Registry", () => {
|
||||||
|
|
||||||
await lockup.state.rpc.whitelistAdd(entries[0], { accounts });
|
await lockup.state.rpc.whitelistAdd(entries[0], { accounts });
|
||||||
|
|
||||||
let lockupAccount = await lockup.state();
|
let lockupAccount = await lockup.state.fetch();
|
||||||
|
|
||||||
assert.ok(lockupAccount.whitelist.length === 1);
|
assert.ok(lockupAccount.whitelist.length === 1);
|
||||||
assert.deepEqual(lockupAccount.whitelist, [entries[0]]);
|
assert.deepEqual(lockupAccount.whitelist, [entries[0]]);
|
||||||
|
@ -106,7 +106,7 @@ describe("Lockup and Registry", () => {
|
||||||
await lockup.state.rpc.whitelistAdd(entries[k], { accounts });
|
await lockup.state.rpc.whitelistAdd(entries[k], { accounts });
|
||||||
}
|
}
|
||||||
|
|
||||||
lockupAccount = await lockup.state();
|
lockupAccount = await lockup.state.fetch();
|
||||||
|
|
||||||
assert.deepEqual(lockupAccount.whitelist, entries);
|
assert.deepEqual(lockupAccount.whitelist, entries);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ describe("Lockup and Registry", () => {
|
||||||
authority: provider.wallet.publicKey,
|
authority: provider.wallet.publicKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let lockupAccount = await lockup.state();
|
let lockupAccount = await lockup.state.fetch();
|
||||||
assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
|
assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ describe("Lockup and Registry", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
vestingAccount = await lockup.account.vesting(vesting.publicKey);
|
vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
|
||||||
|
|
||||||
assert.ok(vestingAccount.beneficiary.equals(provider.wallet.publicKey));
|
assert.ok(vestingAccount.beneficiary.equals(provider.wallet.publicKey));
|
||||||
assert.ok(vestingAccount.mint.equals(mint));
|
assert.ok(vestingAccount.mint.equals(mint));
|
||||||
|
@ -246,7 +246,7 @@ describe("Lockup and Registry", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
vestingAccount = await lockup.account.vesting(vesting.publicKey);
|
vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
|
||||||
assert.ok(vestingAccount.outstanding.eq(new anchor.BN(0)));
|
assert.ok(vestingAccount.outstanding.eq(new anchor.BN(0)));
|
||||||
|
|
||||||
const vaultAccount = await serumCmn.getTokenAccount(
|
const vaultAccount = await serumCmn.getTokenAccount(
|
||||||
|
@ -287,7 +287,7 @@ describe("Lockup and Registry", () => {
|
||||||
accounts: { lockupProgram: lockup.programId },
|
accounts: { lockupProgram: lockup.programId },
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = await registry.state();
|
const state = await registry.state.fetch();
|
||||||
assert.ok(state.lockupProgram.equals(lockup.programId));
|
assert.ok(state.lockupProgram.equals(lockup.programId));
|
||||||
|
|
||||||
// Should not allow a second initializatoin.
|
// Should not allow a second initializatoin.
|
||||||
|
@ -324,7 +324,7 @@ describe("Lockup and Registry", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
registrarAccount = await registry.account.registrar(registrar.publicKey);
|
registrarAccount = await registry.account.registrar.fetch(registrar.publicKey);
|
||||||
|
|
||||||
assert.ok(registrarAccount.authority.equals(provider.wallet.publicKey));
|
assert.ok(registrarAccount.authority.equals(provider.wallet.publicKey));
|
||||||
assert.equal(registrarAccount.nonce, nonce);
|
assert.equal(registrarAccount.nonce, nonce);
|
||||||
|
@ -385,7 +385,7 @@ describe("Lockup and Registry", () => {
|
||||||
|
|
||||||
let txSigs = await provider.sendAll(allTxs);
|
let txSigs = await provider.sendAll(allTxs);
|
||||||
|
|
||||||
memberAccount = await registry.account.member(member.publicKey);
|
memberAccount = await registry.account.member.fetch(member.publicKey);
|
||||||
|
|
||||||
assert.ok(memberAccount.registrar.equals(registrar.publicKey));
|
assert.ok(memberAccount.registrar.equals(registrar.publicKey));
|
||||||
assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));
|
assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));
|
||||||
|
@ -516,7 +516,7 @@ describe("Lockup and Registry", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const vendorAccount = await registry.account.rewardVendor(
|
const vendorAccount = await registry.account.rewardVendor.fetch(
|
||||||
unlockedVendor.publicKey
|
unlockedVendor.publicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ describe("Lockup and Registry", () => {
|
||||||
assert.ok(vendorAccount.rewardEventQCursor === 0);
|
assert.ok(vendorAccount.rewardEventQCursor === 0);
|
||||||
assert.deepEqual(vendorAccount.kind, rewardKind);
|
assert.deepEqual(vendorAccount.kind, rewardKind);
|
||||||
|
|
||||||
const rewardQAccount = await registry.account.rewardQueue(
|
const rewardQAccount = await registry.account.rewardQueue.fetch(
|
||||||
rewardQ.publicKey
|
rewardQ.publicKey
|
||||||
);
|
);
|
||||||
assert.ok(rewardQAccount.head === 1);
|
assert.ok(rewardQAccount.head === 1);
|
||||||
|
@ -571,7 +571,7 @@ describe("Lockup and Registry", () => {
|
||||||
let tokenAccount = await serumCmn.getTokenAccount(provider, token);
|
let tokenAccount = await serumCmn.getTokenAccount(provider, token);
|
||||||
assert.ok(tokenAccount.amount.eq(new anchor.BN(200)));
|
assert.ok(tokenAccount.amount.eq(new anchor.BN(200)));
|
||||||
|
|
||||||
const memberAccount = await registry.account.member(member.publicKey);
|
const memberAccount = await registry.account.member.fetch(member.publicKey);
|
||||||
assert.ok(memberAccount.rewardsCursor == 1);
|
assert.ok(memberAccount.rewardsCursor == 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -635,7 +635,7 @@ describe("Lockup and Registry", () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const vendorAccount = await registry.account.rewardVendor(
|
const vendorAccount = await registry.account.rewardVendor.fetch(
|
||||||
lockedVendor.publicKey
|
lockedVendor.publicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -653,7 +653,7 @@ describe("Lockup and Registry", () => {
|
||||||
JSON.stringify(lockedRewardKind)
|
JSON.stringify(lockedRewardKind)
|
||||||
);
|
);
|
||||||
|
|
||||||
const rewardQAccount = await registry.account.rewardQueue(
|
const rewardQAccount = await registry.account.rewardQueue.fetch(
|
||||||
rewardQ.publicKey
|
rewardQ.publicKey
|
||||||
);
|
);
|
||||||
assert.ok(rewardQAccount.head === 2);
|
assert.ok(rewardQAccount.head === 2);
|
||||||
|
@ -727,7 +727,7 @@ describe("Lockup and Registry", () => {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const lockupAccount = await lockup.account.vesting(
|
const lockupAccount = await lockup.account.vesting.fetch(
|
||||||
vendoredVesting.publicKey
|
vendoredVesting.publicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe("misc", () => {
|
||||||
it("Can allocate extra space for a state constructor", async () => {
|
it("Can allocate extra space for a state constructor", async () => {
|
||||||
const tx = await program.state.rpc.new();
|
const tx = await program.state.rpc.new();
|
||||||
const addr = await program.state.address();
|
const addr = await program.state.address();
|
||||||
const state = await program.state();
|
const state = await program.state.fetch();
|
||||||
const accountInfo = await program.provider.connection.getAccountInfo(addr);
|
const accountInfo = await program.provider.connection.getAccountInfo(addr);
|
||||||
assert.ok(state.v.equals(Buffer.from([])));
|
assert.ok(state.v.equals(Buffer.from([])));
|
||||||
assert.ok(accountInfo.data.length === 99);
|
assert.ok(accountInfo.data.length === 99);
|
||||||
|
@ -32,7 +32,7 @@ describe("misc", () => {
|
||||||
instructions: [await program.account.data.createInstruction(data)],
|
instructions: [await program.account.data.createInstruction(data)],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const dataAccount = await program.account.data(data.publicKey);
|
const dataAccount = await program.account.data.fetch(data.publicKey);
|
||||||
assert.ok(dataAccount.udata.eq(new anchor.BN(1234)));
|
assert.ok(dataAccount.udata.eq(new anchor.BN(1234)));
|
||||||
assert.ok(dataAccount.idata.eq(new anchor.BN(22)));
|
assert.ok(dataAccount.idata.eq(new anchor.BN(22)));
|
||||||
});
|
});
|
||||||
|
@ -47,7 +47,7 @@ describe("misc", () => {
|
||||||
signers: [data],
|
signers: [data],
|
||||||
instructions: [await program.account.dataU16.createInstruction(data)],
|
instructions: [await program.account.dataU16.createInstruction(data)],
|
||||||
});
|
});
|
||||||
const dataAccount = await program.account.dataU16(data.publicKey);
|
const dataAccount = await program.account.dataU16.fetch(data.publicKey);
|
||||||
assert.ok(dataAccount.data === 99);
|
assert.ok(dataAccount.data === 99);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ describe("misc", () => {
|
||||||
authority: program.provider.wallet.publicKey,
|
authority: program.provider.wallet.publicKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let stateAccount = await misc2Program.state();
|
let stateAccount = await misc2Program.state.fetch();
|
||||||
assert.ok(stateAccount.data.eq(oldData));
|
assert.ok(stateAccount.data.eq(oldData));
|
||||||
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
||||||
const newData = new anchor.BN(2134);
|
const newData = new anchor.BN(2134);
|
||||||
|
@ -121,7 +121,7 @@ describe("misc", () => {
|
||||||
misc2Program: misc2Program.programId,
|
misc2Program: misc2Program.programId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
stateAccount = await misc2Program.state();
|
stateAccount = await misc2Program.state.fetch();
|
||||||
assert.ok(stateAccount.data.eq(newData));
|
assert.ok(stateAccount.data.eq(newData));
|
||||||
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
||||||
});
|
});
|
||||||
|
@ -145,7 +145,7 @@ describe("misc", () => {
|
||||||
);
|
);
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
async () => {
|
async () => {
|
||||||
await program.account.testData(associatedAccount);
|
await program.account.testData.fetch(associatedAccount);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
assert.ok(
|
assert.ok(
|
||||||
|
@ -234,7 +234,7 @@ describe("misc", () => {
|
||||||
instructions: [await program.account.dataI8.createInstruction(data)],
|
instructions: [await program.account.dataI8.createInstruction(data)],
|
||||||
signers: [data],
|
signers: [data],
|
||||||
});
|
});
|
||||||
const dataAccount = await program.account.dataI8(data.publicKey);
|
const dataAccount = await program.account.dataI8.fetch(data.publicKey);
|
||||||
assert.ok(dataAccount.data === -3);
|
assert.ok(dataAccount.data === -3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -250,14 +250,14 @@ describe("misc", () => {
|
||||||
instructions: [await program.account.dataI16.createInstruction(data)],
|
instructions: [await program.account.dataI16.createInstruction(data)],
|
||||||
signers: [data],
|
signers: [data],
|
||||||
});
|
});
|
||||||
const dataAccount = await program.account.dataI16(data.publicKey);
|
const dataAccount = await program.account.dataI16.fetch(data.publicKey);
|
||||||
assert.ok(dataAccount.data === -2048);
|
assert.ok(dataAccount.data === -2048);
|
||||||
|
|
||||||
dataPubkey = data.publicKey;
|
dataPubkey = data.publicKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Can use base58 strings to fetch an account", async () => {
|
it("Can use base58 strings to fetch an account", async () => {
|
||||||
const dataAccount = await program.account.dataI16(dataPubkey.toString());
|
const dataAccount = await program.account.dataI16.fetch(dataPubkey.toString());
|
||||||
assert.ok(dataAccount.data === -2048);
|
assert.ok(dataAccount.data === -2048);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe("multisig", () => {
|
||||||
signers: [multisig],
|
signers: [multisig],
|
||||||
});
|
});
|
||||||
|
|
||||||
let multisigAccount = await program.account.multisig(multisig.publicKey);
|
let multisigAccount = await program.account.multisig.fetch(multisig.publicKey);
|
||||||
|
|
||||||
assert.equal(multisigAccount.nonce, nonce);
|
assert.equal(multisigAccount.nonce, nonce);
|
||||||
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
||||||
|
@ -81,7 +81,7 @@ describe("multisig", () => {
|
||||||
signers: [transaction, ownerA],
|
signers: [transaction, ownerA],
|
||||||
});
|
});
|
||||||
|
|
||||||
const txAccount = await program.account.transaction(transaction.publicKey);
|
const txAccount = await program.account.transaction.fetch(transaction.publicKey);
|
||||||
|
|
||||||
assert.ok(txAccount.programId.equals(pid));
|
assert.ok(txAccount.programId.equals(pid));
|
||||||
assert.deepEqual(txAccount.accounts, accounts);
|
assert.deepEqual(txAccount.accounts, accounts);
|
||||||
|
@ -124,7 +124,7 @@ describe("multisig", () => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
multisigAccount = await program.account.multisig(multisig.publicKey);
|
multisigAccount = await program.account.multisig.fetch(multisig.publicKey);
|
||||||
|
|
||||||
assert.equal(multisigAccount.nonce, nonce);
|
assert.equal(multisigAccount.nonce, nonce);
|
||||||
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe("basic-1", () => {
|
||||||
// #endregion code-separated
|
// #endregion code-separated
|
||||||
|
|
||||||
// Fetch the newly created account from the cluster.
|
// Fetch the newly created account from the cluster.
|
||||||
const account = await program.account.myAccount(myAccount.publicKey);
|
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||||
|
|
||||||
// Check it's state was initialized.
|
// Check it's state was initialized.
|
||||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||||
|
@ -81,7 +81,7 @@ describe("basic-1", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the newly created account from the cluster.
|
// Fetch the newly created account from the cluster.
|
||||||
const account = await program.account.myAccount(myAccount.publicKey);
|
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||||
|
|
||||||
// Check it's state was initialized.
|
// Check it's state was initialized.
|
||||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||||
|
@ -108,7 +108,7 @@ describe("basic-1", () => {
|
||||||
// #endregion code-simplified
|
// #endregion code-simplified
|
||||||
|
|
||||||
// Fetch the newly created account from the cluster.
|
// Fetch the newly created account from the cluster.
|
||||||
const account = await program.account.myAccount(myAccount.publicKey);
|
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||||
|
|
||||||
// Check it's state was initialized.
|
// Check it's state was initialized.
|
||||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||||
|
@ -133,7 +133,7 @@ describe("basic-1", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the newly updated account.
|
// Fetch the newly updated account.
|
||||||
const account = await program.account.myAccount(myAccount.publicKey);
|
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||||
|
|
||||||
// Check it's state was mutated.
|
// Check it's state was mutated.
|
||||||
assert.ok(account.data.eq(new anchor.BN(4321)));
|
assert.ok(account.data.eq(new anchor.BN(4321)));
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe('basic-2', () => {
|
||||||
instructions: [await program.account.counter.createInstruction(counter)],
|
instructions: [await program.account.counter.createInstruction(counter)],
|
||||||
})
|
})
|
||||||
|
|
||||||
let counterAccount = await program.account.counter(counter.publicKey)
|
let counterAccount = await program.account.counter.fetch(counter.publicKey)
|
||||||
|
|
||||||
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
||||||
assert.ok(counterAccount.count.toNumber() === 0)
|
assert.ok(counterAccount.count.toNumber() === 0)
|
||||||
|
@ -37,7 +37,7 @@ describe('basic-2', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const counterAccount = await program.account.counter(counter.publicKey)
|
const counterAccount = await program.account.counter.fetch(counter.publicKey)
|
||||||
|
|
||||||
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
||||||
assert.ok(counterAccount.count.toNumber() == 1)
|
assert.ok(counterAccount.count.toNumber() == 1)
|
||||||
|
|
|
@ -41,7 +41,7 @@ describe("basic-3", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check the state updated.
|
// Check the state updated.
|
||||||
puppetAccount = await puppet.account.puppet(newPuppetAccount.publicKey);
|
puppetAccount = await puppet.account.puppet.fetch(newPuppetAccount.publicKey);
|
||||||
assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
|
assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe("basic-4", () => {
|
||||||
|
|
||||||
// Fetch the state struct from the network.
|
// Fetch the state struct from the network.
|
||||||
// #region accessor
|
// #region accessor
|
||||||
const state = await program.state();
|
const state = await program.state.fetch();
|
||||||
// #endregion accessor
|
// #endregion accessor
|
||||||
|
|
||||||
assert.ok(state.count.eq(new anchor.BN(0)));
|
assert.ok(state.count.eq(new anchor.BN(0)));
|
||||||
|
@ -35,7 +35,7 @@ describe("basic-4", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// #endregion instruction
|
// #endregion instruction
|
||||||
const state = await program.state();
|
const state = await program.state.fetch();
|
||||||
assert.ok(state.count.eq(new anchor.BN(1)));
|
assert.ok(state.count.eq(new anchor.BN(1)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe("zero-copy", () => {
|
||||||
authority: program.provider.wallet.publicKey,
|
authority: program.provider.wallet.publicKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const state = await program.state();
|
const state = await program.state.fetch();
|
||||||
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
||||||
assert.ok(state.events.length === 250);
|
assert.ok(state.events.length === 250);
|
||||||
state.events.forEach((event, idx) => {
|
state.events.forEach((event, idx) => {
|
||||||
|
@ -36,7 +36,7 @@ describe("zero-copy", () => {
|
||||||
authority: program.provider.wallet.publicKey,
|
authority: program.provider.wallet.publicKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const state = await program.state();
|
const state = await program.state.fetch();
|
||||||
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
||||||
assert.ok(state.events.length === 250);
|
assert.ok(state.events.length === 250);
|
||||||
state.events.forEach((event, idx) => {
|
state.events.forEach((event, idx) => {
|
||||||
|
@ -60,7 +60,7 @@ describe("zero-copy", () => {
|
||||||
instructions: [await program.account.foo.createInstruction(foo)],
|
instructions: [await program.account.foo.createInstruction(foo)],
|
||||||
signers: [foo],
|
signers: [foo],
|
||||||
});
|
});
|
||||||
const account = await program.account.foo(foo.publicKey);
|
const account = await program.account.foo.fetch(foo.publicKey);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
JSON.stringify(account.authority.toBuffer()) ===
|
JSON.stringify(account.authority.toBuffer()) ===
|
||||||
JSON.stringify(program.provider.wallet.publicKey.toBuffer())
|
JSON.stringify(program.provider.wallet.publicKey.toBuffer())
|
||||||
|
@ -81,7 +81,7 @@ describe("zero-copy", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const account = await program.account.foo(foo.publicKey);
|
const account = await program.account.foo.fetch(foo.publicKey);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
JSON.stringify(account.authority.toBuffer()) ===
|
JSON.stringify(account.authority.toBuffer()) ===
|
||||||
|
@ -103,7 +103,7 @@ describe("zero-copy", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const account = await program.account.foo(foo.publicKey);
|
const account = await program.account.foo.fetch(foo.publicKey);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
JSON.stringify(account.authority.toBuffer()) ===
|
JSON.stringify(account.authority.toBuffer()) ===
|
||||||
|
@ -172,7 +172,7 @@ describe("zero-copy", () => {
|
||||||
],
|
],
|
||||||
signers: [eventQ],
|
signers: [eventQ],
|
||||||
});
|
});
|
||||||
const account = await program.account.eventQ(eventQ.publicKey);
|
const account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||||
assert.ok(account.events.length === 25000);
|
assert.ok(account.events.length === 25000);
|
||||||
account.events.forEach((event) => {
|
account.events.forEach((event) => {
|
||||||
assert.ok(event.from.equals(new PublicKey()));
|
assert.ok(event.from.equals(new PublicKey()));
|
||||||
|
@ -189,7 +189,7 @@ describe("zero-copy", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Verify update.
|
// Verify update.
|
||||||
let account = await program.account.eventQ(eventQ.publicKey);
|
let account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||||
assert.ok(account.events.length === 25000);
|
assert.ok(account.events.length === 25000);
|
||||||
account.events.forEach((event, idx) => {
|
account.events.forEach((event, idx) => {
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
|
@ -209,7 +209,7 @@ describe("zero-copy", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Verify update.
|
// Verify update.
|
||||||
account = await program.account.eventQ(eventQ.publicKey);
|
account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||||
assert.ok(account.events.length === 25000);
|
assert.ok(account.events.length === 25000);
|
||||||
account.events.forEach((event, idx) => {
|
account.events.forEach((event, idx) => {
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
|
@ -232,7 +232,7 @@ describe("zero-copy", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Verify update.
|
// Verify update.
|
||||||
account = await program.account.eventQ(eventQ.publicKey);
|
account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||||
assert.ok(account.events.length === 25000);
|
assert.ok(account.events.length === 25000);
|
||||||
account.events.forEach((event, idx) => {
|
account.events.forEach((event, idx) => {
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"lint:fix": "prettier src/** -w",
|
"lint:fix": "prettier src/** -w",
|
||||||
"watch": "tsc -p tsconfig.cjs.json --watch",
|
"watch": "tsc -p tsconfig.cjs.json --watch",
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"docs": "typedoc --excludePrivate --includeVersion --out ../docs/src/.vuepress/dist/ts/ src/index.ts"
|
"docs": "typedoc --excludePrivate --includeVersion --out ../docs/src/.vuepress/dist/ts/ --readme none src/index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@project-serum/borsh": "^0.2.2",
|
"@project-serum/borsh": "^0.2.2",
|
||||||
|
|
571
ts/src/coder.ts
571
ts/src/coder.ts
|
@ -1,571 +0,0 @@
|
||||||
import camelCase from "camelcase";
|
|
||||||
import * as base64 from "base64-js";
|
|
||||||
import { snakeCase } from "snake-case";
|
|
||||||
import { Layout } from "buffer-layout";
|
|
||||||
import * as sha256 from "js-sha256";
|
|
||||||
import * as borsh from "@project-serum/borsh";
|
|
||||||
import {
|
|
||||||
Idl,
|
|
||||||
IdlField,
|
|
||||||
IdlTypeDef,
|
|
||||||
IdlEnumVariant,
|
|
||||||
IdlType,
|
|
||||||
IdlStateMethod,
|
|
||||||
} from "./idl";
|
|
||||||
import { IdlError } from "./error";
|
|
||||||
import { Event } from "./program/event";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of bytes of the account discriminator.
|
|
||||||
*/
|
|
||||||
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
|
|
||||||
/**
|
|
||||||
* Namespace for state method function signatures.
|
|
||||||
*/
|
|
||||||
export const SIGHASH_STATE_NAMESPACE = "state";
|
|
||||||
/**
|
|
||||||
* Namespace for global instruction function signatures (i.e. functions
|
|
||||||
* that aren't namespaced by the state or any of its trait implementations).
|
|
||||||
*/
|
|
||||||
export const SIGHASH_GLOBAL_NAMESPACE = "global";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coder provides a facade for encoding and decoding all IDL related objects.
|
|
||||||
*/
|
|
||||||
export default class Coder {
|
|
||||||
/**
|
|
||||||
* Instruction coder.
|
|
||||||
*/
|
|
||||||
readonly instruction: InstructionCoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Account coder.
|
|
||||||
*/
|
|
||||||
readonly accounts: AccountsCoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Types coder.
|
|
||||||
*/
|
|
||||||
readonly types: TypesCoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coder for state structs.
|
|
||||||
*/
|
|
||||||
readonly state: StateCoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coder for events.
|
|
||||||
*/
|
|
||||||
readonly events: EventCoder;
|
|
||||||
|
|
||||||
constructor(idl: Idl) {
|
|
||||||
this.instruction = new InstructionCoder(idl);
|
|
||||||
this.accounts = new AccountsCoder(idl);
|
|
||||||
this.types = new TypesCoder(idl);
|
|
||||||
this.events = new EventCoder(idl);
|
|
||||||
if (idl.state) {
|
|
||||||
this.state = new StateCoder(idl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sighash(nameSpace: string, ixName: string): Buffer {
|
|
||||||
return sighash(nameSpace, ixName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes and decodes program instructions.
|
|
||||||
*/
|
|
||||||
class InstructionCoder {
|
|
||||||
/**
|
|
||||||
* Instruction args layout. Maps namespaced method
|
|
||||||
*/
|
|
||||||
private ixLayout: Map<string, Layout>;
|
|
||||||
|
|
||||||
public constructor(idl: Idl) {
|
|
||||||
this.ixLayout = InstructionCoder.parseIxLayout(idl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a program instruction.
|
|
||||||
*/
|
|
||||||
public encode(ixName: string, ix: any) {
|
|
||||||
return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a program state instruction.
|
|
||||||
*/
|
|
||||||
public encodeState(ixName: string, ix: any) {
|
|
||||||
return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
|
|
||||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
|
||||||
const methodName = camelCase(ixName);
|
|
||||||
const len = this.ixLayout.get(methodName).encode(ix, buffer);
|
|
||||||
const data = buffer.slice(0, len);
|
|
||||||
return Buffer.concat([sighash(nameSpace, ixName), data]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parseIxLayout(idl: Idl): Map<string, Layout> {
|
|
||||||
const stateMethods = idl.state ? idl.state.methods : [];
|
|
||||||
|
|
||||||
const ixLayouts = stateMethods
|
|
||||||
.map((m: IdlStateMethod) => {
|
|
||||||
let fieldLayouts = m.args.map((arg: IdlField) => {
|
|
||||||
return IdlCoder.fieldLayout(arg, idl.types);
|
|
||||||
});
|
|
||||||
const name = camelCase(m.name);
|
|
||||||
return [name, borsh.struct(fieldLayouts, name)];
|
|
||||||
})
|
|
||||||
.concat(
|
|
||||||
idl.instructions.map((ix) => {
|
|
||||||
let fieldLayouts = ix.args.map((arg: IdlField) =>
|
|
||||||
IdlCoder.fieldLayout(arg, idl.types)
|
|
||||||
);
|
|
||||||
const name = camelCase(ix.name);
|
|
||||||
return [name, borsh.struct(fieldLayouts, name)];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
return new Map(ixLayouts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes and decodes account objects.
|
|
||||||
*/
|
|
||||||
class AccountsCoder {
|
|
||||||
/**
|
|
||||||
* Maps account type identifier to a layout.
|
|
||||||
*/
|
|
||||||
private accountLayouts: Map<string, Layout>;
|
|
||||||
|
|
||||||
public constructor(idl: Idl) {
|
|
||||||
if (idl.accounts === undefined) {
|
|
||||||
this.accountLayouts = new Map();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const layouts: [string, Layout][] = idl.accounts.map((acc) => {
|
|
||||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.accountLayouts = new Map(layouts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async encode<T = any>(
|
|
||||||
accountName: string,
|
|
||||||
account: T
|
|
||||||
): Promise<Buffer> {
|
|
||||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
|
||||||
const layout = this.accountLayouts.get(accountName);
|
|
||||||
const len = layout.encode(account, buffer);
|
|
||||||
let accountData = buffer.slice(0, len);
|
|
||||||
let discriminator = await accountDiscriminator(accountName);
|
|
||||||
return Buffer.concat([discriminator, accountData]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
|
||||||
// Chop off the discriminator before decoding.
|
|
||||||
const data = ix.slice(8);
|
|
||||||
const layout = this.accountLayouts.get(accountName);
|
|
||||||
return layout.decode(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes and decodes user defined types.
|
|
||||||
*/
|
|
||||||
class TypesCoder {
|
|
||||||
/**
|
|
||||||
* Maps account type identifier to a layout.
|
|
||||||
*/
|
|
||||||
private layouts: Map<string, Layout>;
|
|
||||||
|
|
||||||
public constructor(idl: Idl) {
|
|
||||||
if (idl.types === undefined) {
|
|
||||||
this.layouts = new Map();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const layouts = idl.types.map((acc) => {
|
|
||||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this.layouts = new Map(layouts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public encode<T = any>(accountName: string, account: T): Buffer {
|
|
||||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
|
||||||
const layout = this.layouts.get(accountName);
|
|
||||||
const len = layout.encode(account, buffer);
|
|
||||||
return buffer.slice(0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
|
||||||
const layout = this.layouts.get(accountName);
|
|
||||||
return layout.decode(ix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventCoder {
|
|
||||||
/**
|
|
||||||
* Maps account type identifier to a layout.
|
|
||||||
*/
|
|
||||||
private layouts: Map<string, Layout>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps base64 encoded event discriminator to event name.
|
|
||||||
*/
|
|
||||||
private discriminators: Map<string, string>;
|
|
||||||
|
|
||||||
public constructor(idl: Idl) {
|
|
||||||
if (idl.events === undefined) {
|
|
||||||
this.layouts = new Map();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const layouts = idl.events.map((event) => {
|
|
||||||
let eventTypeDef: IdlTypeDef = {
|
|
||||||
name: event.name,
|
|
||||||
type: {
|
|
||||||
kind: "struct",
|
|
||||||
fields: event.fields.map((f) => {
|
|
||||||
return { name: f.name, type: f.type };
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return [event.name, IdlCoder.typeDefLayout(eventTypeDef, idl.types)];
|
|
||||||
});
|
|
||||||
// @ts-ignore
|
|
||||||
this.layouts = new Map(layouts);
|
|
||||||
|
|
||||||
this.discriminators = new Map<string, string>(
|
|
||||||
idl.events === undefined
|
|
||||||
? []
|
|
||||||
: idl.events.map((e) => [
|
|
||||||
base64.fromByteArray(eventDiscriminator(e.name)),
|
|
||||||
e.name,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode(log: string): Event | null {
|
|
||||||
const logArr = Buffer.from(base64.toByteArray(log));
|
|
||||||
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
|
||||||
|
|
||||||
// Only deserialize if the discriminator implies a proper event.
|
|
||||||
const eventName = this.discriminators.get(disc);
|
|
||||||
if (eventName === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const layout = this.layouts.get(eventName);
|
|
||||||
const data = layout.decode(logArr.slice(8));
|
|
||||||
return { data, name: eventName };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StateCoder {
|
|
||||||
private layout: Layout;
|
|
||||||
|
|
||||||
public constructor(idl: Idl) {
|
|
||||||
if (idl.state === undefined) {
|
|
||||||
throw new Error("Idl state not defined.");
|
|
||||||
}
|
|
||||||
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
|
|
||||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
|
||||||
const len = this.layout.encode(account, buffer);
|
|
||||||
|
|
||||||
const disc = await stateDiscriminator(name);
|
|
||||||
const accData = buffer.slice(0, len);
|
|
||||||
|
|
||||||
return Buffer.concat([disc, accData]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode<T = any>(ix: Buffer): T {
|
|
||||||
// Chop off discriminator.
|
|
||||||
const data = ix.slice(8);
|
|
||||||
return this.layout.decode(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IdlCoder {
|
|
||||||
public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
|
|
||||||
const fieldName =
|
|
||||||
field.name !== undefined ? camelCase(field.name) : undefined;
|
|
||||||
switch (field.type) {
|
|
||||||
case "bool": {
|
|
||||||
return borsh.bool(fieldName);
|
|
||||||
}
|
|
||||||
case "u8": {
|
|
||||||
return borsh.u8(fieldName);
|
|
||||||
}
|
|
||||||
case "i8": {
|
|
||||||
return borsh.i8(fieldName);
|
|
||||||
}
|
|
||||||
case "u16": {
|
|
||||||
return borsh.u16(fieldName);
|
|
||||||
}
|
|
||||||
case "i16": {
|
|
||||||
return borsh.i16(fieldName);
|
|
||||||
}
|
|
||||||
case "u32": {
|
|
||||||
return borsh.u32(fieldName);
|
|
||||||
}
|
|
||||||
case "i32": {
|
|
||||||
return borsh.i32(fieldName);
|
|
||||||
}
|
|
||||||
case "u64": {
|
|
||||||
return borsh.u64(fieldName);
|
|
||||||
}
|
|
||||||
case "i64": {
|
|
||||||
return borsh.i64(fieldName);
|
|
||||||
}
|
|
||||||
case "u128": {
|
|
||||||
return borsh.u128(fieldName);
|
|
||||||
}
|
|
||||||
case "i128": {
|
|
||||||
return borsh.i128(fieldName);
|
|
||||||
}
|
|
||||||
case "bytes": {
|
|
||||||
return borsh.vecU8(fieldName);
|
|
||||||
}
|
|
||||||
case "string": {
|
|
||||||
return borsh.str(fieldName);
|
|
||||||
}
|
|
||||||
case "publicKey": {
|
|
||||||
return borsh.publicKey(fieldName);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
// @ts-ignore
|
|
||||||
if (field.type.vec) {
|
|
||||||
return borsh.vec(
|
|
||||||
IdlCoder.fieldLayout(
|
|
||||||
{
|
|
||||||
name: undefined,
|
|
||||||
// @ts-ignore
|
|
||||||
type: field.type.vec,
|
|
||||||
},
|
|
||||||
types
|
|
||||||
),
|
|
||||||
fieldName
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
} else if (field.type.option) {
|
|
||||||
return borsh.option(
|
|
||||||
IdlCoder.fieldLayout(
|
|
||||||
{
|
|
||||||
name: undefined,
|
|
||||||
// @ts-ignore
|
|
||||||
type: field.type.option,
|
|
||||||
},
|
|
||||||
types
|
|
||||||
),
|
|
||||||
fieldName
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
} else if (field.type.defined) {
|
|
||||||
// User defined type.
|
|
||||||
if (types === undefined) {
|
|
||||||
throw new IdlError("User defined types not provided");
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const filtered = types.filter((t) => t.name === field.type.defined);
|
|
||||||
if (filtered.length !== 1) {
|
|
||||||
throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
|
|
||||||
}
|
|
||||||
return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
|
|
||||||
// @ts-ignore
|
|
||||||
} else if (field.type.array) {
|
|
||||||
// @ts-ignore
|
|
||||||
let arrayTy = field.type.array[0];
|
|
||||||
// @ts-ignore
|
|
||||||
let arrayLen = field.type.array[1];
|
|
||||||
let innerLayout = IdlCoder.fieldLayout(
|
|
||||||
{
|
|
||||||
name: undefined,
|
|
||||||
type: arrayTy,
|
|
||||||
},
|
|
||||||
types
|
|
||||||
);
|
|
||||||
return borsh.array(innerLayout, arrayLen, fieldName);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Not yet implemented: ${field}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static typeDefLayout(
|
|
||||||
typeDef: IdlTypeDef,
|
|
||||||
types: IdlTypeDef[],
|
|
||||||
name?: string
|
|
||||||
): Layout {
|
|
||||||
if (typeDef.type.kind === "struct") {
|
|
||||||
const fieldLayouts = typeDef.type.fields.map((field) => {
|
|
||||||
const x = IdlCoder.fieldLayout(field, types);
|
|
||||||
return x;
|
|
||||||
});
|
|
||||||
return borsh.struct(fieldLayouts, name);
|
|
||||||
} else if (typeDef.type.kind === "enum") {
|
|
||||||
let variants = typeDef.type.variants.map((variant: IdlEnumVariant) => {
|
|
||||||
const name = camelCase(variant.name);
|
|
||||||
if (variant.fields === undefined) {
|
|
||||||
return borsh.struct([], name);
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const fieldLayouts = variant.fields.map((f: IdlField | IdlType) => {
|
|
||||||
// @ts-ignore
|
|
||||||
if (f.name === undefined) {
|
|
||||||
throw new Error("Tuple enum variants not yet implemented.");
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return IdlCoder.fieldLayout(f, types);
|
|
||||||
});
|
|
||||||
return borsh.struct(fieldLayouts, name);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (name !== undefined) {
|
|
||||||
// Buffer-layout lib requires the name to be null (on construction)
|
|
||||||
// when used as a field.
|
|
||||||
return borsh.rustEnum(variants).replicate(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return borsh.rustEnum(variants, name);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown type kint: ${typeDef}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates unique 8 byte discriminator prepended to all anchor accounts.
|
|
||||||
export async function accountDiscriminator(name: string): Promise<Buffer> {
|
|
||||||
// @ts-ignore
|
|
||||||
return Buffer.from(sha256.digest(`account:${name}`)).slice(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
|
||||||
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
|
||||||
// @ts-ignore
|
|
||||||
return Buffer.from(sha256.digest(`state:${name}`)).slice(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function eventDiscriminator(name: string): Buffer {
|
|
||||||
// @ts-ignore
|
|
||||||
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the size of the type in bytes. For variable length types, just return
|
|
||||||
// 1. Users should override this value in such cases.
|
|
||||||
function typeSize(idl: Idl, ty: IdlType): number {
|
|
||||||
switch (ty) {
|
|
||||||
case "bool":
|
|
||||||
return 1;
|
|
||||||
case "u8":
|
|
||||||
return 1;
|
|
||||||
case "i8":
|
|
||||||
return 1;
|
|
||||||
case "i16":
|
|
||||||
return 2;
|
|
||||||
case "u16":
|
|
||||||
return 2;
|
|
||||||
case "u32":
|
|
||||||
return 4;
|
|
||||||
case "i32":
|
|
||||||
return 4;
|
|
||||||
case "u64":
|
|
||||||
return 8;
|
|
||||||
case "i64":
|
|
||||||
return 8;
|
|
||||||
case "u128":
|
|
||||||
return 16;
|
|
||||||
case "i128":
|
|
||||||
return 16;
|
|
||||||
case "bytes":
|
|
||||||
return 1;
|
|
||||||
case "string":
|
|
||||||
return 1;
|
|
||||||
case "publicKey":
|
|
||||||
return 32;
|
|
||||||
default:
|
|
||||||
// @ts-ignore
|
|
||||||
if (ty.vec !== undefined) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
if (ty.option !== undefined) {
|
|
||||||
// @ts-ignore
|
|
||||||
return 1 + typeSize(idl, ty.option);
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
if (ty.defined !== undefined) {
|
|
||||||
// @ts-ignore
|
|
||||||
const filtered = idl.types.filter((t) => t.name === ty.defined);
|
|
||||||
if (filtered.length !== 1) {
|
|
||||||
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
|
|
||||||
}
|
|
||||||
let typeDef = filtered[0];
|
|
||||||
|
|
||||||
return accountSize(idl, typeDef);
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
if (ty.array !== undefined) {
|
|
||||||
// @ts-ignore
|
|
||||||
let arrayTy = ty.array[0];
|
|
||||||
// @ts-ignore
|
|
||||||
let arraySize = ty.array[1];
|
|
||||||
// @ts-ignore
|
|
||||||
return typeSize(idl, arrayTy) * arraySize;
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function accountSize(
|
|
||||||
idl: Idl,
|
|
||||||
idlAccount: IdlTypeDef
|
|
||||||
): number | undefined {
|
|
||||||
if (idlAccount.type.kind === "enum") {
|
|
||||||
let variantSizes = idlAccount.type.variants.map(
|
|
||||||
(variant: IdlEnumVariant) => {
|
|
||||||
if (variant.fields === undefined) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return (
|
|
||||||
variant.fields
|
|
||||||
// @ts-ignore
|
|
||||||
.map((f: IdlField | IdlType) => {
|
|
||||||
// @ts-ignore
|
|
||||||
if (f.name === undefined) {
|
|
||||||
throw new Error("Tuple enum variants not yet implemented.");
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return typeSize(idl, f.type);
|
|
||||||
})
|
|
||||||
.reduce((a: number, b: number) => a + b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return Math.max(...variantSizes) + 1;
|
|
||||||
}
|
|
||||||
if (idlAccount.type.fields === undefined) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return idlAccount.type.fields
|
|
||||||
.map((f) => typeSize(idl, f.type))
|
|
||||||
.reduce((a, b) => a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not technically sighash, since we don't include the arguments, as Rust
|
|
||||||
// doesn't allow function overloading.
|
|
||||||
function sighash(nameSpace: string, ixName: string): Buffer {
|
|
||||||
let name = snakeCase(ixName);
|
|
||||||
let preimage = `${nameSpace}:${name}`;
|
|
||||||
// @ts-ignore
|
|
||||||
return Buffer.from(sha256.digest(preimage)).slice(0, 8);
|
|
||||||
}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import { Idl } from "../idl";
|
||||||
|
import { IdlCoder } from "./idl";
|
||||||
|
import { sha256 } from "js-sha256";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes of the account discriminator.
|
||||||
|
*/
|
||||||
|
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes and decodes account objects.
|
||||||
|
*/
|
||||||
|
export class AccountsCoder {
|
||||||
|
/**
|
||||||
|
* Maps account type identifier to a layout.
|
||||||
|
*/
|
||||||
|
private accountLayouts: Map<string, Layout>;
|
||||||
|
|
||||||
|
public constructor(idl: Idl) {
|
||||||
|
if (idl.accounts === undefined) {
|
||||||
|
this.accountLayouts = new Map();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const layouts: [string, Layout][] = idl.accounts.map((acc) => {
|
||||||
|
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.accountLayouts = new Map(layouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async encode<T = any>(
|
||||||
|
accountName: string,
|
||||||
|
account: T
|
||||||
|
): Promise<Buffer> {
|
||||||
|
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||||
|
const layout = this.accountLayouts.get(accountName);
|
||||||
|
const len = layout.encode(account, buffer);
|
||||||
|
let accountData = buffer.slice(0, len);
|
||||||
|
let discriminator = await accountDiscriminator(accountName);
|
||||||
|
return Buffer.concat([discriminator, accountData]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||||
|
// Chop off the discriminator before decoding.
|
||||||
|
const data = ix.slice(8);
|
||||||
|
const layout = this.accountLayouts.get(accountName);
|
||||||
|
return layout.decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates unique 8 byte discriminator prepended to all anchor accounts.
|
||||||
|
export async function accountDiscriminator(name: string): Promise<Buffer> {
|
||||||
|
return Buffer.from(sha256.digest(`account:${name}`)).slice(0, 8);
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { snakeCase } from "snake-case";
|
||||||
|
import { sha256 } from "js-sha256";
|
||||||
|
import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
||||||
|
import { IdlError } from "../error";
|
||||||
|
|
||||||
|
export function accountSize(
|
||||||
|
idl: Idl,
|
||||||
|
idlAccount: IdlTypeDef
|
||||||
|
): number | undefined {
|
||||||
|
if (idlAccount.type.kind === "enum") {
|
||||||
|
let variantSizes = idlAccount.type.variants.map(
|
||||||
|
(variant: IdlEnumVariant) => {
|
||||||
|
if (variant.fields === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
variant.fields
|
||||||
|
// @ts-ignore
|
||||||
|
.map((f: IdlField | IdlType) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (f.name === undefined) {
|
||||||
|
throw new Error("Tuple enum variants not yet implemented.");
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
return typeSize(idl, f.type);
|
||||||
|
})
|
||||||
|
.reduce((a: number, b: number) => a + b)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Math.max(...variantSizes) + 1;
|
||||||
|
}
|
||||||
|
if (idlAccount.type.fields === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return idlAccount.type.fields
|
||||||
|
.map((f) => typeSize(idl, f.type))
|
||||||
|
.reduce((a, b) => a + b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size of the type in bytes. For variable length types, just return
|
||||||
|
// 1. Users should override this value in such cases.
|
||||||
|
function typeSize(idl: Idl, ty: IdlType): number {
|
||||||
|
switch (ty) {
|
||||||
|
case "bool":
|
||||||
|
return 1;
|
||||||
|
case "u8":
|
||||||
|
return 1;
|
||||||
|
case "i8":
|
||||||
|
return 1;
|
||||||
|
case "i16":
|
||||||
|
return 2;
|
||||||
|
case "u16":
|
||||||
|
return 2;
|
||||||
|
case "u32":
|
||||||
|
return 4;
|
||||||
|
case "i32":
|
||||||
|
return 4;
|
||||||
|
case "u64":
|
||||||
|
return 8;
|
||||||
|
case "i64":
|
||||||
|
return 8;
|
||||||
|
case "u128":
|
||||||
|
return 16;
|
||||||
|
case "i128":
|
||||||
|
return 16;
|
||||||
|
case "bytes":
|
||||||
|
return 1;
|
||||||
|
case "string":
|
||||||
|
return 1;
|
||||||
|
case "publicKey":
|
||||||
|
return 32;
|
||||||
|
default:
|
||||||
|
// @ts-ignore
|
||||||
|
if (ty.vec !== undefined) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (ty.option !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
return 1 + typeSize(idl, ty.option);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (ty.defined !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
const filtered = idl.types.filter((t) => t.name === ty.defined);
|
||||||
|
if (filtered.length !== 1) {
|
||||||
|
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
|
||||||
|
}
|
||||||
|
let typeDef = filtered[0];
|
||||||
|
|
||||||
|
return accountSize(idl, typeDef);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (ty.array !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
let arrayTy = ty.array[0];
|
||||||
|
// @ts-ignore
|
||||||
|
let arraySize = ty.array[1];
|
||||||
|
// @ts-ignore
|
||||||
|
return typeSize(idl, arrayTy) * arraySize;
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not technically sighash, since we don't include the arguments, as Rust
|
||||||
|
// doesn't allow function overloading.
|
||||||
|
export function sighash(nameSpace: string, ixName: string): Buffer {
|
||||||
|
let name = snakeCase(ixName);
|
||||||
|
let preimage = `${nameSpace}:${name}`;
|
||||||
|
return Buffer.from(sha256.digest(preimage)).slice(0, 8);
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import * as base64 from "base64-js";
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import { sha256 } from "js-sha256";
|
||||||
|
import { Idl, IdlTypeDef } from "../idl";
|
||||||
|
import { Event } from "../program/event";
|
||||||
|
import { IdlCoder } from "./idl";
|
||||||
|
|
||||||
|
export class EventCoder {
|
||||||
|
/**
|
||||||
|
* Maps account type identifier to a layout.
|
||||||
|
*/
|
||||||
|
private layouts: Map<string, Layout>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps base64 encoded event discriminator to event name.
|
||||||
|
*/
|
||||||
|
private discriminators: Map<string, string>;
|
||||||
|
|
||||||
|
public constructor(idl: Idl) {
|
||||||
|
if (idl.events === undefined) {
|
||||||
|
this.layouts = new Map();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const layouts = idl.events.map((event) => {
|
||||||
|
let eventTypeDef: IdlTypeDef = {
|
||||||
|
name: event.name,
|
||||||
|
type: {
|
||||||
|
kind: "struct",
|
||||||
|
fields: event.fields.map((f) => {
|
||||||
|
return { name: f.name, type: f.type };
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return [event.name, IdlCoder.typeDefLayout(eventTypeDef, idl.types)];
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
this.layouts = new Map(layouts);
|
||||||
|
|
||||||
|
this.discriminators = new Map<string, string>(
|
||||||
|
idl.events === undefined
|
||||||
|
? []
|
||||||
|
: idl.events.map((e) => [
|
||||||
|
base64.fromByteArray(eventDiscriminator(e.name)),
|
||||||
|
e.name,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode(log: string): Event | null {
|
||||||
|
const logArr = Buffer.from(base64.toByteArray(log));
|
||||||
|
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
||||||
|
|
||||||
|
// Only deserialize if the discriminator implies a proper event.
|
||||||
|
const eventName = this.discriminators.get(disc);
|
||||||
|
if (eventName === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layout = this.layouts.get(eventName);
|
||||||
|
const data = layout.decode(logArr.slice(8));
|
||||||
|
return { data, name: eventName };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eventDiscriminator(name: string): Buffer {
|
||||||
|
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
import camelCase from "camelcase";
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import * as borsh from "@project-serum/borsh";
|
||||||
|
import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
||||||
|
import { IdlError } from "../error";
|
||||||
|
|
||||||
|
export class IdlCoder {
|
||||||
|
public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
|
||||||
|
const fieldName =
|
||||||
|
field.name !== undefined ? camelCase(field.name) : undefined;
|
||||||
|
switch (field.type) {
|
||||||
|
case "bool": {
|
||||||
|
return borsh.bool(fieldName);
|
||||||
|
}
|
||||||
|
case "u8": {
|
||||||
|
return borsh.u8(fieldName);
|
||||||
|
}
|
||||||
|
case "i8": {
|
||||||
|
return borsh.i8(fieldName);
|
||||||
|
}
|
||||||
|
case "u16": {
|
||||||
|
return borsh.u16(fieldName);
|
||||||
|
}
|
||||||
|
case "i16": {
|
||||||
|
return borsh.i16(fieldName);
|
||||||
|
}
|
||||||
|
case "u32": {
|
||||||
|
return borsh.u32(fieldName);
|
||||||
|
}
|
||||||
|
case "i32": {
|
||||||
|
return borsh.i32(fieldName);
|
||||||
|
}
|
||||||
|
case "u64": {
|
||||||
|
return borsh.u64(fieldName);
|
||||||
|
}
|
||||||
|
case "i64": {
|
||||||
|
return borsh.i64(fieldName);
|
||||||
|
}
|
||||||
|
case "u128": {
|
||||||
|
return borsh.u128(fieldName);
|
||||||
|
}
|
||||||
|
case "i128": {
|
||||||
|
return borsh.i128(fieldName);
|
||||||
|
}
|
||||||
|
case "bytes": {
|
||||||
|
return borsh.vecU8(fieldName);
|
||||||
|
}
|
||||||
|
case "string": {
|
||||||
|
return borsh.str(fieldName);
|
||||||
|
}
|
||||||
|
case "publicKey": {
|
||||||
|
return borsh.publicKey(fieldName);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// @ts-ignore
|
||||||
|
if (field.type.vec) {
|
||||||
|
return borsh.vec(
|
||||||
|
IdlCoder.fieldLayout(
|
||||||
|
{
|
||||||
|
name: undefined,
|
||||||
|
// @ts-ignore
|
||||||
|
type: field.type.vec,
|
||||||
|
},
|
||||||
|
types
|
||||||
|
),
|
||||||
|
fieldName
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
} else if (field.type.option) {
|
||||||
|
return borsh.option(
|
||||||
|
IdlCoder.fieldLayout(
|
||||||
|
{
|
||||||
|
name: undefined,
|
||||||
|
// @ts-ignore
|
||||||
|
type: field.type.option,
|
||||||
|
},
|
||||||
|
types
|
||||||
|
),
|
||||||
|
fieldName
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
} else if (field.type.defined) {
|
||||||
|
// User defined type.
|
||||||
|
if (types === undefined) {
|
||||||
|
throw new IdlError("User defined types not provided");
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const filtered = types.filter((t) => t.name === field.type.defined);
|
||||||
|
if (filtered.length !== 1) {
|
||||||
|
throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
|
||||||
|
}
|
||||||
|
return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
|
||||||
|
// @ts-ignore
|
||||||
|
} else if (field.type.array) {
|
||||||
|
// @ts-ignore
|
||||||
|
let arrayTy = field.type.array[0];
|
||||||
|
// @ts-ignore
|
||||||
|
let arrayLen = field.type.array[1];
|
||||||
|
let innerLayout = IdlCoder.fieldLayout(
|
||||||
|
{
|
||||||
|
name: undefined,
|
||||||
|
type: arrayTy,
|
||||||
|
},
|
||||||
|
types
|
||||||
|
);
|
||||||
|
return borsh.array(innerLayout, arrayLen, fieldName);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Not yet implemented: ${field}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static typeDefLayout(
|
||||||
|
typeDef: IdlTypeDef,
|
||||||
|
types: IdlTypeDef[],
|
||||||
|
name?: string
|
||||||
|
): Layout {
|
||||||
|
if (typeDef.type.kind === "struct") {
|
||||||
|
const fieldLayouts = typeDef.type.fields.map((field) => {
|
||||||
|
const x = IdlCoder.fieldLayout(field, types);
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
return borsh.struct(fieldLayouts, name);
|
||||||
|
} else if (typeDef.type.kind === "enum") {
|
||||||
|
let variants = typeDef.type.variants.map((variant: IdlEnumVariant) => {
|
||||||
|
const name = camelCase(variant.name);
|
||||||
|
if (variant.fields === undefined) {
|
||||||
|
return borsh.struct([], name);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const fieldLayouts = variant.fields.map((f: IdlField | IdlType) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (f.name === undefined) {
|
||||||
|
throw new Error("Tuple enum variants not yet implemented.");
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
return IdlCoder.fieldLayout(f, types);
|
||||||
|
});
|
||||||
|
return borsh.struct(fieldLayouts, name);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (name !== undefined) {
|
||||||
|
// Buffer-layout lib requires the name to be null (on construction)
|
||||||
|
// when used as a field.
|
||||||
|
return borsh.rustEnum(variants).replicate(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return borsh.rustEnum(variants, name);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown type kint: ${typeDef}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { Idl } from "../idl";
|
||||||
|
import { InstructionCoder } from "./instruction";
|
||||||
|
import { AccountsCoder } from "./accounts";
|
||||||
|
import { TypesCoder } from "./types";
|
||||||
|
import { EventCoder } from "./event";
|
||||||
|
import { StateCoder } from "./state";
|
||||||
|
import { sighash } from "./common";
|
||||||
|
|
||||||
|
export { accountSize } from "./common";
|
||||||
|
export { TypesCoder } from "./types";
|
||||||
|
export { InstructionCoder } from "./instruction";
|
||||||
|
export {
|
||||||
|
AccountsCoder,
|
||||||
|
accountDiscriminator,
|
||||||
|
ACCOUNT_DISCRIMINATOR_SIZE,
|
||||||
|
} from "./accounts";
|
||||||
|
export { EventCoder, eventDiscriminator } from "./event";
|
||||||
|
export { StateCoder, stateDiscriminator } from "./state";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coder provides a facade for encoding and decoding all IDL related objects.
|
||||||
|
*/
|
||||||
|
export default class Coder {
|
||||||
|
/**
|
||||||
|
* Instruction coder.
|
||||||
|
*/
|
||||||
|
readonly instruction: InstructionCoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account coder.
|
||||||
|
*/
|
||||||
|
readonly accounts: AccountsCoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types coder.
|
||||||
|
*/
|
||||||
|
readonly types: TypesCoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coder for state structs.
|
||||||
|
*/
|
||||||
|
readonly state: StateCoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coder for events.
|
||||||
|
*/
|
||||||
|
readonly events: EventCoder;
|
||||||
|
|
||||||
|
constructor(idl: Idl) {
|
||||||
|
this.instruction = new InstructionCoder(idl);
|
||||||
|
this.accounts = new AccountsCoder(idl);
|
||||||
|
this.types = new TypesCoder(idl);
|
||||||
|
this.events = new EventCoder(idl);
|
||||||
|
if (idl.state) {
|
||||||
|
this.state = new StateCoder(idl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sighash(nameSpace: string, ixName: string): Buffer {
|
||||||
|
return sighash(nameSpace, ixName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import camelCase from "camelcase";
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import * as borsh from "@project-serum/borsh";
|
||||||
|
import { Idl, IdlField, IdlStateMethod } from "../idl";
|
||||||
|
import { IdlCoder } from "./idl";
|
||||||
|
import { sighash } from "./common";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for state method function signatures.
|
||||||
|
*/
|
||||||
|
export const SIGHASH_STATE_NAMESPACE = "state";
|
||||||
|
/**
|
||||||
|
* Namespace for global instruction function signatures (i.e. functions
|
||||||
|
* that aren't namespaced by the state or any of its trait implementations).
|
||||||
|
*/
|
||||||
|
export const SIGHASH_GLOBAL_NAMESPACE = "global";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes and decodes program instructions.
|
||||||
|
*/
|
||||||
|
export class InstructionCoder {
|
||||||
|
/**
|
||||||
|
* Instruction args layout. Maps namespaced method
|
||||||
|
*/
|
||||||
|
private ixLayout: Map<string, Layout>;
|
||||||
|
|
||||||
|
public constructor(idl: Idl) {
|
||||||
|
this.ixLayout = InstructionCoder.parseIxLayout(idl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a program instruction.
|
||||||
|
*/
|
||||||
|
public encode(ixName: string, ix: any) {
|
||||||
|
return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a program state instruction.
|
||||||
|
*/
|
||||||
|
public encodeState(ixName: string, ix: any) {
|
||||||
|
return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
|
||||||
|
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||||
|
const methodName = camelCase(ixName);
|
||||||
|
const len = this.ixLayout.get(methodName).encode(ix, buffer);
|
||||||
|
const data = buffer.slice(0, len);
|
||||||
|
return Buffer.concat([sighash(nameSpace, ixName), data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseIxLayout(idl: Idl): Map<string, Layout> {
|
||||||
|
const stateMethods = idl.state ? idl.state.methods : [];
|
||||||
|
|
||||||
|
const ixLayouts = stateMethods
|
||||||
|
.map((m: IdlStateMethod) => {
|
||||||
|
let fieldLayouts = m.args.map((arg: IdlField) => {
|
||||||
|
return IdlCoder.fieldLayout(arg, idl.types);
|
||||||
|
});
|
||||||
|
const name = camelCase(m.name);
|
||||||
|
return [name, borsh.struct(fieldLayouts, name)];
|
||||||
|
})
|
||||||
|
.concat(
|
||||||
|
idl.instructions.map((ix) => {
|
||||||
|
let fieldLayouts = ix.args.map((arg: IdlField) =>
|
||||||
|
IdlCoder.fieldLayout(arg, idl.types)
|
||||||
|
);
|
||||||
|
const name = camelCase(ix.name);
|
||||||
|
return [name, borsh.struct(fieldLayouts, name)];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
return new Map(ixLayouts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import { sha256 } from "js-sha256";
|
||||||
|
import { Idl } from "../idl";
|
||||||
|
import { IdlCoder } from "./idl";
|
||||||
|
|
||||||
|
export class StateCoder {
|
||||||
|
private layout: Layout;
|
||||||
|
|
||||||
|
public constructor(idl: Idl) {
|
||||||
|
if (idl.state === undefined) {
|
||||||
|
throw new Error("Idl state not defined.");
|
||||||
|
}
|
||||||
|
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
|
||||||
|
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||||
|
const len = this.layout.encode(account, buffer);
|
||||||
|
|
||||||
|
const disc = await stateDiscriminator(name);
|
||||||
|
const accData = buffer.slice(0, len);
|
||||||
|
|
||||||
|
return Buffer.concat([disc, accData]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode<T = any>(ix: Buffer): T {
|
||||||
|
// Chop off discriminator.
|
||||||
|
const data = ix.slice(8);
|
||||||
|
return this.layout.decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
||||||
|
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
||||||
|
return Buffer.from(sha256.digest(`state:${name}`)).slice(0, 8);
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Layout } from "buffer-layout";
|
||||||
|
import { Idl } from "../idl";
|
||||||
|
import { IdlCoder } from "./idl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes and decodes user defined types.
|
||||||
|
*/
|
||||||
|
export class TypesCoder {
|
||||||
|
/**
|
||||||
|
* Maps account type identifier to a layout.
|
||||||
|
*/
|
||||||
|
private layouts: Map<string, Layout>;
|
||||||
|
|
||||||
|
public constructor(idl: Idl) {
|
||||||
|
if (idl.types === undefined) {
|
||||||
|
this.layouts = new Map();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const layouts = idl.types.map((acc) => {
|
||||||
|
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.layouts = new Map(layouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public encode<T = any>(accountName: string, account: T): Buffer {
|
||||||
|
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||||
|
const layout = this.layouts.get(accountName);
|
||||||
|
const len = layout.encode(account, buffer);
|
||||||
|
return buffer.slice(0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||||
|
const layout = this.layouts.get(accountName);
|
||||||
|
return layout.decode(ix);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,6 @@ export type IdlInstruction = {
|
||||||
args: IdlField[];
|
args: IdlField[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// IdlStateMethods are similar to instructions, except they only allow
|
|
||||||
// for a single account, the state account.
|
|
||||||
export type IdlState = {
|
export type IdlState = {
|
||||||
struct: IdlTypeDef;
|
struct: IdlTypeDef;
|
||||||
methods: IdlStateMethod[];
|
methods: IdlStateMethod[];
|
||||||
|
@ -80,6 +78,8 @@ export type IdlType =
|
||||||
| "i32"
|
| "i32"
|
||||||
| "u64"
|
| "u64"
|
||||||
| "i64"
|
| "i64"
|
||||||
|
| "u128"
|
||||||
|
| "i128"
|
||||||
| "bytes"
|
| "bytes"
|
||||||
| "string"
|
| "string"
|
||||||
| "publicKey"
|
| "publicKey"
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
import BN from "bn.js";
|
import BN from "bn.js";
|
||||||
import * as web3 from "@solana/web3.js";
|
import * as web3 from "@solana/web3.js";
|
||||||
import Provider, { NodeWallet as Wallet } from "./provider";
|
import Provider, { NodeWallet as Wallet } from "./provider";
|
||||||
import Coder from "./coder";
|
import Coder, {
|
||||||
|
InstructionCoder,
|
||||||
|
EventCoder,
|
||||||
|
StateCoder,
|
||||||
|
TypesCoder,
|
||||||
|
} from "./coder";
|
||||||
import { Idl } from "./idl";
|
import { Idl } from "./idl";
|
||||||
import workspace from "./workspace";
|
import workspace from "./workspace";
|
||||||
import utils from "./utils";
|
import utils from "./utils";
|
||||||
import { Program } from "./program";
|
import { Program } from "./program";
|
||||||
import { Address } from "./program/common";
|
import { Address } from "./program/common";
|
||||||
import { ProgramAccount } from "./program/namespace";
|
import { Event } from "./program/event";
|
||||||
|
import {
|
||||||
|
ProgramAccount,
|
||||||
|
AccountNamespace,
|
||||||
|
AccountClient,
|
||||||
|
StateClient,
|
||||||
|
RpcNamespace,
|
||||||
|
RpcFn,
|
||||||
|
SimulateNamespace,
|
||||||
|
SimulateFn,
|
||||||
|
TransactionNamespace,
|
||||||
|
TransactionFn,
|
||||||
|
InstructionNamespace,
|
||||||
|
InstructionFn,
|
||||||
|
} from "./program/namespace";
|
||||||
import { Context, Accounts } from "./program/context";
|
import { Context, Accounts } from "./program/context";
|
||||||
|
|
||||||
let _provider: Provider | null = null;
|
let _provider: Provider | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default provider on the client.
|
||||||
|
*/
|
||||||
function setProvider(provider: Provider) {
|
function setProvider(provider: Provider) {
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default provider being used by the client.
|
||||||
|
*/
|
||||||
function getProvider(): Provider {
|
function getProvider(): Provider {
|
||||||
if (_provider === null) {
|
if (_provider === null) {
|
||||||
return Provider.local();
|
return Provider.local();
|
||||||
|
@ -26,10 +51,26 @@ function getProvider(): Provider {
|
||||||
export {
|
export {
|
||||||
workspace,
|
workspace,
|
||||||
Program,
|
Program,
|
||||||
|
AccountNamespace,
|
||||||
|
AccountClient,
|
||||||
|
StateClient,
|
||||||
|
RpcNamespace,
|
||||||
|
RpcFn,
|
||||||
|
SimulateNamespace,
|
||||||
|
SimulateFn,
|
||||||
|
TransactionNamespace,
|
||||||
|
TransactionFn,
|
||||||
|
InstructionNamespace,
|
||||||
|
InstructionFn,
|
||||||
ProgramAccount,
|
ProgramAccount,
|
||||||
Context,
|
Context,
|
||||||
Accounts,
|
Accounts,
|
||||||
Coder,
|
Coder,
|
||||||
|
InstructionCoder,
|
||||||
|
EventCoder,
|
||||||
|
StateCoder,
|
||||||
|
TypesCoder,
|
||||||
|
Event,
|
||||||
setProvider,
|
setProvider,
|
||||||
getProvider,
|
getProvider,
|
||||||
Provider,
|
Provider,
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
import * as bs58 from "bs58";
|
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
|
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
|
||||||
import { ProgramError } from "../error";
|
import { ProgramError } from "../error";
|
||||||
import { Accounts } from "./context";
|
import { Accounts } from "./context";
|
||||||
import Provider from "../provider";
|
|
||||||
|
|
||||||
export type Subscription = {
|
export type Subscription = {
|
||||||
listener: number;
|
listener: number;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import NamespaceFactory, {
|
||||||
InstructionNamespace,
|
InstructionNamespace,
|
||||||
TransactionNamespace,
|
TransactionNamespace,
|
||||||
AccountNamespace,
|
AccountNamespace,
|
||||||
StateNamespace,
|
StateClient,
|
||||||
SimulateNamespace,
|
SimulateNamespace,
|
||||||
} from "./namespace";
|
} from "./namespace";
|
||||||
import { getProvider } from "../";
|
import { getProvider } from "../";
|
||||||
|
@ -28,27 +28,29 @@ import { Address, translateAddress } from "./common";
|
||||||
* changes, and listen to events.
|
* changes, and listen to events.
|
||||||
*
|
*
|
||||||
* In addition to field accessors and methods, the object provides a set of
|
* In addition to field accessors and methods, the object provides a set of
|
||||||
* dynamically generated properties (internally referred to as namespaces) that
|
* dynamically generated properties, also known as namespaces, that
|
||||||
* map one-to-one to program instructions and accounts. These namespaces
|
* map one-to-one to program methods and accounts. These namespaces generally
|
||||||
* generally can be used as follows:
|
* can be used as follows:
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.<namespace>.<program-specific-field>
|
* program.<namespace>.<program-specific-method>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* API specifics are namespace dependent. The examples used in the documentation
|
* API specifics are namespace dependent. The examples used in the documentation
|
||||||
* below will refer to the two counter examples found
|
* below will refer to the two counter examples found
|
||||||
* [here](https://project-serum.github.io/anchor/ts/#examples).
|
* [here](https://github.com/project-serum/anchor#examples).
|
||||||
*/
|
*/
|
||||||
export class Program {
|
export class Program {
|
||||||
/**
|
/**
|
||||||
* Async methods to send signed transactions invoking *non*-state methods
|
* Async methods to send signed transactions to *non*-state methods on the
|
||||||
* on an Anchor program.
|
* program, returning a [[TransactionSignature]].
|
||||||
*
|
*
|
||||||
* ## rpc
|
* ## Usage
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.rpc.<method>(...args, ctx);
|
* rpc.<method>(...args, ctx);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ## Parameters
|
* ## Parameters
|
||||||
|
@ -74,32 +76,32 @@ export class Program {
|
||||||
readonly rpc: RpcNamespace;
|
readonly rpc: RpcNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async functions to fetch deserialized program accounts from a cluster.
|
* The namespace provides handles to an [[AccountClient]] object for each
|
||||||
|
* account in the program.
|
||||||
*
|
*
|
||||||
* ## account
|
* ## Usage
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.account.<account>(address);
|
* program.account.<account-client>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ## Parameters
|
|
||||||
*
|
|
||||||
* 1. `address` - The [[Address]] of the account.
|
|
||||||
*
|
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
* To fetch a `Counter` object from the above example,
|
* To fetch a `Counter` account from the above example,
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* const counter = await program.account.counter(address);
|
* const counter = await program.account.counter.fetch(address);
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
* For the full API, see the [[AccountClient]] reference.
|
||||||
*/
|
*/
|
||||||
readonly account: AccountNamespace;
|
readonly account: AccountNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions to build [[TransactionInstruction]] objects for program methods.
|
* The namespace provides functions to build [[TransactionInstruction]]
|
||||||
|
* objects for each method of a program.
|
||||||
*
|
*
|
||||||
* ## instruction
|
* ## Usage
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.instruction.<method>(...args, ctx);
|
* program.instruction.<method>(...args, ctx);
|
||||||
|
@ -127,9 +129,10 @@ export class Program {
|
||||||
readonly instruction: InstructionNamespace;
|
readonly instruction: InstructionNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions to build [[Transaction]] objects.
|
* The namespace provides functions to build [[Transaction]] objects for each
|
||||||
|
* method of a program.
|
||||||
*
|
*
|
||||||
* ## transaction
|
* ## Usage
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* program.transaction.<method>(...args, ctx);
|
* program.transaction.<method>(...args, ctx);
|
||||||
|
@ -157,8 +160,9 @@ export class Program {
|
||||||
readonly transaction: TransactionNamespace;
|
readonly transaction: TransactionNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async functions to simulate instructions against an Anchor program,
|
* The namespace provides functions to simulate transactions for each method
|
||||||
* returning a list of deserialized events *and* raw program logs.
|
* of a program, returning a list of deserialized events *and* raw program
|
||||||
|
* logs.
|
||||||
*
|
*
|
||||||
* One can use this to read data calculated from a program on chain, by
|
* One can use this to read data calculated from a program on chain, by
|
||||||
* emitting an event in the program and reading the emitted event client side
|
* emitting an event in the program and reading the emitted event client side
|
||||||
|
@ -182,7 +186,7 @@ export class Program {
|
||||||
* To simulate the `increment` method above,
|
* To simulate the `increment` method above,
|
||||||
*
|
*
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* const tx = await program.simulate.increment({
|
* const events = await program.simulate.increment({
|
||||||
* accounts: {
|
* accounts: {
|
||||||
* counter,
|
* counter,
|
||||||
* },
|
* },
|
||||||
|
@ -192,9 +196,11 @@ export class Program {
|
||||||
readonly simulate: SimulateNamespace;
|
readonly simulate: SimulateNamespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with state account accessors and rpcs.
|
* A client for the program state. Similar to the base [[Program]] client,
|
||||||
|
* one can use this to send transactions and read accounts for the state
|
||||||
|
* abstraction.
|
||||||
*/
|
*/
|
||||||
readonly state: StateNamespace;
|
readonly state: StateClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Address of the program.
|
* Address of the program.
|
||||||
|
@ -249,15 +255,15 @@ export class Program {
|
||||||
instruction,
|
instruction,
|
||||||
transaction,
|
transaction,
|
||||||
account,
|
account,
|
||||||
state,
|
|
||||||
simulate,
|
simulate,
|
||||||
|
state,
|
||||||
] = NamespaceFactory.build(idl, this._coder, programId, this._provider);
|
] = NamespaceFactory.build(idl, this._coder, programId, this._provider);
|
||||||
this.rpc = rpc;
|
this.rpc = rpc;
|
||||||
this.instruction = instruction;
|
this.instruction = instruction;
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.state = state;
|
|
||||||
this.simulate = simulate;
|
this.simulate = simulate;
|
||||||
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,39 +9,260 @@ import {
|
||||||
Commitment,
|
Commitment,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import Provider from "../../provider";
|
import Provider from "../../provider";
|
||||||
import { Idl } from "../../idl";
|
import { Idl, IdlTypeDef } from "../../idl";
|
||||||
import Coder, {
|
import Coder, {
|
||||||
ACCOUNT_DISCRIMINATOR_SIZE,
|
ACCOUNT_DISCRIMINATOR_SIZE,
|
||||||
accountDiscriminator,
|
accountDiscriminator,
|
||||||
accountSize,
|
accountSize,
|
||||||
} from "../../coder";
|
} from "../../coder";
|
||||||
import { Subscription, Address, translateAddress } from "../common";
|
import { Subscription, Address, translateAddress } from "../common";
|
||||||
|
import { getProvider } from "../../";
|
||||||
|
|
||||||
/**
|
export default class AccountFactory {
|
||||||
* Accounts is a dynamically generated object to fetch any given account
|
public static build(
|
||||||
* of a program.
|
idl: Idl,
|
||||||
*/
|
coder: Coder,
|
||||||
export interface AccountNamespace {
|
programId: PublicKey,
|
||||||
[key: string]: AccountFn;
|
provider: Provider
|
||||||
|
): AccountNamespace {
|
||||||
|
const accountFns: AccountNamespace = {};
|
||||||
|
|
||||||
|
idl.accounts.forEach((idlAccount) => {
|
||||||
|
const name = camelCase(idlAccount.name);
|
||||||
|
accountFns[name] = new AccountClient(
|
||||||
|
idl,
|
||||||
|
idlAccount,
|
||||||
|
programId,
|
||||||
|
provider,
|
||||||
|
coder
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return accountFns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account is a function returning a deserialized account, given an address.
|
* The namespace provides handles to an [[AccountClient]] object for each
|
||||||
|
* account in a program.
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* account.<account-client>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* To fetch a `Counter` account from the above example,
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const counter = await program.account.counter.fetch(address);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* For the full API, see the [[AccountClient]] reference.
|
||||||
*/
|
*/
|
||||||
export type AccountFn<T = any> = AccountProps & ((address: PublicKey) => T);
|
export interface AccountNamespace {
|
||||||
|
[key: string]: AccountClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AccountClient {
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes in this account.
|
||||||
|
*/
|
||||||
|
get size(): number {
|
||||||
|
return this._size;
|
||||||
|
}
|
||||||
|
private _size: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non function properties on the acccount namespace.
|
* Returns the program ID owning all accounts.
|
||||||
*/
|
*/
|
||||||
type AccountProps = {
|
get programId(): PublicKey {
|
||||||
size: number;
|
return this._programId;
|
||||||
all: (filter?: Buffer) => Promise<ProgramAccount<any>[]>;
|
}
|
||||||
subscribe: (address: Address, commitment?: Commitment) => EventEmitter;
|
private _programId: PublicKey;
|
||||||
unsubscribe: (address: Address) => void;
|
|
||||||
createInstruction: (signer: Signer) => Promise<TransactionInstruction>;
|
/**
|
||||||
associated: (...args: PublicKey[]) => Promise<any>;
|
* Returns the cleint's wallet and network provider.
|
||||||
associatedAddress: (...args: PublicKey[]) => Promise<PublicKey>;
|
*/
|
||||||
|
get provider(): Provider {
|
||||||
|
return this._provider;
|
||||||
|
}
|
||||||
|
private _provider: Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the coder.
|
||||||
|
*/
|
||||||
|
get coder(): Coder {
|
||||||
|
return this._coder;
|
||||||
|
}
|
||||||
|
private _coder: Coder;
|
||||||
|
|
||||||
|
private _idlAccount: IdlTypeDef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
idl: Idl,
|
||||||
|
idlAccount: IdlTypeDef,
|
||||||
|
programId: PublicKey,
|
||||||
|
provider?: Provider,
|
||||||
|
coder?: Coder
|
||||||
|
) {
|
||||||
|
this._idlAccount = idlAccount;
|
||||||
|
this._programId = programId;
|
||||||
|
this._provider = provider ?? getProvider();
|
||||||
|
this._coder = coder ?? new Coder(idl);
|
||||||
|
this._size = ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a deserialized account.
|
||||||
|
*
|
||||||
|
* @param address The address of the account to fetch.
|
||||||
|
*/
|
||||||
|
async fetch(address: Address): Promise<Object> {
|
||||||
|
const accountInfo = await this._provider.connection.getAccountInfo(
|
||||||
|
translateAddress(address)
|
||||||
|
);
|
||||||
|
if (accountInfo === null) {
|
||||||
|
throw new Error(`Account does not exist ${address.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert the account discriminator is correct.
|
||||||
|
const discriminator = await accountDiscriminator(this._idlAccount.name);
|
||||||
|
if (discriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||||
|
throw new Error("Invalid account discriminator");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._coder.accounts.decode(this._idlAccount.name, accountInfo.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all instances of this account type for the program.
|
||||||
|
*/
|
||||||
|
async all(filter?: Buffer): Promise<ProgramAccount<any>[]> {
|
||||||
|
let bytes = await accountDiscriminator(this._idlAccount.name);
|
||||||
|
if (filter !== undefined) {
|
||||||
|
bytes = Buffer.concat([bytes, filter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = await this._provider.connection.getProgramAccounts(
|
||||||
|
this._programId,
|
||||||
|
{
|
||||||
|
commitment: this._provider.connection.commitment,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
memcmp: {
|
||||||
|
offset: 0,
|
||||||
|
bytes: bs58.encode(bytes),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return resp.map(({ pubkey, account }) => {
|
||||||
|
return {
|
||||||
|
publicKey: pubkey,
|
||||||
|
account: this._coder.accounts.decode(
|
||||||
|
this._idlAccount.name,
|
||||||
|
account.data
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an `EventEmitter` emitting a "change" event whenever the account
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
subscribe(address: Address, commitment?: Commitment): EventEmitter {
|
||||||
|
if (subscriptions.get(address.toString())) {
|
||||||
|
return subscriptions.get(address.toString()).ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ee = new EventEmitter();
|
||||||
|
address = translateAddress(address);
|
||||||
|
const listener = this._provider.connection.onAccountChange(
|
||||||
|
address,
|
||||||
|
(acc) => {
|
||||||
|
const account = this._coder.accounts.decode(
|
||||||
|
this._idlAccount.name,
|
||||||
|
acc.data
|
||||||
|
);
|
||||||
|
ee.emit("change", account);
|
||||||
|
},
|
||||||
|
commitment
|
||||||
|
);
|
||||||
|
|
||||||
|
subscriptions.set(address.toString(), {
|
||||||
|
ee,
|
||||||
|
listener,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from the account at the given address.
|
||||||
|
*/
|
||||||
|
unsubscribe(address: Address) {
|
||||||
|
let sub = subscriptions.get(address.toString());
|
||||||
|
if (!sub) {
|
||||||
|
console.warn("Address is not subscribed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (subscriptions) {
|
||||||
|
this._provider.connection
|
||||||
|
.removeAccountChangeListener(sub.listener)
|
||||||
|
.then(() => {
|
||||||
|
subscriptions.delete(address.toString());
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instruction for creating this account.
|
||||||
|
*/
|
||||||
|
async createInstruction(
|
||||||
|
signer: Signer,
|
||||||
|
sizeOverride?: number
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
const size = this.size;
|
||||||
|
|
||||||
|
return SystemProgram.createAccount({
|
||||||
|
fromPubkey: this._provider.wallet.publicKey,
|
||||||
|
newAccountPubkey: signer.publicKey,
|
||||||
|
space: sizeOverride ?? size,
|
||||||
|
lamports: await this._provider.connection.getMinimumBalanceForRentExemption(
|
||||||
|
sizeOverride ?? size
|
||||||
|
),
|
||||||
|
programId: this._programId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function returning the associated account. Args are keys to associate.
|
||||||
|
* Order matters.
|
||||||
|
*/
|
||||||
|
async associated(...args: PublicKey[]): Promise<any> {
|
||||||
|
const addr = await this.associatedAddress(...args);
|
||||||
|
return await this.fetch(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function returning the associated address. Args are keys to associate.
|
||||||
|
* Order matters.
|
||||||
|
*/
|
||||||
|
async associatedAddress(...args: PublicKey[]): Promise<PublicKey> {
|
||||||
|
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
||||||
|
args.forEach((arg) => {
|
||||||
|
seeds.push(translateAddress(arg).toBuffer());
|
||||||
|
});
|
||||||
|
const [assoc] = await PublicKey.findProgramAddress(seeds, this._programId);
|
||||||
|
return assoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -55,176 +276,3 @@ export type ProgramAccount<T = any> = {
|
||||||
|
|
||||||
// Tracks all subscriptions.
|
// Tracks all subscriptions.
|
||||||
const subscriptions: Map<string, Subscription> = new Map();
|
const subscriptions: Map<string, Subscription> = new Map();
|
||||||
|
|
||||||
export default class AccountFactory {
|
|
||||||
// Returns the generated accounts namespace.
|
|
||||||
public static build(
|
|
||||||
idl: Idl,
|
|
||||||
coder: Coder,
|
|
||||||
programId: PublicKey,
|
|
||||||
provider: Provider
|
|
||||||
): AccountNamespace {
|
|
||||||
const accountFns: AccountNamespace = {};
|
|
||||||
|
|
||||||
idl.accounts.forEach((idlAccount) => {
|
|
||||||
const name = camelCase(idlAccount.name);
|
|
||||||
|
|
||||||
// Fetches the decoded account from the network.
|
|
||||||
const accountsNamespace = async (address: Address): Promise<any> => {
|
|
||||||
const accountInfo = await provider.connection.getAccountInfo(
|
|
||||||
translateAddress(address)
|
|
||||||
);
|
|
||||||
if (accountInfo === null) {
|
|
||||||
throw new Error(`Account does not exist ${address.toString()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert the account discriminator is correct.
|
|
||||||
const discriminator = await accountDiscriminator(idlAccount.name);
|
|
||||||
if (discriminator.compare(accountInfo.data.slice(0, 8))) {
|
|
||||||
throw new Error("Invalid account discriminator");
|
|
||||||
}
|
|
||||||
|
|
||||||
return coder.accounts.decode(idlAccount.name, accountInfo.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns the size of the account.
|
|
||||||
// @ts-ignore
|
|
||||||
accountsNamespace["size"] =
|
|
||||||
ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
|
|
||||||
|
|
||||||
// Returns an instruction for creating this account.
|
|
||||||
// @ts-ignore
|
|
||||||
accountsNamespace["createInstruction"] = async (
|
|
||||||
signer: Signer,
|
|
||||||
sizeOverride?: number
|
|
||||||
): Promise<TransactionInstruction> => {
|
|
||||||
// @ts-ignore
|
|
||||||
const size = accountsNamespace["size"];
|
|
||||||
|
|
||||||
return SystemProgram.createAccount({
|
|
||||||
fromPubkey: provider.wallet.publicKey,
|
|
||||||
newAccountPubkey: signer.publicKey,
|
|
||||||
space: sizeOverride ?? size,
|
|
||||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(
|
|
||||||
sizeOverride ?? size
|
|
||||||
),
|
|
||||||
programId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subscribes to all changes to this account.
|
|
||||||
// @ts-ignore
|
|
||||||
accountsNamespace["subscribe"] = (
|
|
||||||
address: Address,
|
|
||||||
commitment?: Commitment
|
|
||||||
): EventEmitter => {
|
|
||||||
if (subscriptions.get(address.toString())) {
|
|
||||||
return subscriptions.get(address.toString()).ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ee = new EventEmitter();
|
|
||||||
address = translateAddress(address);
|
|
||||||
const listener = provider.connection.onAccountChange(
|
|
||||||
address,
|
|
||||||
(acc) => {
|
|
||||||
const account = coder.accounts.decode(idlAccount.name, acc.data);
|
|
||||||
ee.emit("change", account);
|
|
||||||
},
|
|
||||||
commitment
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.set(address.toString(), {
|
|
||||||
ee,
|
|
||||||
listener,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ee;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unsubscribes to account changes.
|
|
||||||
// @ts-ignore
|
|
||||||
accountsNamespace["unsubscribe"] = (address: Address) => {
|
|
||||||
let sub = subscriptions.get(address.toString());
|
|
||||||
if (!sub) {
|
|
||||||
console.warn("Address is not subscribed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (subscriptions) {
|
|
||||||
provider.connection
|
|
||||||
.removeAccountChangeListener(sub.listener)
|
|
||||||
.then(() => {
|
|
||||||
subscriptions.delete(address.toString());
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns all instances of this account type for the program.
|
|
||||||
// @ts-ignore
|
|
||||||
accountsNamespace["all"] = async (
|
|
||||||
filter?: Buffer
|
|
||||||
): Promise<ProgramAccount<any>[]> => {
|
|
||||||
let bytes = await accountDiscriminator(idlAccount.name);
|
|
||||||
if (filter !== undefined) {
|
|
||||||
bytes = Buffer.concat([bytes, filter]);
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
let resp = await provider.connection._rpcRequest("getProgramAccounts", [
|
|
||||||
programId.toBase58(),
|
|
||||||
{
|
|
||||||
commitment: provider.connection.commitment,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
memcmp: {
|
|
||||||
offset: 0,
|
|
||||||
bytes: bs58.encode(bytes),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
if (resp.error) {
|
|
||||||
console.error(resp);
|
|
||||||
throw new Error("Failed to get accounts");
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
resp.result
|
|
||||||
// @ts-ignore
|
|
||||||
.map(({ pubkey, account: { data } }) => {
|
|
||||||
data = bs58.decode(data);
|
|
||||||
return {
|
|
||||||
publicKey: new PublicKey(pubkey),
|
|
||||||
account: coder.accounts.decode(idlAccount.name, data),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function returning the associated address. Args are keys to associate.
|
|
||||||
// Order matters.
|
|
||||||
accountsNamespace["associatedAddress"] = async (
|
|
||||||
...args: Address[]
|
|
||||||
): Promise<PublicKey> => {
|
|
||||||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
|
||||||
args.forEach((arg) => {
|
|
||||||
seeds.push(translateAddress(arg).toBuffer());
|
|
||||||
});
|
|
||||||
const [assoc] = await PublicKey.findProgramAddress(seeds, programId);
|
|
||||||
return assoc;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function returning the associated account. Args are keys to associate.
|
|
||||||
// Order matters.
|
|
||||||
accountsNamespace["associated"] = async (
|
|
||||||
...args: Address[]
|
|
||||||
): Promise<any> => {
|
|
||||||
const addr = await accountsNamespace["associatedAddress"](...args);
|
|
||||||
return await accountsNamespace(addr);
|
|
||||||
};
|
|
||||||
|
|
||||||
accountFns[name] = accountsNamespace;
|
|
||||||
});
|
|
||||||
|
|
||||||
return accountFns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,21 +3,21 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import Coder from "../../coder";
|
import Coder from "../../coder";
|
||||||
import Provider from "../../provider";
|
import Provider from "../../provider";
|
||||||
import { Idl } from "../../idl";
|
import { Idl } from "../../idl";
|
||||||
import { parseIdlErrors } from "../common";
|
import StateFactory, { StateClient } from "./state";
|
||||||
import StateFactory, { StateNamespace } from "./state";
|
|
||||||
import InstructionFactory, { InstructionNamespace } from "./instruction";
|
import InstructionFactory, { InstructionNamespace } from "./instruction";
|
||||||
import TransactionFactory, { TransactionNamespace } from "./transaction";
|
import TransactionFactory, { TransactionNamespace } from "./transaction";
|
||||||
import RpcFactory, { RpcNamespace } from "./rpc";
|
import RpcFactory, { RpcNamespace } from "./rpc";
|
||||||
import AccountFactory, { AccountNamespace } from "./account";
|
import AccountFactory, { AccountNamespace } from "./account";
|
||||||
import SimulateFactory, { SimulateNamespace } from "./simulate";
|
import SimulateFactory, { SimulateNamespace } from "./simulate";
|
||||||
|
import { parseIdlErrors } from "../common";
|
||||||
|
|
||||||
// Re-exports.
|
// Re-exports.
|
||||||
export { StateNamespace } from "./state";
|
export { StateClient } from "./state";
|
||||||
export { InstructionNamespace } from "./instruction";
|
export { InstructionNamespace, InstructionFn } from "./instruction";
|
||||||
export { TransactionNamespace, TxFn } from "./transaction";
|
export { TransactionNamespace, TransactionFn } from "./transaction";
|
||||||
export { RpcNamespace, RpcFn } from "./rpc";
|
export { RpcNamespace, RpcFn } from "./rpc";
|
||||||
export { AccountNamespace, AccountFn, ProgramAccount } from "./account";
|
export { AccountNamespace, AccountClient, ProgramAccount } from "./account";
|
||||||
export { SimulateNamespace } from "./simulate";
|
export { SimulateNamespace, SimulateFn } from "./simulate";
|
||||||
|
|
||||||
export default class NamespaceFactory {
|
export default class NamespaceFactory {
|
||||||
/**
|
/**
|
||||||
|
@ -33,26 +33,24 @@ export default class NamespaceFactory {
|
||||||
InstructionNamespace,
|
InstructionNamespace,
|
||||||
TransactionNamespace,
|
TransactionNamespace,
|
||||||
AccountNamespace,
|
AccountNamespace,
|
||||||
StateNamespace,
|
SimulateNamespace,
|
||||||
SimulateNamespace
|
StateClient
|
||||||
] {
|
] {
|
||||||
const idlErrors = parseIdlErrors(idl);
|
|
||||||
|
|
||||||
const rpc: RpcNamespace = {};
|
const rpc: RpcNamespace = {};
|
||||||
const instruction: InstructionNamespace = {};
|
const instruction: InstructionNamespace = {};
|
||||||
const transaction: TransactionNamespace = {};
|
const transaction: TransactionNamespace = {};
|
||||||
const simulate: SimulateNamespace = {};
|
const simulate: SimulateNamespace = {};
|
||||||
|
|
||||||
const state = StateFactory.build(
|
const idlErrors = parseIdlErrors(idl);
|
||||||
idl,
|
|
||||||
coder,
|
const state = StateFactory.build(idl, coder, programId, provider);
|
||||||
programId,
|
|
||||||
idlErrors,
|
|
||||||
provider
|
|
||||||
);
|
|
||||||
|
|
||||||
idl.instructions.forEach((idlIx) => {
|
idl.instructions.forEach((idlIx) => {
|
||||||
const ixItem = InstructionFactory.build(idlIx, coder, programId);
|
const ixItem = InstructionFactory.build(
|
||||||
|
idlIx,
|
||||||
|
(ixName: string, ix: any) => coder.instruction.encode(ixName, ix),
|
||||||
|
programId
|
||||||
|
);
|
||||||
const txItem = TransactionFactory.build(idlIx, ixItem);
|
const txItem = TransactionFactory.build(idlIx, ixItem);
|
||||||
const rpcItem = RpcFactory.build(idlIx, txItem, idlErrors, provider);
|
const rpcItem = RpcFactory.build(idlIx, txItem, idlErrors, provider);
|
||||||
const simulateItem = SimulateFactory.build(
|
const simulateItem = SimulateFactory.build(
|
||||||
|
@ -77,6 +75,6 @@ export default class NamespaceFactory {
|
||||||
? AccountFactory.build(idl, coder, programId, provider)
|
? AccountFactory.build(idl, coder, programId, provider)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
return [rpc, instruction, transaction, account, state, simulate];
|
return [rpc, instruction, transaction, account, simulate, state];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||||
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
|
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
|
||||||
import { IdlError } from "../../error";
|
import { IdlError } from "../../error";
|
||||||
import Coder from "../../coder";
|
|
||||||
import {
|
import {
|
||||||
toInstruction,
|
toInstruction,
|
||||||
validateAccounts,
|
validateAccounts,
|
||||||
|
@ -10,28 +9,12 @@ import {
|
||||||
} from "../common";
|
} from "../common";
|
||||||
import { Accounts, splitArgsAndCtx } from "../context";
|
import { Accounts, splitArgsAndCtx } from "../context";
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically generated instruction namespace.
|
|
||||||
*/
|
|
||||||
export interface InstructionNamespace {
|
|
||||||
[key: string]: IxFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ix is a function to create a `TransactionInstruction` generated from an IDL.
|
|
||||||
*/
|
|
||||||
export type IxFn = IxProps & ((...args: any[]) => any);
|
|
||||||
type IxProps = {
|
|
||||||
accounts: (ctx: Accounts) => any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class InstructionNamespaceFactory {
|
export default class InstructionNamespaceFactory {
|
||||||
// Builds the instuction namespace.
|
|
||||||
public static build(
|
public static build(
|
||||||
idlIx: IdlInstruction,
|
idlIx: IdlInstruction,
|
||||||
coder: Coder,
|
encodeFn: InstructionEncodeFn,
|
||||||
programId: PublicKey
|
programId: PublicKey
|
||||||
): IxFn {
|
): InstructionFn {
|
||||||
if (idlIx.name === "_inner") {
|
if (idlIx.name === "_inner") {
|
||||||
throw new IdlError("the _inner name is reserved");
|
throw new IdlError("the _inner name is reserved");
|
||||||
}
|
}
|
||||||
|
@ -41,10 +24,7 @@ export default class InstructionNamespaceFactory {
|
||||||
validateAccounts(idlIx.accounts, ctx.accounts);
|
validateAccounts(idlIx.accounts, ctx.accounts);
|
||||||
validateInstruction(idlIx, ...args);
|
validateInstruction(idlIx, ...args);
|
||||||
|
|
||||||
const keys = InstructionNamespaceFactory.accountsArray(
|
const keys = ix.accounts(ctx.accounts);
|
||||||
ctx.accounts,
|
|
||||||
idlIx.accounts
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ctx.remainingAccounts !== undefined) {
|
if (ctx.remainingAccounts !== undefined) {
|
||||||
keys.push(...ctx.remainingAccounts);
|
keys.push(...ctx.remainingAccounts);
|
||||||
|
@ -56,10 +36,7 @@ export default class InstructionNamespaceFactory {
|
||||||
return new TransactionInstruction({
|
return new TransactionInstruction({
|
||||||
keys,
|
keys,
|
||||||
programId,
|
programId,
|
||||||
data: coder.instruction.encode(
|
data: encodeFn(idlIx.name, toInstruction(idlIx, ...ixArgs)),
|
||||||
idlIx.name,
|
|
||||||
toInstruction(idlIx, ...ixArgs)
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,6 +73,51 @@ export default class InstructionNamespaceFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace provides functions to build [[TransactionInstruction]]
|
||||||
|
* objects for each method of a program.
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* instruction.<method>(...args, ctx);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* 1. `args` - The positional arguments for the program. The type and number
|
||||||
|
* of these arguments depend on the program being used.
|
||||||
|
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||||
|
* Always the last parameter in the method call.
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* To create an instruction for the `increment` method above,
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const tx = await program.instruction.increment({
|
||||||
|
* accounts: {
|
||||||
|
* counter,
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export interface InstructionNamespace {
|
||||||
|
[key: string]: InstructionFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to create a `TransactionInstruction` generated from an IDL.
|
||||||
|
* Additionally it provides an `accounts` utility method, returning a list
|
||||||
|
* of ordered accounts for the instruction.
|
||||||
|
*/
|
||||||
|
export type InstructionFn = IxProps & ((...args: any[]) => any);
|
||||||
|
type IxProps = {
|
||||||
|
accounts: (ctx: Accounts) => any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer;
|
||||||
|
|
||||||
// Throws error if any argument required for the `ix` is not given.
|
// Throws error if any argument required for the `ix` is not given.
|
||||||
function validateInstruction(ix: IdlInstruction, ...args: any[]) {
|
function validateInstruction(ix: IdlInstruction, ...args: any[]) {
|
||||||
// todo
|
// todo
|
||||||
|
|
|
@ -3,25 +3,12 @@ import Provider from "../../provider";
|
||||||
import { IdlInstruction } from "../../idl";
|
import { IdlInstruction } from "../../idl";
|
||||||
import { translateError } from "../common";
|
import { translateError } from "../common";
|
||||||
import { splitArgsAndCtx } from "../context";
|
import { splitArgsAndCtx } from "../context";
|
||||||
import { TxFn } from "./transaction";
|
import { TransactionFn } from "./transaction";
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically generated rpc namespace.
|
|
||||||
*/
|
|
||||||
export interface RpcNamespace {
|
|
||||||
[key: string]: RpcFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RpcFn is a single rpc method generated from an IDL.
|
|
||||||
*/
|
|
||||||
export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
|
|
||||||
|
|
||||||
export default class RpcFactory {
|
export default class RpcFactory {
|
||||||
// Builds the rpc namespace.
|
|
||||||
public static build(
|
public static build(
|
||||||
idlIx: IdlInstruction,
|
idlIx: IdlInstruction,
|
||||||
txFn: TxFn,
|
txFn: TransactionFn,
|
||||||
idlErrors: Map<number, string>,
|
idlErrors: Map<number, string>,
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): RpcFn {
|
): RpcFn {
|
||||||
|
@ -44,3 +31,47 @@ export default class RpcFactory {
|
||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace provides async methods to send signed transactions for each
|
||||||
|
* *non*-state method on Anchor program.
|
||||||
|
*
|
||||||
|
* Keys are method names, values are RPC functions returning a
|
||||||
|
* [[TransactionInstruction]].
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* rpc.<method>(...args, ctx);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* 1. `args` - The positional arguments for the program. The type and number
|
||||||
|
* of these arguments depend on the program being used.
|
||||||
|
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||||
|
* Always the last parameter in the method call.
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* To send a transaction invoking the `increment` method above,
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const txSignature = await program.rpc.increment({
|
||||||
|
* accounts: {
|
||||||
|
* counter,
|
||||||
|
* authority,
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export interface RpcNamespace {
|
||||||
|
[key: string]: RpcFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RpcFn is a single RPC method generated from an IDL, sending a transaction
|
||||||
|
* paid for and signed by the configured provider.
|
||||||
|
*/
|
||||||
|
export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
|
||||||
|
|
|
@ -3,33 +3,15 @@ import Provider from "../../provider";
|
||||||
import { IdlInstruction } from "../../idl";
|
import { IdlInstruction } from "../../idl";
|
||||||
import { translateError } from "../common";
|
import { translateError } from "../common";
|
||||||
import { splitArgsAndCtx } from "../context";
|
import { splitArgsAndCtx } from "../context";
|
||||||
import { TxFn } from "./transaction";
|
import { TransactionFn } from "./transaction";
|
||||||
import { EventParser } from "../event";
|
import { EventParser } from "../event";
|
||||||
import Coder from "../../coder";
|
import Coder from "../../coder";
|
||||||
import { Idl } from "../../idl";
|
import { Idl } from "../../idl";
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically generated simualte namespace.
|
|
||||||
*/
|
|
||||||
export interface SimulateNamespace {
|
|
||||||
[key: string]: SimulateFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RpcFn is a single rpc method generated from an IDL.
|
|
||||||
*/
|
|
||||||
export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
|
|
||||||
|
|
||||||
type SimulateResponse = {
|
|
||||||
events: Event[];
|
|
||||||
raw: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class SimulateFactory {
|
export default class SimulateFactory {
|
||||||
// Builds the rpc namespace.
|
|
||||||
public static build(
|
public static build(
|
||||||
idlIx: IdlInstruction,
|
idlIx: IdlInstruction,
|
||||||
txFn: TxFn,
|
txFn: TransactionFn,
|
||||||
idlErrors: Map<number, string>,
|
idlErrors: Map<number, string>,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
coder: Coder,
|
coder: Coder,
|
||||||
|
@ -74,3 +56,54 @@ export default class SimulateFactory {
|
||||||
return simulate;
|
return simulate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace provides functions to simulate transactions for each method
|
||||||
|
* of a program, returning a list of deserialized events *and* raw program
|
||||||
|
* logs.
|
||||||
|
*
|
||||||
|
* One can use this to read data calculated from a program on chain, by
|
||||||
|
* emitting an event in the program and reading the emitted event client side
|
||||||
|
* via the `simulate` namespace.
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* program.simulate.<method>(...args, ctx);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* 1. `args` - The positional arguments for the program. The type and number
|
||||||
|
* of these arguments depend on the program being used.
|
||||||
|
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||||
|
* Always the last parameter in the method call.
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* To simulate the `increment` method above,
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const events = await program.simulate.increment({
|
||||||
|
* accounts: {
|
||||||
|
* counter,
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export interface SimulateNamespace {
|
||||||
|
[key: string]: SimulateFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RpcFn is a single method generated from an IDL. It simulates a method
|
||||||
|
* against a cluster configured by the provider, returning a list of all the
|
||||||
|
* events and raw logs that were emitted during the execution of the
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
|
||||||
|
|
||||||
|
type SimulateResponse = {
|
||||||
|
events: Event[];
|
||||||
|
raw: string[];
|
||||||
|
};
|
||||||
|
|
|
@ -1,176 +1,224 @@
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
|
import camelCase from "camelcase";
|
||||||
import {
|
import {
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
|
||||||
TransactionSignature,
|
|
||||||
TransactionInstruction,
|
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
Commitment,
|
Commitment,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import Provider from "../../provider";
|
import Provider from "../../provider";
|
||||||
import { Idl, IdlStateMethod } from "../../idl";
|
import { Idl, IdlStateMethod } from "../../idl";
|
||||||
import Coder, { stateDiscriminator } from "../../coder";
|
import Coder, { stateDiscriminator } from "../../coder";
|
||||||
import { RpcNamespace, InstructionNamespace } from "./";
|
import { RpcNamespace, InstructionNamespace, TransactionNamespace } from "./";
|
||||||
import {
|
import { getProvider } from "../../";
|
||||||
Subscription,
|
import { Subscription, validateAccounts, parseIdlErrors } from "../common";
|
||||||
translateError,
|
import { findProgramAddressSync, createWithSeedSync } from "../../utils/pubkey";
|
||||||
toInstruction,
|
import { Accounts } from "../context";
|
||||||
validateAccounts,
|
|
||||||
} from "../common";
|
|
||||||
import { Accounts, splitArgsAndCtx } from "../context";
|
|
||||||
import InstructionNamespaceFactory from "./instruction";
|
import InstructionNamespaceFactory from "./instruction";
|
||||||
|
import RpcNamespaceFactory from "./rpc";
|
||||||
export type StateNamespace = () =>
|
import TransactionNamespaceFactory from "./transaction";
|
||||||
| Promise<any>
|
|
||||||
| {
|
|
||||||
address: () => Promise<PublicKey>;
|
|
||||||
rpc: RpcNamespace;
|
|
||||||
instruction: InstructionNamespace;
|
|
||||||
subscribe: (commitment?: Commitment) => EventEmitter;
|
|
||||||
unsubscribe: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class StateFactory {
|
export default class StateFactory {
|
||||||
// Builds the state namespace.
|
|
||||||
public static build(
|
public static build(
|
||||||
idl: Idl,
|
idl: Idl,
|
||||||
coder: Coder,
|
coder: Coder,
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
idlErrors: Map<number, string>,
|
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): StateNamespace | undefined {
|
): StateClient | undefined {
|
||||||
if (idl.state === undefined) {
|
if (idl.state === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
return new StateClient(idl, programId, provider, coder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetches the state object from the blockchain.
|
/**
|
||||||
const state = async (): Promise<any> => {
|
* A client for the program state. Similar to the base [[Program]] client,
|
||||||
const addr = await programStateAddress(programId);
|
* one can use this to send transactions and read accounts for the state
|
||||||
const accountInfo = await provider.connection.getAccountInfo(addr);
|
* abstraction.
|
||||||
|
*/
|
||||||
|
export class StateClient {
|
||||||
|
/**
|
||||||
|
* [[RpcNamespace]] for all state methods.
|
||||||
|
*/
|
||||||
|
readonly rpc: RpcNamespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [[InstructionNamespace]] for all state methods.
|
||||||
|
*/
|
||||||
|
readonly instruction: InstructionNamespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [[TransactionNamespace]] for all state methods.
|
||||||
|
*/
|
||||||
|
readonly transaction: TransactionNamespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the program ID owning the state.
|
||||||
|
*/
|
||||||
|
get programId(): PublicKey {
|
||||||
|
return this._programId;
|
||||||
|
}
|
||||||
|
private _programId: PublicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's wallet and network provider.
|
||||||
|
*/
|
||||||
|
get provider(): Provider {
|
||||||
|
return this._provider;
|
||||||
|
}
|
||||||
|
private _provider: Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the coder.
|
||||||
|
*/
|
||||||
|
get coder(): Coder {
|
||||||
|
return this._coder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _address: PublicKey;
|
||||||
|
private _coder: Coder;
|
||||||
|
private _idl: Idl;
|
||||||
|
private _sub: Subscription | null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
idl: Idl,
|
||||||
|
programId: PublicKey,
|
||||||
|
provider?: Provider,
|
||||||
|
coder?: Coder
|
||||||
|
) {
|
||||||
|
this._idl = idl;
|
||||||
|
this._programId = programId;
|
||||||
|
this._address = programStateAddress(programId);
|
||||||
|
this._provider = provider ?? getProvider();
|
||||||
|
this._coder = coder ?? new Coder(idl);
|
||||||
|
this._sub = null;
|
||||||
|
|
||||||
|
// Build namespaces.
|
||||||
|
const [instruction, transaction, rpc] = ((): [
|
||||||
|
InstructionNamespace,
|
||||||
|
TransactionNamespace,
|
||||||
|
RpcNamespace
|
||||||
|
] => {
|
||||||
|
let instruction: InstructionNamespace = {};
|
||||||
|
let transaction: TransactionNamespace = {};
|
||||||
|
let rpc: RpcNamespace = {};
|
||||||
|
|
||||||
|
idl.state.methods.forEach((m: IdlStateMethod) => {
|
||||||
|
// Build instruction method.
|
||||||
|
const ixItem = InstructionNamespaceFactory.build(
|
||||||
|
m,
|
||||||
|
(ixName: string, ix: any) =>
|
||||||
|
coder.instruction.encodeState(ixName, ix),
|
||||||
|
programId
|
||||||
|
);
|
||||||
|
ixItem["accounts"] = (accounts: Accounts) => {
|
||||||
|
const keys = stateInstructionKeys(programId, provider, m, accounts);
|
||||||
|
return keys.concat(
|
||||||
|
InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Build transaction method.
|
||||||
|
const txItem = TransactionNamespaceFactory.build(m, ixItem);
|
||||||
|
// Build RPC method.
|
||||||
|
const rpcItem = RpcNamespaceFactory.build(
|
||||||
|
m,
|
||||||
|
txItem,
|
||||||
|
parseIdlErrors(idl),
|
||||||
|
provider
|
||||||
|
);
|
||||||
|
|
||||||
|
// Attach them all to their respective namespaces.
|
||||||
|
const name = camelCase(m.name);
|
||||||
|
instruction[name] = ixItem;
|
||||||
|
transaction[name] = txItem;
|
||||||
|
rpc[name] = rpcItem;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [instruction, transaction, rpc];
|
||||||
|
})();
|
||||||
|
this.instruction = instruction;
|
||||||
|
this.transaction = transaction;
|
||||||
|
this.rpc = rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the deserialized state account.
|
||||||
|
*/
|
||||||
|
async fetch(): Promise<Object> {
|
||||||
|
const addr = this.address();
|
||||||
|
const accountInfo = await this.provider.connection.getAccountInfo(addr);
|
||||||
if (accountInfo === null) {
|
if (accountInfo === null) {
|
||||||
throw new Error(`Account does not exist ${addr.toString()}`);
|
throw new Error(`Account does not exist ${addr.toString()}`);
|
||||||
}
|
}
|
||||||
// Assert the account discriminator is correct.
|
// Assert the account discriminator is correct.
|
||||||
const expectedDiscriminator = await stateDiscriminator(
|
const expectedDiscriminator = await stateDiscriminator(
|
||||||
idl.state.struct.name
|
this._idl.state.struct.name
|
||||||
);
|
);
|
||||||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||||
throw new Error("Invalid account discriminator");
|
throw new Error("Invalid account discriminator");
|
||||||
}
|
}
|
||||||
return coder.state.decode(accountInfo.data);
|
return this.coder.state.decode(accountInfo.data);
|
||||||
};
|
|
||||||
|
|
||||||
// Namespace with all rpc functions.
|
|
||||||
const rpc: RpcNamespace = {};
|
|
||||||
const ix: InstructionNamespace = {};
|
|
||||||
|
|
||||||
idl.state.methods.forEach((m: IdlStateMethod) => {
|
|
||||||
const accounts = async (accounts: Accounts): Promise<any> => {
|
|
||||||
const keys = await stateInstructionKeys(
|
|
||||||
programId,
|
|
||||||
provider,
|
|
||||||
m,
|
|
||||||
accounts
|
|
||||||
);
|
|
||||||
return keys.concat(
|
|
||||||
InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const ixFn = async (...args: any[]): Promise<TransactionInstruction> => {
|
|
||||||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
|
|
||||||
return new TransactionInstruction({
|
|
||||||
keys: await accounts(ctx.accounts),
|
|
||||||
programId,
|
|
||||||
data: coder.instruction.encodeState(
|
|
||||||
m.name,
|
|
||||||
toInstruction(m, ...ixArgs)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
ixFn["accounts"] = accounts;
|
|
||||||
ix[m.name] = ixFn;
|
|
||||||
|
|
||||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
|
||||||
const [, ctx] = splitArgsAndCtx(m, [...args]);
|
|
||||||
const tx = new Transaction();
|
|
||||||
if (ctx.instructions !== undefined) {
|
|
||||||
tx.add(...ctx.instructions);
|
|
||||||
}
|
}
|
||||||
tx.add(await ix[m.name](...args));
|
|
||||||
try {
|
/**
|
||||||
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
* Returns the state address.
|
||||||
return txSig;
|
*/
|
||||||
} catch (err) {
|
address(): PublicKey {
|
||||||
let translatedErr = translateError(idlErrors, err);
|
return this._address;
|
||||||
if (translatedErr === null) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
throw translatedErr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
state["rpc"] = rpc;
|
/**
|
||||||
state["instruction"] = ix;
|
* Returns an `EventEmitter` with a `"change"` event that's fired whenever
|
||||||
// Calculates the address of the program's global state object account.
|
* the state account cahnges.
|
||||||
state["address"] = async (): Promise<PublicKey> =>
|
*/
|
||||||
programStateAddress(programId);
|
subscribe(commitment?: Commitment): EventEmitter {
|
||||||
|
if (this._sub !== null) {
|
||||||
// Subscription singleton.
|
return this._sub.ee;
|
||||||
let sub: null | Subscription = null;
|
|
||||||
|
|
||||||
// Subscribe to account changes.
|
|
||||||
state["subscribe"] = (commitment?: Commitment): EventEmitter => {
|
|
||||||
if (sub !== null) {
|
|
||||||
return sub.ee;
|
|
||||||
}
|
}
|
||||||
const ee = new EventEmitter();
|
const ee = new EventEmitter();
|
||||||
|
|
||||||
state["address"]().then((address) => {
|
const listener = this.provider.connection.onAccountChange(
|
||||||
const listener = provider.connection.onAccountChange(
|
this.address(),
|
||||||
address,
|
|
||||||
(acc) => {
|
(acc) => {
|
||||||
const account = coder.state.decode(acc.data);
|
const account = this.coder.state.decode(acc.data);
|
||||||
ee.emit("change", account);
|
ee.emit("change", account);
|
||||||
},
|
},
|
||||||
commitment
|
commitment
|
||||||
);
|
);
|
||||||
|
|
||||||
sub = {
|
this._sub = {
|
||||||
ee,
|
ee,
|
||||||
listener,
|
listener,
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
return ee;
|
return ee;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Unsubscribe from account changes.
|
/**
|
||||||
state["unsubscribe"] = () => {
|
* Unsubscribes to state changes.
|
||||||
if (sub !== null) {
|
*/
|
||||||
provider.connection
|
unsubscribe() {
|
||||||
.removeAccountChangeListener(sub.listener)
|
if (this._sub !== null) {
|
||||||
|
this.provider.connection
|
||||||
|
.removeAccountChangeListener(this._sub.listener)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
sub = null;
|
this._sub = null;
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates the deterministic address of the program's "state" account.
|
// Calculates the deterministic address of the program's "state" account.
|
||||||
async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
|
function programStateAddress(programId: PublicKey): PublicKey {
|
||||||
let [registrySigner] = await PublicKey.findProgramAddress([], programId);
|
let [registrySigner] = findProgramAddressSync([], programId);
|
||||||
return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
|
return createWithSeedSync(registrySigner, "unversioned", programId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the common keys that are prepended to all instructions targeting
|
// Returns the common keys that are prepended to all instructions targeting
|
||||||
// the "state" of a program.
|
// the "state" of a program.
|
||||||
async function stateInstructionKeys(
|
function stateInstructionKeys(
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
m: IdlStateMethod,
|
m: IdlStateMethod,
|
||||||
|
@ -178,7 +226,7 @@ async function stateInstructionKeys(
|
||||||
) {
|
) {
|
||||||
if (m.name === "new") {
|
if (m.name === "new") {
|
||||||
// Ctor `new` method.
|
// Ctor `new` method.
|
||||||
const [programSigner] = await PublicKey.findProgramAddress([], programId);
|
const [programSigner] = findProgramAddressSync([], programId);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
pubkey: provider.wallet.publicKey,
|
pubkey: provider.wallet.publicKey,
|
||||||
|
@ -186,7 +234,7 @@ async function stateInstructionKeys(
|
||||||
isSigner: true,
|
isSigner: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pubkey: await programStateAddress(programId),
|
pubkey: programStateAddress(programId),
|
||||||
isWritable: true,
|
isWritable: true,
|
||||||
isSigner: false,
|
isSigner: false,
|
||||||
},
|
},
|
||||||
|
@ -208,7 +256,7 @@ async function stateInstructionKeys(
|
||||||
validateAccounts(m.accounts, accounts);
|
validateAccounts(m.accounts, accounts);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
pubkey: await programStateAddress(programId),
|
pubkey: programStateAddress(programId),
|
||||||
isWritable: true,
|
isWritable: true,
|
||||||
isSigner: false,
|
isSigner: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
import { Transaction } from "@solana/web3.js";
|
import { Transaction } from "@solana/web3.js";
|
||||||
import { IdlInstruction } from "../../idl";
|
import { IdlInstruction } from "../../idl";
|
||||||
import { splitArgsAndCtx } from "../context";
|
import { splitArgsAndCtx } from "../context";
|
||||||
import { IxFn } from "./instruction";
|
import { InstructionFn } from "./instruction";
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically generated transaction namespace.
|
|
||||||
*/
|
|
||||||
export interface TransactionNamespace {
|
|
||||||
[key: string]: TxFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tx is a function to create a `Transaction` generate from an IDL.
|
|
||||||
*/
|
|
||||||
export type TxFn = (...args: any[]) => Transaction;
|
|
||||||
|
|
||||||
export default class TransactionFactory {
|
export default class TransactionFactory {
|
||||||
// Builds the transaction namespace.
|
public static build(
|
||||||
public static build(idlIx: IdlInstruction, ixFn: IxFn): TxFn {
|
idlIx: IdlInstruction,
|
||||||
|
ixFn: InstructionFn
|
||||||
|
): TransactionFn {
|
||||||
const txFn = (...args: any[]): Transaction => {
|
const txFn = (...args: any[]): Transaction => {
|
||||||
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||||
const tx = new Transaction();
|
const tx = new Transaction();
|
||||||
|
@ -31,3 +21,41 @@ export default class TransactionFactory {
|
||||||
return txFn;
|
return txFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace provides functions to build [[Transaction]] objects for each
|
||||||
|
* method of a program.
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* program.transaction.<method>(...args, ctx);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## Parameters
|
||||||
|
*
|
||||||
|
* 1. `args` - The positional arguments for the program. The type and number
|
||||||
|
* of these arguments depend on the program being used.
|
||||||
|
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||||
|
* Always the last parameter in the method call.
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* To create an instruction for the `increment` method above,
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const tx = await program.transaction.increment({
|
||||||
|
* accounts: {
|
||||||
|
* counter,
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export interface TransactionNamespace {
|
||||||
|
[key: string]: TransactionFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tx is a function to create a `Transaction` for a given program instruction.
|
||||||
|
*/
|
||||||
|
export type TransactionFn = (...args: any[]) => Transaction;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { sha256 } from "crypto-hash";
|
||||||
|
import * as bs58 from "bs58";
|
||||||
|
import * as rpc from "./rpc";
|
||||||
|
import * as publicKey from "./pubkey";
|
||||||
|
|
||||||
|
export function decodeUtf8(array: Uint8Array): string {
|
||||||
|
const decoder =
|
||||||
|
typeof TextDecoder === "undefined"
|
||||||
|
? new (require("util").TextDecoder)("utf-8") // Node.
|
||||||
|
: new TextDecoder("utf-8"); // Browser.
|
||||||
|
return decoder.decode(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { sha256, bs58, rpc, publicKey };
|
|
@ -0,0 +1,78 @@
|
||||||
|
import BN from "bn.js";
|
||||||
|
import { sha256 as sha256Sync } from "js-sha256";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
|
||||||
|
// Sync version of web3.PublicKey.createWithSeed.
|
||||||
|
export function createWithSeedSync(
|
||||||
|
fromPublicKey: PublicKey,
|
||||||
|
seed: string,
|
||||||
|
programId: PublicKey
|
||||||
|
): PublicKey {
|
||||||
|
const buffer = Buffer.concat([
|
||||||
|
fromPublicKey.toBuffer(),
|
||||||
|
Buffer.from(seed),
|
||||||
|
programId.toBuffer(),
|
||||||
|
]);
|
||||||
|
const hash = sha256Sync.digest(buffer);
|
||||||
|
return new PublicKey(Buffer.from(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync version of web3.PublicKey.createProgramAddress.
|
||||||
|
export function createProgramAddressSync(
|
||||||
|
seeds: Array<Buffer | Uint8Array>,
|
||||||
|
programId: PublicKey
|
||||||
|
): PublicKey {
|
||||||
|
const MAX_SEED_LENGTH = 32;
|
||||||
|
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
seeds.forEach(function (seed) {
|
||||||
|
if (seed.length > MAX_SEED_LENGTH) {
|
||||||
|
throw new TypeError(`Max seed length exceeded`);
|
||||||
|
}
|
||||||
|
buffer = Buffer.concat([buffer, toBuffer(seed)]);
|
||||||
|
});
|
||||||
|
buffer = Buffer.concat([
|
||||||
|
buffer,
|
||||||
|
programId.toBuffer(),
|
||||||
|
Buffer.from("ProgramDerivedAddress"),
|
||||||
|
]);
|
||||||
|
let hash = sha256Sync(new Uint8Array(buffer));
|
||||||
|
let publicKeyBytes = new BN(hash, 16).toArray(undefined, 32);
|
||||||
|
if (PublicKey.isOnCurve(new Uint8Array(publicKeyBytes))) {
|
||||||
|
throw new Error(`Invalid seeds, address must fall off the curve`);
|
||||||
|
}
|
||||||
|
return new PublicKey(publicKeyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync version of web3.PublicKey.findProgramAddress.
|
||||||
|
export function findProgramAddressSync(
|
||||||
|
seeds: Array<Buffer | Uint8Array>,
|
||||||
|
programId: PublicKey
|
||||||
|
): [PublicKey, number] {
|
||||||
|
let nonce = 255;
|
||||||
|
let address: PublicKey | undefined;
|
||||||
|
while (nonce != 0) {
|
||||||
|
try {
|
||||||
|
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
|
||||||
|
address = createProgramAddressSync(seedsWithNonce, programId);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof TypeError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
nonce--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return [address, nonce];
|
||||||
|
}
|
||||||
|
throw new Error(`Unable to find a viable program address nonce`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toBuffer = (arr: Buffer | Uint8Array | Array<number>): Buffer => {
|
||||||
|
if (arr instanceof Buffer) {
|
||||||
|
return arr;
|
||||||
|
} else if (arr instanceof Uint8Array) {
|
||||||
|
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||||
|
} else {
|
||||||
|
return Buffer.from(arr);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,14 +1,7 @@
|
||||||
import * as bs58 from "bs58";
|
|
||||||
import { sha256 } from "crypto-hash";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { PublicKey, AccountInfo, Connection } from "@solana/web3.js";
|
import { PublicKey, AccountInfo, Connection } from "@solana/web3.js";
|
||||||
import { idlAddress } from "./idl";
|
|
||||||
|
|
||||||
export const TOKEN_PROGRAM_ID = new PublicKey(
|
export async function getMultipleAccounts(
|
||||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
||||||
);
|
|
||||||
|
|
||||||
async function getMultipleAccounts(
|
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
publicKeys: PublicKey[]
|
publicKeys: PublicKey[]
|
||||||
): Promise<
|
): Promise<
|
||||||
|
@ -68,20 +61,3 @@ async function getMultipleAccounts(
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeUtf8(array: Uint8Array): string {
|
|
||||||
const decoder =
|
|
||||||
typeof TextDecoder === "undefined"
|
|
||||||
? new (require("util").TextDecoder)("utf-8") // Node.
|
|
||||||
: new TextDecoder("utf-8"); // Browser.
|
|
||||||
return decoder.decode(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
const utils = {
|
|
||||||
bs58,
|
|
||||||
sha256,
|
|
||||||
getMultipleAccounts,
|
|
||||||
idlAddress,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default utils;
|
|
Loading…
Reference in New Issue